linux-omap 2.6.37: fix mmc regulators when using wl1271 expansion board
authorKoen Kooi <koen@openembedded.org>
Sat, 22 Jan 2011 17:31:26 +0000 (18:31 +0100)
committerKoen Kooi <koen@openembedded.org>
Sun, 23 Jan 2011 11:52:26 +0000 (12:52 +0100)
linux-omap 2.6.37: cherry-pick a few wl1271 patches

* tested on a beagleboard xM with wl1271 expansionboard

linux-omap 2.6.37: update wl127xx patchset to include BT and FM drivers

Signed-off-by: Koen Kooi <koen@openembedded.org>
18 files changed:
recipes/linux/linux-omap-2.6.37/base/0013-omap3-beagleboard-add-WIP-support-for-beagleboardtoy.patch
recipes/linux/linux-omap-2.6.37/beagleboard/defconfig
recipes/linux/linux-omap-2.6.37/wl1271/0001-wl12xx-Read-MAC-address-from-NVS-file-on-HW-startup.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0002-wl1271-11n-Support-Add-Definitions.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0003-wl1271-11n-Support-ACX-Commands.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0004-wl1271-11n-Support-functionality-and-configuration-a.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0005-wl1271-set-wl-vif-only-if-add_interface-succeeded.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0006-wl12xx-Unset-bssid-filter-ssid-and-bssid-from-firmwa.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0007-drivers-media-radio-wl128x-FM-Driver-common-header-f.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0008-drivers-media-radio-wl128x-FM-Driver-V4L2-sources.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0009-drivers-media-radio-wl128x-FM-Driver-Common-sources.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0010-drivers-media-radio-wl128x-FM-driver-RX-sources.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0011-drivers-media-radio-wl128x-FM-driver-TX-sources.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0012-drivers-media-radio-wl128x-Kconfig-Makefile-for-wl12.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0013-drivers-media-radio-Update-Kconfig-and-Makefile-for-.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0014-drivers-misc-ti-st-change-protocol-parse-logic.patch [new file with mode: 0644]
recipes/linux/linux-omap-2.6.37/wl1271/0015-Bluetooth-btwilink-driver.patch [new file with mode: 0644]
recipes/linux/linux-omap_2.6.37.bb

index 1bb7e63..9b08d5f 100644 (file)
@@ -1,4 +1,4 @@
-From 554ec2d3959b1cc942337b67560bfb9c7b91f645 Mon Sep 17 00:00:00 2001
+From f7d71be36165002251727019b1a03a19938bfa64 Mon Sep 17 00:00:00 2001
 From: Koen Kooi <koen@beagleboard.org>
 Date: Mon, 20 Dec 2010 11:57:56 +0100
 Subject: [PATCH 13/28] omap3: beagleboard: add WIP support for beagleboardtoys WL12xx board
@@ -7,11 +7,11 @@ Based on a patch by Luciano Coelho <luciano.coelho@nokia.com>
 
 Signed-off-by: Koen Kooi <koen@beagleboard.org>
 ---
- arch/arm/mach-omap2/board-omap3beagle.c |   76 +++++++++++++++++++++++++++++++
- 1 files changed, 76 insertions(+), 0 deletions(-)
+ arch/arm/mach-omap2/board-omap3beagle.c |   84 ++++++++++++++++++++++++++++++-
+ 1 files changed, 83 insertions(+), 1 deletions(-)
 
 diff --git a/arch/arm/mach-omap2/board-omap3beagle.c b/arch/arm/mach-omap2/board-omap3beagle.c
-index 64a181e..f6c8152 100644
+index 64a181e..59b26da 100644
 --- a/arch/arm/mach-omap2/board-omap3beagle.c
 +++ b/arch/arm/mach-omap2/board-omap3beagle.c
 @@ -146,6 +146,67 @@ fail0:
@@ -37,12 +37,12 @@ index 64a181e..f6c8152 100644
 +              .gpio_wp        = 29,
 +      },
 +      {
-+              .name           = "wl1271",
-+              .mmc            = 2,
-+              .caps           = MMC_CAP_4_BIT_DATA,
-+              .gpio_wp        = -EINVAL,
-+              .gpio_cd        = -EINVAL,
-+              .nonremovable   = true,
++              .name           = "wl1271",
++              .mmc            = 2,
++              .caps           = MMC_CAP_4_BIT_DATA | MMC_CAP_POWER_OFF_CARD,
++              .gpio_wp        = -EINVAL,
++              .gpio_cd        = -EINVAL,
++              .nonremovable   = true,
 +      },
 +      {}      /* Terminator */
 + };
@@ -82,22 +82,33 @@ index 64a181e..f6c8152 100644
  #if defined(CONFIG_ENC28J60) || defined(CONFIG_ENC28J60_MODULE)
  
  #include <plat/mcspi.h>
-@@ -384,7 +445,14 @@ static int beagle_twl_gpio_setup(struct device *dev,
+@@ -384,11 +445,24 @@ static int beagle_twl_gpio_setup(struct device *dev,
        }
        /* gpio + 0 is "mmc0_cd" (input/IRQ) */
        mmc[0].gpio_cd = gpio + 0;
 +#if defined(CONFIG_WL1271) || defined(CONFIG_WL1271_MODULE)
-+      if(!strcmp(expansionboard_name, "fixme-beagletoy")) 
++      if(!strcmp(expansionboard_name, "fixme-beagletoy")) 
 +              omap2_hsmmc_init(mmcbbt);
-+      else
++              /* link regulators to MMC adapters */
++              beagle_vmmc1_supply.dev = mmcbbt[0].dev;
++              beagle_vsim_supply.dev = mmcbbt[0].dev;
++      } else {
 +              omap2_hsmmc_init(mmc);
++              /* link regulators to MMC adapters */
++              beagle_vmmc1_supply.dev = mmc[0].dev;
++              beagle_vsim_supply.dev = mmc[0].dev;
++      }
 +#else
        omap2_hsmmc_init(mmc);
-+#endif
+-
        /* link regulators to MMC adapters */
        beagle_vmmc1_supply.dev = mmc[0].dev;
-@@ -788,6 +856,14 @@ static void __init omap3_beagle_init(void)
+       beagle_vsim_supply.dev = mmc[0].dev;
++#endif
+       /* REVISIT: need ehci-omap hooks for external VBUS
+        * power switch and overcurrent detect
+@@ -788,6 +862,14 @@ static void __init omap3_beagle_init(void)
                gpio_export(162, 1);
        }
  
index 7723d85..cb21bec 100644 (file)
@@ -1,7 +1,7 @@
 #
 # Automatically generated make config: don't edit
-# Linux/arm 2.6.37-rc7 Kernel Configuration
-# Wed Dec 29 11:57:49 2010
+# Linux/arm 2.6.37 Kernel Configuration
+# Sun Jan 23 11:58:18 2011
 #
 CONFIG_ARM=y
 CONFIG_HAVE_PWM=y
@@ -954,6 +954,7 @@ CONFIG_BT_HCIBFUSB=m
 # CONFIG_BT_HCIVHCI is not set
 # CONFIG_BT_MRVL is not set
 CONFIG_BT_ATH3K=m
+CONFIG_BT_WILINK=m
 CONFIG_AF_RXRPC=m
 # CONFIG_AF_RXRPC_DEBUG is not set
 # CONFIG_RXKAD is not set
@@ -965,7 +966,7 @@ CONFIG_WEXT_PROC=y
 CONFIG_WEXT_SPY=y
 CONFIG_WEXT_PRIV=y
 CONFIG_CFG80211=m
-# CONFIG_NL80211_TESTMODE is not set
+CONFIG_NL80211_TESTMODE=y
 # CONFIG_CFG80211_DEVELOPER_WARNINGS is not set
 # CONFIG_CFG80211_REG_DEBUG is not set
 CONFIG_CFG80211_DEFAULT_PS=y
@@ -1341,6 +1342,7 @@ CONFIG_WL1251_SPI=m
 CONFIG_WL1251_SDIO=m
 CONFIG_WL12XX=m
 CONFIG_WL1271=m
+CONFIG_WL1271_HT=y
 CONFIG_WL1271_SPI=m
 CONFIG_WL1271_SDIO=m
 CONFIG_WL12XX_PLATFORM_DATA=y
@@ -1568,8 +1570,6 @@ CONFIG_I2C_MUX=m
 # CONFIG_I2C_MUX_PCA954x is not set
 CONFIG_I2C_HELPER_AUTO=y
 CONFIG_I2C_ALGOBIT=m
-# CONFIG_I2C_ALGOPCF is not set
-# CONFIG_I2C_ALGOPCA is not set
 
 #
 # I2C Hardware Bus support
@@ -1800,6 +1800,7 @@ CONFIG_TPS6507X=m
 CONFIG_TWL4030_CORE=y
 CONFIG_TWL4030_POWER=y
 CONFIG_TWL4030_CODEC=y
+CONFIG_TWL4030_MADC=m
 CONFIG_TWL6030_PWM=m
 # CONFIG_MFD_STMPE is not set
 # CONFIG_MFD_TC35892 is not set
@@ -2072,6 +2073,11 @@ CONFIG_RADIO_ADAPTERS=y
 # CONFIG_RADIO_TEA5764 is not set
 CONFIG_RADIO_SAA7706H=m
 # CONFIG_RADIO_TEF6862 is not set
+
+#
+# Texas Instruments WL128x FM driver (ST based)
+#
+CONFIG_RADIO_WL128X=m
 CONFIG_DVB_MAX_ADAPTERS=8
 CONFIG_DVB_DYNAMIC_MINORS=y
 CONFIG_DVB_CAPTURE_DRIVERS=y
@@ -2131,16 +2137,12 @@ CONFIG_DVB_B2C2_FLEXCOP_USB=m
 #
 CONFIG_DVB_STB0899=m
 CONFIG_DVB_STB6100=m
-# CONFIG_DVB_STV090x is not set
-# CONFIG_DVB_STV6110x is not set
 
 #
 # DVB-S (satellite) frontends
 #
-# CONFIG_DVB_CX24110 is not set
 CONFIG_DVB_CX24123=m
 CONFIG_DVB_MT312=m
-# CONFIG_DVB_ZL10036 is not set
 CONFIG_DVB_ZL10039=m
 CONFIG_DVB_S5H1420=m
 CONFIG_DVB_STV0288=m
@@ -2148,29 +2150,18 @@ CONFIG_DVB_STB6000=m
 CONFIG_DVB_STV0299=m
 CONFIG_DVB_STV6110=m
 CONFIG_DVB_STV0900=m
-# CONFIG_DVB_TDA8083 is not set
 CONFIG_DVB_TDA10086=m
-# CONFIG_DVB_TDA8261 is not set
-# CONFIG_DVB_VES1X93 is not set
 CONFIG_DVB_TUNER_ITD1000=m
 CONFIG_DVB_TUNER_CX24113=m
 CONFIG_DVB_TDA826X=m
-# CONFIG_DVB_TUA6100 is not set
 CONFIG_DVB_CX24116=m
 CONFIG_DVB_SI21XX=m
 CONFIG_DVB_DS3000=m
-# CONFIG_DVB_MB86A16 is not set
 
 #
 # DVB-T (terrestrial) frontends
 #
-# CONFIG_DVB_SP8870 is not set
-# CONFIG_DVB_SP887X is not set
-# CONFIG_DVB_CX22700 is not set
 CONFIG_DVB_CX22702=m
-# CONFIG_DVB_S5H1432 is not set
-# CONFIG_DVB_DRX397XD is not set
-# CONFIG_DVB_L64781 is not set
 CONFIG_DVB_TDA1004X=m
 CONFIG_DVB_NXT6000=m
 CONFIG_DVB_MT352=m
@@ -2181,13 +2172,10 @@ CONFIG_DVB_DIB7000M=m
 CONFIG_DVB_DIB7000P=m
 CONFIG_DVB_TDA10048=m
 CONFIG_DVB_AF9013=m
-# CONFIG_DVB_EC100 is not set
 
 #
 # DVB-C (cable) frontends
 #
-# CONFIG_DVB_VES1820 is not set
-# CONFIG_DVB_TDA10021 is not set
 CONFIG_DVB_TDA10023=m
 CONFIG_DVB_STV0297=m
 
@@ -2195,19 +2183,15 @@ CONFIG_DVB_STV0297=m
 # ATSC (North American/Korean Terrestrial/Cable DTV) frontends
 #
 CONFIG_DVB_NXT200X=m
-# CONFIG_DVB_OR51211 is not set
-# CONFIG_DVB_OR51132 is not set
 CONFIG_DVB_BCM3510=m
 CONFIG_DVB_LGDT330X=m
 CONFIG_DVB_LGDT3305=m
 CONFIG_DVB_S5H1409=m
-# CONFIG_DVB_AU8522 is not set
 CONFIG_DVB_S5H1411=m
 
 #
 # ISDB-T (terrestrial) frontends
 #
-# CONFIG_DVB_S921 is not set
 CONFIG_DVB_DIB8000=m
 
 #
@@ -2221,13 +2205,9 @@ CONFIG_DVB_TUNER_DIB0090=m
 # SEC control devices for DVB-S
 #
 CONFIG_DVB_LNBP21=m
-# CONFIG_DVB_ISL6405 is not set
 CONFIG_DVB_ISL6421=m
-# CONFIG_DVB_ISL6423 is not set
-# CONFIG_DVB_LGS8GL5 is not set
 CONFIG_DVB_LGS8GXX=m
 CONFIG_DVB_ATBM8830=m
-# CONFIG_DVB_TDA665x is not set
 CONFIG_DVB_IX2505V=m
 
 #
@@ -2294,6 +2274,8 @@ CONFIG_FB_OMAP2_NUM_FBS=2
 # OMAP2/3 Display Device Drivers
 #
 CONFIG_PANEL_GENERIC=y
+# CONFIG_PANEL_LGPHILIPS_LB035Q02 is not set
+# CONFIG_PANEL_SAMSUNG_LTE430WQ_F0C is not set
 CONFIG_PANEL_SHARP_LS037V7DW01=y
 # CONFIG_PANEL_SHARP_LQ043T1DG01 is not set
 # CONFIG_PANEL_TAAL is not set
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0001-wl12xx-Read-MAC-address-from-NVS-file-on-HW-startup.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0001-wl12xx-Read-MAC-address-from-NVS-file-on-HW-startup.patch
new file mode 100644 (file)
index 0000000..a4f0873
--- /dev/null
@@ -0,0 +1,41 @@
+From 5d302917bbdb377538f6c848243a6265878abcee Mon Sep 17 00:00:00 2001
+From: Arik Nemtsov <arik@wizery.com>
+Date: Sat, 16 Oct 2010 21:49:52 +0200
+Subject: [PATCH 01/15] wl12xx: Read MAC address from NVS file on HW startup
+
+Try to read the MAC address from the on-disk NVS file.
+A non-zero MAC address is required to add an AP interface.
+
+Signed-off-by: Arik Nemtsov <arik@wizery.com>
+Reviewed-by: Luciano Coelho <coelho@ti.com>
+Signed-off-by: Luciano Coelho <coelho@ti.com>
+---
+ drivers/net/wireless/wl12xx/wl1271_main.c |   12 ++++++++++++
+ 1 files changed, 12 insertions(+), 0 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/wl1271_main.c b/drivers/net/wireless/wl12xx/wl1271_main.c
+index 48a4b99..591de0e 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_main.c
++++ b/drivers/net/wireless/wl12xx/wl1271_main.c
+@@ -2391,6 +2391,18 @@ int wl1271_register_hw(struct wl1271 *wl)
+       if (wl->mac80211_registered)
+               return 0;
++      ret = wl1271_fetch_nvs(wl);
++      if (ret == 0) {
++              u8 *nvs_ptr = (u8 *)wl->nvs->nvs;
++
++              wl->mac_addr[0] = nvs_ptr[11];
++              wl->mac_addr[1] = nvs_ptr[10];
++              wl->mac_addr[2] = nvs_ptr[6];
++              wl->mac_addr[3] = nvs_ptr[5];
++              wl->mac_addr[4] = nvs_ptr[4];
++              wl->mac_addr[5] = nvs_ptr[3];
++      }
++
+       SET_IEEE80211_PERM_ADDR(wl->hw, wl->mac_addr);
+       ret = ieee80211_register_hw(wl->hw);
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0002-wl1271-11n-Support-Add-Definitions.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0002-wl1271-11n-Support-Add-Definitions.patch
new file mode 100644 (file)
index 0000000..1da7be6
--- /dev/null
@@ -0,0 +1,165 @@
+From 99d1c6c23faa446ec0ebdf056d8aa8f4d983d518 Mon Sep 17 00:00:00 2001
+From: Shahar Levi <shahar_levi@ti.com>
+Date: Wed, 13 Oct 2010 16:09:39 +0200
+Subject: [PATCH 02/15] wl1271: 11n Support, Add Definitions
+
+Two acx commands: ht_capabilities & ht_information, 11n sta capabilities
+macro.
+
+Signed-off-by: Shahar Levi <shahar_levi@ti.com>
+Reviewed-by: Luciano Coelho <luciano.coelho@nokia.com>
+Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
+---
+ drivers/net/wireless/wl12xx/wl1271.h      |   11 ++++-
+ drivers/net/wireless/wl12xx/wl1271_acx.h  |   81 +++++++++++++++++++++++++++++
+ drivers/net/wireless/wl12xx/wl1271_main.c |   15 +++++
+ 3 files changed, 106 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/wl1271.h b/drivers/net/wireless/wl12xx/wl1271.h
+index 8a4cd76..45a2583 100644
+--- a/drivers/net/wireless/wl12xx/wl1271.h
++++ b/drivers/net/wireless/wl12xx/wl1271.h
+@@ -432,7 +432,12 @@ struct wl1271 {
+       /* Our association ID */
+       u16 aid;
+-      /* currently configured 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 sta_rate_set;
+       u32 basic_rate_set;
+       u32 basic_rate;
+@@ -509,4 +514,8 @@ int wl1271_plt_stop(struct wl1271 *wl);
+ #define WL1271_PRE_POWER_ON_SLEEP 20 /* in miliseconds */
+ #define WL1271_POWER_ON_SLEEP 200 /* in miliseconds */
++/* Macros to handle wl1271.sta_rate_set */
++#define HW_BG_RATES_MASK      0xffff
++#define HW_HT_RATES_OFFSET    16
++
+ #endif
+diff --git a/drivers/net/wireless/wl12xx/wl1271_acx.h b/drivers/net/wireless/wl12xx/wl1271_acx.h
+index ebb341d..f090a04 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_acx.h
++++ b/drivers/net/wireless/wl12xx/wl1271_acx.h
+@@ -964,6 +964,87 @@ struct wl1271_acx_rssi_snr_avg_weights {
+       u8 snr_data;
+ };
++/*
++ * 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;
++
++      /*
++       * bit 0 - Allow HT Operation
++       * bit 1 - Allow Greenfield format in TX
++       * bit 2 - Allow Short GI in TX
++       * bit 3 - Allow L-SIG TXOP Protection in TX
++       * bit 4 - Allow HT Control fields in TX.
++       *         Note, driver will still leave space for HT control in packets
++       *         regardless of the value of this field. FW will be responsible
++       *         to drop the HT field from any frame when this Bit set to 0.
++       * bit 5 - Allow RD initiation in TXOP. FW is allowed to initate RD.
++       *         Exact policy setting for this feature is TBD.
++       *         Note, this bit can only be set to 1 if bit 3 is set to 1.
++       */
++      __le32 ht_capabilites;
++
++      /*
++       * Indicates to which peer these capabilities apply.
++       * For infrastructure use ff:ff:ff:ff:ff:ff that indicates relevance
++       * for all peers.
++       * Only valid for IBSS/DLS operation.
++       */
++      u8 mac_address[ETH_ALEN];
++
++      /*
++       * 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;
++} __packed;
++
++/* HT Capabilites Fw Bit Mask Mapping */
++#define WL1271_ACX_FW_CAP_HT_OPERATION                 BIT(0)
++#define WL1271_ACX_FW_CAP_GREENFIELD_FRAME_FORMAT      BIT(1)
++#define WL1271_ACX_FW_CAP_SHORT_GI_FOR_20MHZ_PACKETS   BIT(2)
++#define WL1271_ACX_FW_CAP_LSIG_TXOP_PROTECTION         BIT(3)
++#define WL1271_ACX_FW_CAP_HT_CONTROL_FIELDS            BIT(4)
++#define WL1271_ACX_FW_CAP_RD_INITIATION                BIT(5)
++
++
++/*
++ * ACX_HT_BSS_OPERATION
++ * Configure HT capabilities - AP rules for behavior in the BSS.
++ */
++struct wl1271_acx_ht_information {
++      struct acx_header header;
++
++      /* 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[3];
++} __packed;
++
+ struct wl1271_acx_fw_tsf_information {
+       struct acx_header header;
+diff --git a/drivers/net/wireless/wl12xx/wl1271_main.c b/drivers/net/wireless/wl12xx/wl1271_main.c
+index 591de0e..785b73c 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_main.c
++++ b/drivers/net/wireless/wl12xx/wl1271_main.c
+@@ -2134,6 +2134,21 @@ static const u8 wl1271_rate_to_idx_2ghz[] = {
+       0                              /* CONF_HW_RXTX_RATE_1    */
+ };
++/* 11n STA capabilities */
++#define HW_RX_HIGHEST_RATE    72
++
++#define WL1271_HT_CAP { \
++      .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20, \
++      .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(HW_RX_HIGHEST_RATE), \
++              .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \
++              }, \
++}
++
+ /* can't be const, mac80211 writes to this */
+ static struct ieee80211_supported_band wl1271_band_2ghz = {
+       .channels = wl1271_channels,
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0003-wl1271-11n-Support-ACX-Commands.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0003-wl1271-11n-Support-ACX-Commands.patch
new file mode 100644 (file)
index 0000000..8d2412b
--- /dev/null
@@ -0,0 +1,128 @@
+From 160169e1e717020b8456278950c30d2b1f15c314 Mon Sep 17 00:00:00 2001
+From: Shahar Levi <shahar_levi@ti.com>
+Date: Wed, 13 Oct 2010 16:09:40 +0200
+Subject: [PATCH 03/15] wl1271: 11n Support, ACX Commands
+
+Added ACX command to the FW for 11n support.
+
+Signed-off-by: Shahar Levi <shahar_levi@ti.com>
+Reviewed-by: Luciano Coelho <luciano.coelho@nokia.com>
+Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
+---
+ drivers/net/wireless/wl12xx/wl1271_acx.c |   83 ++++++++++++++++++++++++++++++
+ drivers/net/wireless/wl12xx/wl1271_acx.h |    5 ++
+ 2 files changed, 88 insertions(+), 0 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/wl1271_acx.c b/drivers/net/wireless/wl12xx/wl1271_acx.c
+index 6189934..bd7f95f 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_acx.c
++++ b/drivers/net/wireless/wl12xx/wl1271_acx.c
+@@ -1226,6 +1226,89 @@ out:
+       return ret;
+ }
++int wl1271_acx_set_ht_capabilities(struct wl1271 *wl,
++                                  struct ieee80211_sta_ht_cap *ht_cap,
++                                  bool allow_ht_operation)
++{
++      struct wl1271_acx_ht_capabilities *acx;
++      u8 mac_address[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
++      int ret = 0;
++
++      wl1271_debug(DEBUG_ACX, "acx ht capabilities setting");
++
++      acx = kzalloc(sizeof(*acx), GFP_KERNEL);
++      if (!acx) {
++              ret = -ENOMEM;
++              goto out;
++      }
++
++      /* Allow HT Operation ? */
++      if (allow_ht_operation) {
++              acx->ht_capabilites =
++                      WL1271_ACX_FW_CAP_HT_OPERATION;
++              if (ht_cap->cap & IEEE80211_HT_CAP_GRN_FLD)
++                      acx->ht_capabilites |=
++                              WL1271_ACX_FW_CAP_GREENFIELD_FRAME_FORMAT;
++              if (ht_cap->cap & IEEE80211_HT_CAP_SGI_20)
++                      acx->ht_capabilites |=
++                              WL1271_ACX_FW_CAP_SHORT_GI_FOR_20MHZ_PACKETS;
++              if (ht_cap->cap & IEEE80211_HT_CAP_LSIG_TXOP_PROT)
++                      acx->ht_capabilites |=
++                              WL1271_ACX_FW_CAP_LSIG_TXOP_PROTECTION;
++
++              /* get data from A-MPDU parameters field */
++              acx->ampdu_max_length = ht_cap->ampdu_factor;
++              acx->ampdu_min_spacing = ht_cap->ampdu_density;
++
++              memcpy(acx->mac_address, mac_address, ETH_ALEN);
++      } else { /* HT operations are not allowed */
++              acx->ht_capabilites = 0;
++      }
++
++      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;
++}
++
++int wl1271_acx_set_ht_information(struct wl1271 *wl,
++                                 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->ht_protection =
++              (u8)(ht_operation_mode & IEEE80211_HT_OP_MODE_PROTECTION);
++      acx->rifs_mode = 0;
++      acx->gf_protection = 0;
++      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;
++}
++
+ int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime)
+ {
+       struct wl1271_acx_fw_tsf_information *tsf_info;
+diff --git a/drivers/net/wireless/wl12xx/wl1271_acx.h b/drivers/net/wireless/wl12xx/wl1271_acx.h
+index f090a04..7589167 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_acx.h
++++ b/drivers/net/wireless/wl12xx/wl1271_acx.h
+@@ -1174,6 +1174,11 @@ int wl1271_acx_keep_alive_config(struct wl1271 *wl, u8 index, u8 tpl_valid);
+ int wl1271_acx_rssi_snr_trigger(struct wl1271 *wl, bool enable,
+                               s16 thold, u8 hyst);
+ int wl1271_acx_rssi_snr_avg_weights(struct wl1271 *wl);
++int wl1271_acx_set_ht_capabilities(struct wl1271 *wl,
++                                  struct ieee80211_sta_ht_cap *ht_cap,
++                                  bool allow_ht_operation);
++int wl1271_acx_set_ht_information(struct wl1271 *wl,
++                                 u16 ht_operation_mode);
+ int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime);
+ #endif /* __WL1271_ACX_H__ */
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0004-wl1271-11n-Support-functionality-and-configuration-a.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0004-wl1271-11n-Support-functionality-and-configuration-a.patch
new file mode 100644 (file)
index 0000000..17ed6e6
--- /dev/null
@@ -0,0 +1,249 @@
+From 9685ab91494ae35d2cb7e0033c5ee1bf3cdf0c64 Mon Sep 17 00:00:00 2001
+From: Shahar Levi <shahar_levi@ti.com>
+Date: Wed, 13 Oct 2010 16:09:41 +0200
+Subject: [PATCH 04/15] wl1271: 11n Support, functionality and configuration ability
+
+Add 11n ability in scan, connection and using MCS rates.
+The configuration is temporary due to the code incomplete and
+still in testing process. That plans to be remove in the future.
+
+Signed-off-by: Shahar Levi <shahar_levi@ti.com>
+Reviewed-by: Luciano Coelho <luciano.coelho@nokia.com>
+Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
+---
+ drivers/net/wireless/wl12xx/Kconfig       |   10 +++
+ drivers/net/wireless/wl12xx/wl1271_main.c |   96 +++++++++++++++++++++++------
+ drivers/net/wireless/wl12xx/wl1271_rx.c   |    6 ++
+ drivers/net/wireless/wl12xx/wl1271_tx.c   |   11 +++
+ 4 files changed, 105 insertions(+), 18 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/Kconfig b/drivers/net/wireless/wl12xx/Kconfig
+index b447559..1b3b7bd 100644
+--- a/drivers/net/wireless/wl12xx/Kconfig
++++ b/drivers/net/wireless/wl12xx/Kconfig
+@@ -18,6 +18,16 @@ config WL1271
+         If you choose to build a module, it'll be called wl1271. Say N if
+         unsure.
++config WL1271_HT
++        bool "TI wl1271 802.11 HT support (EXPERIMENTAL)"
++        depends on WL1271 && EXPERIMENTAL
++        default n
++        ---help---
++          This will enable 802.11 HT support for TI wl1271 chipset.
++
++        That configuration is temporary due to the code incomplete and
++        still in testing process.
++
+ config WL1271_SPI
+       tristate "TI wl1271 SPI support"
+       depends on WL1271 && SPI_MASTER
+diff --git a/drivers/net/wireless/wl12xx/wl1271_main.c b/drivers/net/wireless/wl12xx/wl1271_main.c
+index 785b73c..49ec0ef 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_main.c
++++ b/drivers/net/wireless/wl12xx/wl1271_main.c
+@@ -851,12 +851,32 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
+       struct ieee80211_sta *sta = txinfo->control.sta;
+       unsigned long flags;
+-      /* peek into the rates configured in the STA entry */
++      /*
++       * peek into the rates configured in the STA entry.
++       * The rates set after connection stage, The first block only BG sets:
++       * the compare is for bit 0-16 of sta_rate_set. The second block add
++       * HT rates in case of HT supported.
++       */
+       spin_lock_irqsave(&wl->wl_lock, flags);
+-      if (sta && sta->supp_rates[conf->channel->band] != wl->sta_rate_set) {
++      if (sta &&
++          (sta->supp_rates[conf->channel->band] !=
++          (wl->sta_rate_set & HW_BG_RATES_MASK))) {
+               wl->sta_rate_set = sta->supp_rates[conf->channel->band];
+               set_bit(WL1271_FLAG_STA_RATES_CHANGED, &wl->flags);
+       }
++
++#ifdef CONFIG_WL1271_HT
++      if (sta &&
++          sta->ht_cap.ht_supported &&
++          ((wl->sta_rate_set >> HW_HT_RATES_OFFSET) !=
++            sta->ht_cap.mcs.rx_mask[0])) {
++              /* Clean MCS bits before setting them */
++              wl->sta_rate_set &= HW_BG_RATES_MASK;
++              wl->sta_rate_set |=
++                      (sta->ht_cap.mcs.rx_mask[0] << HW_HT_RATES_OFFSET);
++              set_bit(WL1271_FLAG_STA_RATES_CHANGED, &wl->flags);
++      }
++#endif
+       spin_unlock_irqrestore(&wl->wl_lock, flags);
+       /* queue the packet */
+@@ -1709,6 +1729,7 @@ static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw,
+ {
+       enum wl1271_cmd_ps_mode mode;
+       struct wl1271 *wl = hw->priv;
++      struct ieee80211_sta *sta = ieee80211_find_sta(vif, bss_conf->bssid);
+       bool do_join = false;
+       bool set_assoc = false;
+       int ret;
+@@ -1927,6 +1948,37 @@ static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw,
+               }
+       }
++      /*
++       * Takes care of: New association with HT enable,
++       *                HT information change in beacon.
++       */
++      if (sta &&
++          (changed & BSS_CHANGED_HT) &&
++          (bss_conf->channel_type != NL80211_CHAN_NO_HT)) {
++              ret = wl1271_acx_set_ht_capabilities(wl, &sta->ht_cap, true);
++              if (ret < 0) {
++                      wl1271_warning("Set ht cap true failed %d", ret);
++                      goto out_sleep;
++              }
++                      ret = wl1271_acx_set_ht_information(wl,
++                              bss_conf->ht_operation_mode);
++              if (ret < 0) {
++                      wl1271_warning("Set ht information failed %d", ret);
++                      goto out_sleep;
++              }
++      }
++      /*
++       * Takes care of: New association without HT,
++       *                Disassociation.
++       */
++      else if (sta && (changed & BSS_CHANGED_ASSOC)) {
++              ret = wl1271_acx_set_ht_capabilities(wl, &sta->ht_cap, false);
++              if (ret < 0) {
++                      wl1271_warning("Set ht cap false failed %d", ret);
++                      goto out_sleep;
++              }
++      }
++
+       if (changed & BSS_CHANGED_ARP_FILTER) {
+               __be32 addr = bss_conf->arp_addr_list[0];
+               WARN_ON(wl->bss_type != BSS_TYPE_STA_BSS);
+@@ -2107,14 +2159,14 @@ static struct ieee80211_channel wl1271_channels[] = {
+ /* mapping to indexes for wl1271_rates */
+ static const u8 wl1271_rate_to_idx_2ghz[] = {
+       /* MCS rates are used only with 11n */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS7 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS6 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS5 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS4 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS3 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS2 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS1 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS0 */
++      7,                            /* CONF_HW_RXTX_RATE_MCS7 */
++      6,                            /* CONF_HW_RXTX_RATE_MCS6 */
++      5,                            /* CONF_HW_RXTX_RATE_MCS5 */
++      4,                            /* CONF_HW_RXTX_RATE_MCS4 */
++      3,                            /* CONF_HW_RXTX_RATE_MCS3 */
++      2,                            /* CONF_HW_RXTX_RATE_MCS2 */
++      1,                            /* CONF_HW_RXTX_RATE_MCS1 */
++      0,                            /* CONF_HW_RXTX_RATE_MCS0 */
+       11,                            /* CONF_HW_RXTX_RATE_54   */
+       10,                            /* CONF_HW_RXTX_RATE_48   */
+@@ -2137,6 +2189,7 @@ static const u8 wl1271_rate_to_idx_2ghz[] = {
+ /* 11n STA capabilities */
+ #define HW_RX_HIGHEST_RATE    72
++#ifdef CONFIG_WL1271_HT
+ #define WL1271_HT_CAP { \
+       .cap = IEEE80211_HT_CAP_GRN_FLD | IEEE80211_HT_CAP_SGI_20, \
+       .ht_supported = true, \
+@@ -2148,6 +2201,11 @@ static const u8 wl1271_rate_to_idx_2ghz[] = {
+               .tx_params = IEEE80211_HT_MCS_TX_DEFINED, \
+               }, \
+ }
++#else
++#define WL1271_HT_CAP { \
++      .ht_supported = false, \
++}
++#endif
+ /* can't be const, mac80211 writes to this */
+ static struct ieee80211_supported_band wl1271_band_2ghz = {
+@@ -2155,6 +2213,7 @@ static struct ieee80211_supported_band wl1271_band_2ghz = {
+       .n_channels = ARRAY_SIZE(wl1271_channels),
+       .bitrates = wl1271_rates,
+       .n_bitrates = ARRAY_SIZE(wl1271_rates),
++      .ht_cap = WL1271_HT_CAP,
+ };
+ /* 5 GHz data rates for WL1273 */
+@@ -2237,14 +2296,14 @@ static struct ieee80211_channel wl1271_channels_5ghz[] = {
+ /* mapping to indexes for wl1271_rates_5ghz */
+ static const u8 wl1271_rate_to_idx_5ghz[] = {
+       /* MCS rates are used only with 11n */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS7 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS6 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS5 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS4 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS3 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS2 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS1 */
+-      CONF_HW_RXTX_RATE_UNSUPPORTED, /* CONF_HW_RXTX_RATE_MCS0 */
++      7,                            /* CONF_HW_RXTX_RATE_MCS7 */
++      6,                            /* CONF_HW_RXTX_RATE_MCS6 */
++      5,                            /* CONF_HW_RXTX_RATE_MCS5 */
++      4,                            /* CONF_HW_RXTX_RATE_MCS4 */
++      3,                            /* CONF_HW_RXTX_RATE_MCS3 */
++      2,                            /* CONF_HW_RXTX_RATE_MCS2 */
++      1,                            /* CONF_HW_RXTX_RATE_MCS1 */
++      0,                            /* CONF_HW_RXTX_RATE_MCS0 */
+       7,                             /* CONF_HW_RXTX_RATE_54   */
+       6,                             /* CONF_HW_RXTX_RATE_48   */
+@@ -2269,6 +2328,7 @@ static struct ieee80211_supported_band wl1271_band_5ghz = {
+       .n_channels = ARRAY_SIZE(wl1271_channels_5ghz),
+       .bitrates = wl1271_rates_5ghz,
+       .n_bitrates = ARRAY_SIZE(wl1271_rates_5ghz),
++      .ht_cap = WL1271_HT_CAP,
+ };
+ static const u8 *wl1271_band_rate_to_idx[] = {
+diff --git a/drivers/net/wireless/wl12xx/wl1271_rx.c b/drivers/net/wireless/wl12xx/wl1271_rx.c
+index bea133b..ac13f7d 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_rx.c
++++ b/drivers/net/wireless/wl12xx/wl1271_rx.c
+@@ -53,6 +53,12 @@ static void wl1271_rx_status(struct wl1271 *wl,
+       status->band = wl->band;
+       status->rate_idx = wl1271_rate_to_idx(wl, desc->rate);
++#ifdef CONFIG_WL1271_HT
++      /* 11n support */
++      if (desc->rate <= CONF_HW_RXTX_RATE_MCS0)
++              status->flag |= RX_FLAG_HT;
++#endif
++
+       status->signal = desc->rssi;
+       /*
+diff --git a/drivers/net/wireless/wl12xx/wl1271_tx.c b/drivers/net/wireless/wl12xx/wl1271_tx.c
+index e3dc13c..6a87633 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_tx.c
++++ b/drivers/net/wireless/wl12xx/wl1271_tx.c
+@@ -201,6 +201,17 @@ u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set)
+               rate_set >>= 1;
+       }
++#ifdef CONFIG_WL1271_HT
++      /* MCS rates indication are on bits 16 - 23 */
++      rate_set >>= HW_HT_RATES_OFFSET - band->n_bitrates;
++
++      for (bit = 0; bit < 8; bit++) {
++              if (rate_set & 0x1)
++                      enabled_rates |= (CONF_HW_BIT_RATE_MCS_0 << bit);
++              rate_set >>= 1;
++      }
++#endif
++
+       return enabled_rates;
+ }
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0005-wl1271-set-wl-vif-only-if-add_interface-succeeded.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0005-wl1271-set-wl-vif-only-if-add_interface-succeeded.patch
new file mode 100644 (file)
index 0000000..3707b7c
--- /dev/null
@@ -0,0 +1,86 @@
+From dd812452fb91de492a8fd8d838d16cfc67cbfcf4 Mon Sep 17 00:00:00 2001
+From: Eliad Peller <eliad@wizery.com>
+Date: Thu, 28 Oct 2010 21:46:43 +0200
+Subject: [PATCH 05/15] wl1271: set wl->vif only if add_interface succeeded.
+
+set wl->vif to the newly created interface only after the firmware booted
+successfully. on the way - make the function flow more clear.
+
+Signed-off-by: Eliad Peller <eliad@wizery.com>
+Reviewed-by: Luciano Coelho <luciano.coelho@nokia.com>
+Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
+---
+ drivers/net/wireless/wl12xx/wl1271_main.c |   33 +++++++++++++++++-----------
+ 1 files changed, 20 insertions(+), 13 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/wl1271_main.c b/drivers/net/wireless/wl12xx/wl1271_main.c
+index 49ec0ef..78273c9 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_main.c
++++ b/drivers/net/wireless/wl12xx/wl1271_main.c
+@@ -939,18 +939,19 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
+       struct wiphy *wiphy = hw->wiphy;
+       int retries = WL1271_BOOT_RETRIES;
+       int ret = 0;
++      bool booted = false;
+       wl1271_debug(DEBUG_MAC80211, "mac80211 add interface type %d mac %pM",
+                    vif->type, vif->addr);
+       mutex_lock(&wl->mutex);
+       if (wl->vif) {
++              wl1271_debug(DEBUG_MAC80211,
++                           "multiple vifs are not supported yet");
+               ret = -EBUSY;
+               goto out;
+       }
+-      wl->vif = vif;
+-
+       switch (vif->type) {
+       case NL80211_IFTYPE_STATION:
+               wl->bss_type = BSS_TYPE_STA_BSS;
+@@ -988,15 +989,8 @@ static int wl1271_op_add_interface(struct ieee80211_hw *hw,
+               if (ret < 0)
+                       goto irq_disable;
+-              wl->state = WL1271_STATE_ON;
+-              wl1271_info("firmware booted (%s)", wl->chip.fw_ver);
+-
+-              /* update hw/fw version info in wiphy struct */
+-              wiphy->hw_version = wl->chip.id;
+-              strncpy(wiphy->fw_version, wl->chip.fw_ver,
+-                      sizeof(wiphy->fw_version));
+-
+-              goto out;
++              booted = true;
++              break;
+ irq_disable:
+               wl1271_disable_interrupts(wl);
+@@ -1014,8 +1008,21 @@ power_off:
+               wl1271_power_off(wl);
+       }
+-      wl1271_error("firmware boot failed despite %d retries",
+-                   WL1271_BOOT_RETRIES);
++      if (!booted) {
++              wl1271_error("firmware boot failed despite %d retries",
++                           WL1271_BOOT_RETRIES);
++              goto out;
++      }
++
++      wl->vif = vif;
++      wl->state = WL1271_STATE_ON;
++      wl1271_info("firmware booted (%s)", wl->chip.fw_ver);
++
++      /* update hw/fw version info in wiphy struct */
++      wiphy->hw_version = wl->chip.id;
++      strncpy(wiphy->fw_version, wl->chip.fw_ver,
++              sizeof(wiphy->fw_version));
++
+ out:
+       mutex_unlock(&wl->mutex);
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0006-wl12xx-Unset-bssid-filter-ssid-and-bssid-from-firmwa.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0006-wl12xx-Unset-bssid-filter-ssid-and-bssid-from-firmwa.patch
new file mode 100644 (file)
index 0000000..5b83bc9
--- /dev/null
@@ -0,0 +1,44 @@
+From 54b32b60bed66ac4ecf00279466496d9d4e80afa Mon Sep 17 00:00:00 2001
+From: Juuso Oikarinen <juuso.oikarinen@nokia.com>
+Date: Mon, 22 Nov 2010 12:59:08 +0200
+Subject: [PATCH 06/15] wl12xx: Unset bssid filter, ssid and bssid from firmware on disassoc
+
+On the disassociation event from the mac80211, the wl12xx driver does not
+clear the chipset configuration related to the AP - i.e. it does not perform
+a DISCONNECT and then a JOIN with zero SSID and dummy BSSID. Also, it does not
+unset the BSSID filter.
+
+Often this is not a problem, as the above is performed upon entering idle
+state. But if a scenario arises where a new association is attempted without
+cycling through idle state, the new association will fail.
+
+Fix this by resetting the firmware state on disassociation.
+
+Signed-off-by: Juuso Oikarinen <juuso.oikarinen@nokia.com>
+Reviewed-by: Luciano Coelho <luciano.coelho@nokia.com>
+Signed-off-by: Luciano Coelho <luciano.coelho@nokia.com>
+---
+ drivers/net/wireless/wl12xx/wl1271_main.c |    5 ++++-
+ 1 files changed, 4 insertions(+), 1 deletions(-)
+
+diff --git a/drivers/net/wireless/wl12xx/wl1271_main.c b/drivers/net/wireless/wl12xx/wl1271_main.c
+index 78273c9..db97648 100644
+--- a/drivers/net/wireless/wl12xx/wl1271_main.c
++++ b/drivers/net/wireless/wl12xx/wl1271_main.c
+@@ -1919,9 +1919,12 @@ static void wl1271_op_bss_info_changed(struct ieee80211_hw *hw,
+                       /* Disable the keep-alive feature */
+                       ret = wl1271_acx_keep_alive_mode(wl, false);
+-
+                       if (ret < 0)
+                               goto out_sleep;
++
++                      /* restore the bssid filter and go to dummy bssid */
++                      wl1271_unjoin(wl);
++                      wl1271_dummy_join(wl);
+               }
+       }
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0007-drivers-media-radio-wl128x-FM-Driver-common-header-f.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0007-drivers-media-radio-wl128x-FM-Driver-common-header-f.patch
new file mode 100644 (file)
index 0000000..d104a72
--- /dev/null
@@ -0,0 +1,268 @@
+From f568ec9bb6ccd1e17278dcab3fbc810cf2e071ac Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:21 +0000
+Subject: [PATCH 07/15] drivers:media:radio: wl128x: FM Driver common header file
+
+These are common headers used in FM submodules (FM V4L2,
+FM common, FM Rx,and FM TX).
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/fmdrv.h |  244 ++++++++++++++++++++++++++++++++++++
+ 1 files changed, 244 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/fmdrv.h
+
+diff --git a/drivers/media/radio/wl128x/fmdrv.h b/drivers/media/radio/wl128x/fmdrv.h
+new file mode 100644
+index 0000000..392b62d
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv.h
+@@ -0,0 +1,244 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *
++ *  Common header for all FM driver sub-modules.
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#ifndef _FM_DRV_H
++#define _FM_DRV_H
++
++#include <linux/skbuff.h>
++#include <linux/interrupt.h>
++#include <sound/core.h>
++#include <sound/initval.h>
++#include <linux/timer.h>
++#include <linux/version.h>
++#include <media/v4l2-ioctl.h>
++#include <media/v4l2-common.h>
++#include <media/v4l2-ctrls.h>
++
++#define FM_DRV_VERSION            "0.09"
++/* Should match with FM_DRV_VERSION */
++#define FM_DRV_RADIO_VERSION      KERNEL_VERSION(0, 0, 1)
++#define FM_DRV_NAME               "ti_fmdrv"
++#define FM_DRV_CARD_SHORT_NAME    "TI FM Radio"
++#define FM_DRV_CARD_LONG_NAME     "Texas Instruments FM Radio"
++
++/* Flag info */
++#define FM_INTTASK_RUNNING            0
++#define FM_INTTASK_SCHEDULE_PENDING   1
++#define FM_FW_DW_INPROGRESS     2
++#define FM_CORE_READY                 3
++#define FM_CORE_TRANSPORT_READY       4
++#define FM_AF_SWITCH_INPROGRESS             5
++#define FM_CORE_TX_XMITING          6
++
++#define FM_TUNE_COMPLETE            0x1
++#define FM_BAND_LIMIT               0x2
++
++#define FM_DRV_TX_TIMEOUT      (5*HZ) /* 5 seconds */
++#define FM_DRV_RX_SEEK_TIMEOUT (20*HZ)        /* 20 seconds */
++
++#define NO_OF_ENTRIES_IN_ARRAY(array) (sizeof(array) / sizeof(array[0]))
++
++#define fmerr(format, ...) \
++      printk(KERN_ERR "fmdrv: " format, ## __VA_ARGS__)
++#define fmwarn(format, ...) \
++      printk(KERN_WARNING "fmdrv: " format, ##__VA_ARGS__)
++#ifdef DEBUG
++#define fmdbg(format, ...) \
++      printk(KERN_DEBUG "fmdrv: " format, ## __VA_ARGS__)
++#else /* DEBUG */
++#define fmdbg(format, ...)
++#endif
++enum {
++      FM_MODE_OFF,
++      FM_MODE_TX,
++      FM_MODE_RX,
++      FM_MODE_ENTRY_MAX
++};
++
++#define FM_RX_RDS_INFO_FIELD_MAX      8       /* 4 Group * 2 Bytes */
++
++/* RX RDS data format */
++struct fm_rdsdata_format {
++      union {
++              struct {
++                      u8 buff[FM_RX_RDS_INFO_FIELD_MAX];
++              } groupdatabuff;
++              struct {
++                      u16 pidata;
++                      u8 blk_b[2];
++                      u8 blk_c[2];
++                      u8 blk_d[2];
++              } groupgeneral;
++              struct {
++                      u16 pidata;
++                      u8 blk_b[2];
++                      u8 af[2];
++                      u8 ps[2];
++              } group0A;
++              struct {
++                      u16 pi[2];
++                      u8 blk_b[2];
++                      u8 ps[2];
++              } group0B;
++      } data;
++};
++
++/* FM region (Europe/US, Japan) info */
++struct region_info {
++      u32 chanl_space;
++      u32 bot_freq;
++      u32 top_freq;
++      u8 fm_band;
++};
++struct fmdev;
++typedef void (*int_handler_prototype) (struct fmdev *);
++
++/* FM Interrupt processing related info */
++struct fm_irq {
++      u8 stage;
++      u16 flag;       /* FM interrupt flag */
++      u16 mask;       /* FM interrupt mask */
++      /* Interrupt process timeout handler */
++      struct timer_list timer;
++      u8 retry;
++      int_handler_prototype *handlers;
++};
++
++/* RDS info */
++struct fm_rds {
++      u8 flag;        /* RX RDS on/off status */
++      u8 last_blk_idx;        /* Last received RDS block */
++
++      /* RDS buffer */
++      wait_queue_head_t read_queue;
++      u32 buf_size;   /* Size is always multiple of 3 */
++      u32 wr_idx;
++      u32 rd_idx;
++      u8 *buff;
++};
++
++#define FM_RDS_MAX_AF_LIST            25
++
++/*
++ * Current RX channel Alternate Frequency cache.
++ * This info is used to switch to other freq (AF)
++ * when current channel signal strengh is below RSSI threshold.
++ */
++struct tuned_station_info {
++      u16 picode;
++      u32 af_cache[FM_RDS_MAX_AF_LIST];
++      u8 afcache_size;
++      u8 af_list_max;
++};
++
++/* FM RX mode info */
++struct fm_rx {
++      struct region_info region;      /* Current selected band */
++      u32 freq;       /* Current RX frquency */
++      u8 mute_mode;   /* Current mute mode */
++      u8 deemphasis_mode; /* Current deemphasis mode */
++      /* RF dependent soft mute mode */
++      u8 rf_depend_mute;
++      u16 volume;     /* Current volume level */
++      u16 rssi_threshold;     /* Current RSSI threshold level */
++      /* Holds the index of the current AF jump */
++      u8 afjump_idx;
++      /* Will hold the frequency before the jump */
++      u32 freq_before_jump;
++      u8 rds_mode;    /* RDS operation mode (RDS/RDBS) */
++      u8 af_mode;     /* Alternate frequency on/off */
++      struct tuned_station_info stat_info;
++      struct fm_rds rds;
++};
++
++#define FMTX_RDS_TXT_STR_SIZE 25
++/*
++ * FM TX RDS data
++ *
++ * @ text_type: is the text following PS or RT
++ * @ text: radio text string which could either be PS or RT
++ * @ af_freq: alternate frequency for Tx
++ * TODO: to be declared in application
++ */
++struct tx_rds {
++      u8 text_type;
++      u8 text[FMTX_RDS_TXT_STR_SIZE];
++      u8 flag;
++      u32 af_freq;
++};
++/*
++ * FM TX global data
++ *
++ * @ pwr_lvl: Power Level of the Transmission from mixer control
++ * @ xmit_state: Transmission state = Updated locally upon Start/Stop
++ * @ audio_io: i2S/Analog
++ * @ tx_frq: Transmission frequency
++ */
++struct fmtx_data {
++      u8 pwr_lvl;
++      u8 xmit_state;
++      u8 audio_io;
++      u8 region;
++      u16 aud_mode;
++      u32 preemph;
++      u32 tx_frq;
++      struct tx_rds rds;
++};
++
++/* FM driver operation structure */
++struct fmdev {
++      struct video_device *radio_dev; /* V4L2 video device pointer */
++      struct snd_card *card;  /* Card which holds FM mixer controls */
++      u16 asci_id;
++      spinlock_t rds_buff_lock; /* To protect access to RDS buffer */
++      spinlock_t resp_skb_lock; /* To protect access to received SKB */
++
++      long flag;              /*  FM driver state machine info */
++      u8 streg_cbdata; /* status of ST registration */
++
++      struct sk_buff_head rx_q;       /* RX queue */
++      struct tasklet_struct rx_task;  /* RX Tasklet */
++
++      struct sk_buff_head tx_q;       /* TX queue */
++      struct tasklet_struct tx_task;  /* TX Tasklet */
++      unsigned long last_tx_jiffies;  /* Timestamp of last pkt sent */
++      atomic_t tx_cnt;        /* Number of packets can send at a time */
++
++      struct sk_buff *resp_skb;       /* Response from the chip */
++      /* Main task completion handler */
++      struct completion maintask_comp;
++      /* Opcode of last command sent to the chip */
++      u8 pre_op;
++      /* Handler used for wakeup when response packet is received */
++      struct completion *resp_comp;
++      struct fm_irq irq_info;
++      u8 curr_fmmode; /* Current FM chip mode (TX, RX, OFF) */
++      struct fm_rx rx;        /* FM receiver info */
++      struct fmtx_data tx_data;
++
++      /* V4L2 ctrl framwork handler*/
++      struct v4l2_ctrl_handler ctrl_handler;
++
++      /* For core assisted locking */
++      struct mutex mutex;
++};
++#endif
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0008-drivers-media-radio-wl128x-FM-Driver-V4L2-sources.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0008-drivers-media-radio-wl128x-FM-Driver-V4L2-sources.patch
new file mode 100644 (file)
index 0000000..c3eae97
--- /dev/null
@@ -0,0 +1,645 @@
+From d532e33a286ec2275b441c05675de52cd5b069d2 Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:22 +0000
+Subject: [PATCH 08/15] drivers:media:radio: wl128x: FM Driver V4L2 sources
+
+This module interfaces V4L2 subsystem and FM common module.
+It registers itself with V4L2 as Radio module.
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/fmdrv_v4l2.c |  580 +++++++++++++++++++++++++++++++
+ drivers/media/radio/wl128x/fmdrv_v4l2.h |   33 ++
+ 2 files changed, 613 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_v4l2.c
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_v4l2.h
+
+diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.c b/drivers/media/radio/wl128x/fmdrv_v4l2.c
+new file mode 100644
+index 0000000..d50e5ac
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_v4l2.c
+@@ -0,0 +1,580 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  This file provides interfaces to V4L2 subsystem.
++ *
++ *  This module registers with V4L2 subsystem as Radio
++ *  data system interface (/dev/radio). During the registration,
++ *  it will expose two set of function pointers.
++ *
++ *    1) File operation related API (open, close, read, write, poll...etc).
++ *    2) Set of V4L2 IOCTL complaint API.
++ *
++ *  Copyright (C) 2011 Texas Instruments
++ *  Author: Raja Mani <raja_mani@ti.com>
++ *  Author: Manjunatha Halli <manjunatha_halli@ti.com>
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#include "fmdrv.h"
++#include "fmdrv_v4l2.h"
++#include "fmdrv_common.h"
++#include "fmdrv_rx.h"
++#include "fmdrv_tx.h"
++
++static struct video_device *gradio_dev;
++static u8 radio_disconnected;
++
++/* -- V4L2 RADIO (/dev/radioX) device file operation interfaces --- */
++
++/* Read RX RDS data */
++static ssize_t fm_v4l2_fops_read(struct file *file, char __user * buf,
++                                      size_t count, loff_t *ppos)
++{
++      u8 rds_mode;
++      int ret;
++      struct fmdev *fmdev;
++
++      fmdev = video_drvdata(file);
++
++      if (!radio_disconnected) {
++              fmerr("FM device is already disconnected\n");
++              return -EIO;
++      }
++
++      /* Turn on RDS mode , if it is disabled */
++      ret = fm_rx_get_rds_mode(fmdev, &rds_mode);
++      if (ret < 0) {
++              fmerr("Unable to read current rds mode\n");
++              return ret;
++      }
++
++      if (rds_mode == FM_RDS_DISABLE) {
++              ret = fmc_set_rds_mode(fmdev, FM_RDS_ENABLE);
++              if (ret < 0) {
++                      fmerr("Failed to enable rds mode\n");
++                      return ret;
++              }
++      }
++
++      /* Copy RDS data from internal buffer to user buffer */
++      return fmc_transfer_rds_from_internal_buff(fmdev, file, buf, count);
++}
++
++/* Write TX RDS data */
++static ssize_t fm_v4l2_fops_write(struct file *file, const char __user * buf,
++              size_t count, loff_t *ppos)
++{
++      struct tx_rds rds;
++      int ret;
++      struct fmdev *fmdev;
++
++      ret = copy_from_user(&rds, buf, sizeof(rds));
++      fmdbg("(%d)type: %d, text %s, af %d\n",
++                 ret, rds.text_type, rds.text, rds.af_freq);
++
++      fmdev = video_drvdata(file);
++      fm_tx_set_radio_text(fmdev, rds.text, rds.text_type);
++      fm_tx_set_af(fmdev, rds.af_freq);
++
++      return 0;
++}
++
++static u32 fm_v4l2_fops_poll(struct file *file, struct poll_table_struct *pts)
++{
++      int ret;
++      struct fmdev *fmdev;
++
++      fmdev = video_drvdata(file);
++      ret = fmc_is_rds_data_available(fmdev, file, pts);
++      if (ret < 0)
++              return POLLIN | POLLRDNORM;
++
++      return 0;
++}
++
++/*
++ * Handle open request for "/dev/radioX" device.
++ * Start with FM RX mode as default.
++ */
++static int fm_v4l2_fops_open(struct file *file)
++{
++      int ret;
++      struct fmdev *fmdev = NULL;
++
++      /* Don't allow multiple open */
++      if (radio_disconnected) {
++              fmerr("FM device is already opened\n");
++              return -EBUSY;
++      }
++
++      fmdev = video_drvdata(file);
++
++      ret = fmc_prepare(fmdev);
++      if (ret < 0) {
++              fmerr("Unable to prepare FM CORE\n");
++              return ret;
++      }
++
++      fmdbg("Load FM RX firmware..\n");
++
++      ret = fmc_set_mode(fmdev, FM_MODE_RX);
++      if (ret < 0) {
++              fmerr("Unable to load FM RX firmware\n");
++              return ret;
++      }
++      radio_disconnected = 1;
++
++      return ret;
++}
++
++static int fm_v4l2_fops_release(struct file *file)
++{
++      int ret;
++      struct fmdev *fmdev;
++
++      fmdev = video_drvdata(file);
++      if (!radio_disconnected) {
++              fmdbg("FM device is already closed\n");
++              return 0;
++      }
++
++      ret = fmc_set_mode(fmdev, FM_MODE_OFF);
++      if (ret < 0) {
++              fmerr("Unable to turn off the chip\n");
++              return ret;
++      }
++
++      ret = fmc_release(fmdev);
++      if (ret < 0) {
++              fmerr("FM CORE release failed\n");
++              return ret;
++      }
++      radio_disconnected = 0;
++
++      return ret;
++}
++
++/* V4L2 RADIO (/dev/radioX) device IOCTL interfaces */
++static int fm_v4l2_vidioc_querycap(struct file *file, void *priv,
++              struct v4l2_capability *capability)
++{
++      strlcpy(capability->driver, FM_DRV_NAME, sizeof(capability->driver));
++      strlcpy(capability->card, FM_DRV_CARD_SHORT_NAME,
++                      sizeof(capability->card));
++      sprintf(capability->bus_info, "UART");
++      capability->version = FM_DRV_RADIO_VERSION;
++      capability->capabilities = V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_TUNER |
++              V4L2_CAP_RADIO | V4L2_CAP_MODULATOR |
++              V4L2_CAP_AUDIO | V4L2_CAP_READWRITE |
++              V4L2_CAP_RDS_CAPTURE;
++
++      return 0;
++}
++
++static int fm_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
++{
++      struct fmdev *fmdev = container_of(ctrl->handler,
++                      struct fmdev, ctrl_handler);
++
++      switch (ctrl->id) {
++      case  V4L2_CID_TUNE_ANTENNA_CAPACITOR:
++              ctrl->val = fm_tx_get_tune_cap_val(fmdev);
++              break;
++      default:
++              fmwarn("%s: Unknown IOCTL: %d\n", __func__, ctrl->id);
++              break;
++      }
++
++      return 0;
++}
++
++static int fm_v4l2_s_ctrl(struct v4l2_ctrl *ctrl)
++{
++      struct fmdev *fmdev = container_of(ctrl->handler,
++                      struct fmdev, ctrl_handler);
++
++      switch (ctrl->id) {
++      case V4L2_CID_AUDIO_VOLUME:     /* set volume */
++              return fm_rx_set_volume(fmdev, (u16)ctrl->val);
++
++      case V4L2_CID_AUDIO_MUTE:       /* set mute */
++              return fmc_set_mute_mode(fmdev, (u8)ctrl->val);
++
++      case V4L2_CID_TUNE_POWER_LEVEL:
++              /* set TX power level - ext control */
++              return fm_tx_set_pwr_lvl(fmdev, (u8)ctrl->val);
++
++      case V4L2_CID_TUNE_PREEMPHASIS:
++              return fm_tx_set_preemph_filter(fmdev, (u8) ctrl->val);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++static int fm_v4l2_vidioc_g_audio(struct file *file, void *priv,
++              struct v4l2_audio *audio)
++{
++      memset(audio, 0, sizeof(*audio));
++      strcpy(audio->name, "Radio");
++      audio->capability = V4L2_AUDCAP_STEREO;
++
++      return 0;
++}
++
++static int fm_v4l2_vidioc_s_audio(struct file *file, void *priv,
++              struct v4l2_audio *audio)
++{
++      if (audio->index != 0)
++              return -EINVAL;
++
++      return 0;
++}
++
++/* Get tuner attributes. If current mode is NOT RX, return error */
++static int fm_v4l2_vidioc_g_tuner(struct file *file, void *priv,
++              struct v4l2_tuner *tuner)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++      u32 bottom_freq;
++      u32 top_freq;
++      u16 stereo_mono_mode;
++      u16 rssilvl;
++      int ret;
++
++      if (tuner->index != 0)
++              return -EINVAL;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      ret = fm_rx_get_band_freq_range(fmdev, &bottom_freq, &top_freq);
++      if (ret != 0)
++              return ret;
++
++      ret = fm_rx_get_stereo_mono(fmdev, &stereo_mono_mode);
++      if (ret != 0)
++              return ret;
++
++      ret = fm_rx_get_rssi_level(fmdev, &rssilvl);
++      if (ret != 0)
++              return ret;
++
++      strcpy(tuner->name, "FM");
++      tuner->type = V4L2_TUNER_RADIO;
++      /* Store rangelow and rangehigh freq in unit of 62.5 Hz */
++      tuner->rangelow = bottom_freq * 16;
++      tuner->rangehigh = top_freq * 16;
++      tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO |
++      ((fmdev->rx.rds.flag == FM_RDS_ENABLE) ? V4L2_TUNER_SUB_RDS : 0);
++      tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
++                          V4L2_TUNER_CAP_LOW;
++      tuner->audmode = (stereo_mono_mode ?
++                        V4L2_TUNER_MODE_MONO : V4L2_TUNER_MODE_STEREO);
++
++      /*
++       * Actual rssi value lies in between -128 to +127.
++       * Convert this range from 0 to 255 by adding +128
++       */
++      rssilvl += 128;
++
++      /*
++       * Return signal strength value should be within 0 to 65535.
++       * Find out correct signal radio by multiplying (65535/255) = 257
++       */
++      tuner->signal = rssilvl * 257;
++      tuner->afc = 0;
++
++      return ret;
++}
++
++/*
++ * Set tuner attributes. If current mode is NOT RX, set to RX.
++ * Currently, we set only audio mode (mono/stereo) and RDS state (on/off).
++ * Should we set other tuner attributes, too?
++ */
++static int fm_v4l2_vidioc_s_tuner(struct file *file, void *priv,
++              struct v4l2_tuner *tuner)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++      u16 aud_mode;
++      u8 rds_mode;
++      int ret;
++
++      if (tuner->index != 0)
++              return -EINVAL;
++
++      aud_mode = (tuner->audmode == V4L2_TUNER_MODE_STEREO) ?
++                      FM_STEREO_MODE : FM_MONO_MODE;
++      rds_mode = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
++                      FM_RDS_ENABLE : FM_RDS_DISABLE;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX) {
++              ret = fmc_set_mode(fmdev, FM_MODE_RX);
++              if (ret < 0) {
++                      fmerr("Failed to set RX mode\n");
++                      return ret;
++              }
++      }
++
++      ret = fmc_set_stereo_mono(fmdev, aud_mode);
++      if (ret < 0) {
++              fmerr("Failed to set RX stereo/mono mode\n");
++              return ret;
++      }
++
++      ret = fmc_set_rds_mode(fmdev, rds_mode);
++      if (ret < 0)
++              fmerr("Failed to set RX RDS mode\n");
++
++      return ret;
++}
++
++/* Get tuner or modulator radio frequency */
++static int fm_v4l2_vidioc_g_freq(struct file *file, void *priv,
++              struct v4l2_frequency *freq)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++      int ret;
++
++      ret = fmc_get_freq(fmdev, &freq->frequency);
++      if (ret < 0) {
++              fmerr("Failed to get frequency\n");
++              return ret;
++      }
++
++      /* Frequency unit of 62.5 Hz*/
++      freq->frequency = (u32) freq->frequency * 16;
++
++      return 0;
++}
++
++/* Set tuner or modulator radio frequency */
++static int fm_v4l2_vidioc_s_freq(struct file *file, void *priv,
++              struct v4l2_frequency *freq)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++
++      /*
++       * As V4L2_TUNER_CAP_LOW is set 1 user sends the frequency
++       * in units of 62.5 Hz.
++       */
++      freq->frequency = (u32)(freq->frequency / 16);
++
++      return fmc_set_freq(fmdev, freq->frequency);
++}
++
++/* Set hardware frequency seek. If current mode is NOT RX, set it RX. */
++static int fm_v4l2_vidioc_s_hw_freq_seek(struct file *file, void *priv,
++              struct v4l2_hw_freq_seek *seek)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++      int ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX) {
++              ret = fmc_set_mode(fmdev, FM_MODE_RX);
++              if (ret != 0) {
++                      fmerr("Failed to set RX mode\n");
++                      return ret;
++              }
++      }
++
++      ret = fm_rx_seek(fmdev, seek->seek_upward, seek->wrap_around,
++                      seek->spacing);
++      if (ret < 0)
++              fmerr("RX seek failed - %d\n", ret);
++
++      return ret;
++}
++/* Get modulator attributes. If mode is not TX, return no attributes. */
++static int fm_v4l2_vidioc_g_modulator(struct file *file, void *priv,
++              struct v4l2_modulator *mod)
++{
++      struct fmdev *fmdev = video_drvdata(file);;
++
++      if (mod->index != 0)
++              return -EINVAL;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++
++      mod->txsubchans = ((fmdev->tx_data.aud_mode == FM_STEREO_MODE) ?
++                              V4L2_TUNER_SUB_STEREO : V4L2_TUNER_SUB_MONO) |
++                              ((fmdev->tx_data.rds.flag == FM_RDS_ENABLE) ?
++                              V4L2_TUNER_SUB_RDS : 0);
++
++      mod->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_RDS |
++                              V4L2_TUNER_CAP_LOW;
++
++      return 0;
++}
++
++/* Set modulator attributes. If mode is not TX, set to TX. */
++static int fm_v4l2_vidioc_s_modulator(struct file *file, void *priv,
++              struct v4l2_modulator *mod)
++{
++      struct fmdev *fmdev = video_drvdata(file);
++      u8 rds_mode;
++      u16 aud_mode;
++      int ret;
++
++      if (mod->index != 0)
++              return -EINVAL;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX) {
++              ret = fmc_set_mode(fmdev, FM_MODE_TX);
++              if (ret != 0) {
++                      fmerr("Failed to set TX mode\n");
++                      return ret;
++              }
++      }
++
++      aud_mode = (mod->txsubchans & V4L2_TUNER_SUB_STEREO) ?
++                      FM_STEREO_MODE : FM_MONO_MODE;
++      rds_mode = (mod->txsubchans & V4L2_TUNER_SUB_RDS) ?
++                      FM_RDS_ENABLE : FM_RDS_DISABLE;
++      ret = fm_tx_set_stereo_mono(fmdev, aud_mode);
++      if (ret < 0) {
++              fmerr("Failed to set mono/stereo mode for TX\n");
++              return ret;
++      }
++      ret = fm_tx_set_rds_mode(fmdev, rds_mode);
++      if (ret < 0)
++              fmerr("Failed to set rds mode for TX\n");
++
++      return ret;
++}
++
++static const struct v4l2_file_operations fm_drv_fops = {
++      .owner = THIS_MODULE,
++      .read = fm_v4l2_fops_read,
++      .write = fm_v4l2_fops_write,
++      .poll = fm_v4l2_fops_poll,
++      .unlocked_ioctl = video_ioctl2,
++      .open = fm_v4l2_fops_open,
++      .release = fm_v4l2_fops_release,
++};
++
++static const struct v4l2_ctrl_ops fm_ctrl_ops = {
++      .s_ctrl = fm_v4l2_s_ctrl,
++      .g_volatile_ctrl = fm_g_volatile_ctrl,
++};
++static const struct v4l2_ioctl_ops fm_drv_ioctl_ops = {
++      .vidioc_querycap = fm_v4l2_vidioc_querycap,
++      .vidioc_g_audio = fm_v4l2_vidioc_g_audio,
++      .vidioc_s_audio = fm_v4l2_vidioc_s_audio,
++      .vidioc_g_tuner = fm_v4l2_vidioc_g_tuner,
++      .vidioc_s_tuner = fm_v4l2_vidioc_s_tuner,
++      .vidioc_g_frequency = fm_v4l2_vidioc_g_freq,
++      .vidioc_s_frequency = fm_v4l2_vidioc_s_freq,
++      .vidioc_s_hw_freq_seek = fm_v4l2_vidioc_s_hw_freq_seek,
++      .vidioc_g_modulator = fm_v4l2_vidioc_g_modulator,
++      .vidioc_s_modulator = fm_v4l2_vidioc_s_modulator
++};
++
++/* V4L2 RADIO device parent structure */
++static struct video_device fm_viddev_template = {
++      .fops = &fm_drv_fops,
++      .ioctl_ops = &fm_drv_ioctl_ops,
++      .name = FM_DRV_NAME,
++      .release = video_device_release,
++};
++
++int fm_v4l2_init_video_device(struct fmdev *fmdev, int radio_nr)
++{
++      struct v4l2_ctrl *ctrl;
++      int ret;
++
++      /* Init mutex for core locking */
++      mutex_init(&fmdev->mutex);
++
++      /* Allocate new video device */
++      gradio_dev = video_device_alloc();
++      if (NULL == gradio_dev) {
++              fmerr("Can't allocate video device\n");
++              return -ENOMEM;
++      }
++
++      /* Setup FM driver's V4L2 properties */
++      memcpy(gradio_dev, &fm_viddev_template, sizeof(fm_viddev_template));
++
++      video_set_drvdata(gradio_dev, fmdev);
++
++      gradio_dev->lock = &fmdev->mutex;
++
++      /* Register with V4L2 subsystem as RADIO device */
++      if (video_register_device(gradio_dev, VFL_TYPE_RADIO, radio_nr)) {
++              video_device_release(gradio_dev);
++              fmerr("Could not register video device\n");
++              return -ENOMEM;
++      }
++
++      fmdev->radio_dev = gradio_dev;
++
++      /* Register to v4l2 ctrl handler framework */
++      fmdev->radio_dev->ctrl_handler = &fmdev->ctrl_handler;
++
++      ret = v4l2_ctrl_handler_init(&fmdev->ctrl_handler, 5);
++      if (ret < 0) {
++              fmerr("(fmdev): Can't init ctrl handler\n");
++              v4l2_ctrl_handler_free(&fmdev->ctrl_handler);
++              return -EBUSY;
++      }
++
++      /*
++       * Following controls are handled by V4L2 control framework.
++       * Added in ascending ID order.
++       */
++      v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
++                      V4L2_CID_AUDIO_VOLUME, FM_RX_VOLUME_MIN,
++                      FM_RX_VOLUME_MAX, 1, FM_RX_VOLUME_MAX);
++
++      v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
++                      V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
++
++      v4l2_ctrl_new_std_menu(&fmdev->ctrl_handler, &fm_ctrl_ops,
++                      V4L2_CID_TUNE_PREEMPHASIS, V4L2_PREEMPHASIS_75_uS,
++                      0, V4L2_PREEMPHASIS_75_uS);
++
++      v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
++                      V4L2_CID_TUNE_POWER_LEVEL, FM_PWR_LVL_LOW,
++                      FM_PWR_LVL_HIGH, 1, FM_PWR_LVL_HIGH);
++
++      ctrl = v4l2_ctrl_new_std(&fmdev->ctrl_handler, &fm_ctrl_ops,
++                      V4L2_CID_TUNE_ANTENNA_CAPACITOR, 0,
++                      255, 1, 255);
++
++      if (ctrl)
++              ctrl->is_volatile = 1;
++
++      return 0;
++}
++
++void *fm_v4l2_deinit_video_device(void)
++{
++      struct fmdev *fmdev;
++
++
++      fmdev = video_get_drvdata(gradio_dev);
++
++      /* Unregister to v4l2 ctrl handler framework*/
++      v4l2_ctrl_handler_free(&fmdev->ctrl_handler);
++
++      /* Unregister RADIO device from V4L2 subsystem */
++      video_unregister_device(gradio_dev);
++
++      return fmdev;
++}
+diff --git a/drivers/media/radio/wl128x/fmdrv_v4l2.h b/drivers/media/radio/wl128x/fmdrv_v4l2.h
+new file mode 100644
+index 0000000..0ba79d7
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_v4l2.h
+@@ -0,0 +1,33 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *
++ *  FM V4L2 module header.
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#ifndef _FMDRV_V4L2_H
++#define _FMDRV_V4L2_H
++
++#include <media/v4l2-ioctl.h>
++#include <media/v4l2-common.h>
++#include <media/v4l2-ctrls.h>
++
++int fm_v4l2_init_video_device(struct fmdev *, int);
++void *fm_v4l2_deinit_video_device(void);
++
++#endif
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0009-drivers-media-radio-wl128x-FM-Driver-Common-sources.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0009-drivers-media-radio-wl128x-FM-Driver-Common-sources.patch
new file mode 100644 (file)
index 0000000..61dc164
--- /dev/null
@@ -0,0 +1,2114 @@
+From 1c32040233847f9c7998e7c557fa80dfd953e236 Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:23 +0000
+Subject: [PATCH 09/15] drivers:media:radio: wl128x: FM Driver Common sources
+
+These are the sources for the common interfaces required by the
+FM V4L2 driver for TI WL127x and WL128x chips.
+
+These implement the FM channel-8 protocol communication with the
+chip. This makes use of the Shared Transport as its transport.
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/fmdrv_common.c | 1677 +++++++++++++++++++++++++++++
+ drivers/media/radio/wl128x/fmdrv_common.h |  402 +++++++
+ 2 files changed, 2079 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_common.c
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_common.h
+
+diff --git a/drivers/media/radio/wl128x/fmdrv_common.c b/drivers/media/radio/wl128x/fmdrv_common.c
+new file mode 100644
+index 0000000..12f4c65
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_common.c
+@@ -0,0 +1,1677 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *
++ *  This sub-module of FM driver is common for FM RX and TX
++ *  functionality. This module is responsible for:
++ *  1) Forming group of Channel-8 commands to perform particular
++ *     functionality (eg., frequency set require more than
++ *     one Channel-8 command to be sent to the chip).
++ *  2) Sending each Channel-8 command to the chip and reading
++ *     response back over Shared Transport.
++ *  3) Managing TX and RX Queues and Tasklets.
++ *  4) Handling FM Interrupt packet and taking appropriate action.
++ *  5) Loading FM firmware to the chip (common, FM TX, and FM RX
++ *     firmware files based on mode selection)
++ *
++ *  Copyright (C) 2011 Texas Instruments
++ *  Author: Raja Mani <raja_mani@ti.com>
++ *  Author: Manjunatha Halli <manjunatha_halli@ti.com>
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#include <linux/module.h>
++#include <linux/firmware.h>
++#include <linux/delay.h>
++#include "fmdrv.h"
++#include "fmdrv_v4l2.h"
++#include "fmdrv_common.h"
++#include <linux/ti_wilink_st.h>
++#include "fmdrv_rx.h"
++#include "fmdrv_tx.h"
++
++/* Region info */
++static struct region_info region_configs[] = {
++      /* Europe/US */
++      {
++       .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL,
++       .bot_freq = 87500,     /* 87.5 MHz */
++       .top_freq = 108000,    /* 108 MHz */
++       .fm_band = 0,
++       },
++      /* Japan */
++      {
++       .chanl_space = FM_CHANNEL_SPACING_200KHZ * FM_FREQ_MUL,
++       .bot_freq = 76000,     /* 76 MHz */
++       .top_freq = 90000,     /* 90 MHz */
++       .fm_band = 1,
++       },
++};
++
++/* Band selection */
++static u8 default_radio_region;       /* Europe/US */
++module_param(default_radio_region, byte, 0);
++MODULE_PARM_DESC(default_radio_region, "Region: 0=Europe/US, 1=Japan");
++
++/* RDS buffer blocks */
++static u32 default_rds_buf = 300;
++module_param(default_rds_buf, uint, 0444);
++MODULE_PARM_DESC(rds_buf, "RDS buffer entries");
++
++/* Radio Nr */
++static u32 radio_nr = -1;
++module_param(radio_nr, int, 0444);
++MODULE_PARM_DESC(radio_nr, "Radio Nr");
++
++/* FM irq handlers forward declaration */
++static void fm_irq_send_flag_getcmd(struct fmdev *);
++static void fm_irq_handle_flag_getcmd_resp(struct fmdev *);
++static void fm_irq_handle_hw_malfunction(struct fmdev *);
++static void fm_irq_handle_rds_start(struct fmdev *);
++static void fm_irq_send_rdsdata_getcmd(struct fmdev *);
++static void fm_irq_handle_rdsdata_getcmd_resp(struct fmdev *);
++static void fm_irq_handle_rds_finish(struct fmdev *);
++static void fm_irq_handle_tune_op_ended(struct fmdev *);
++static void fm_irq_handle_power_enb(struct fmdev *);
++static void fm_irq_handle_low_rssi_start(struct fmdev *);
++static void fm_irq_afjump_set_pi(struct fmdev *);
++static void fm_irq_handle_set_pi_resp(struct fmdev *);
++static void fm_irq_afjump_set_pimask(struct fmdev *);
++static void fm_irq_handle_set_pimask_resp(struct fmdev *);
++static void fm_irq_afjump_setfreq(struct fmdev *);
++static void fm_irq_handle_setfreq_resp(struct fmdev *);
++static void fm_irq_afjump_enableint(struct fmdev *);
++static void fm_irq_afjump_enableint_resp(struct fmdev *);
++static void fm_irq_start_afjump(struct fmdev *);
++static void fm_irq_handle_start_afjump_resp(struct fmdev *);
++static void fm_irq_afjump_rd_freq(struct fmdev *);
++static void fm_irq_afjump_rd_freq_resp(struct fmdev *);
++static void fm_irq_handle_low_rssi_finish(struct fmdev *);
++static void fm_irq_send_intmsk_cmd(struct fmdev *);
++static void fm_irq_handle_intmsk_cmd_resp(struct fmdev *);
++
++/*
++ * When FM common module receives interrupt packet, following handlers
++ * will be executed one after another to service the interrupt(s)
++ */
++enum fmc_irq_handler_index {
++      FM_SEND_FLAG_GETCMD_IDX,
++      FM_HANDLE_FLAG_GETCMD_RESP_IDX,
++
++      /* HW malfunction irq handler */
++      FM_HW_MAL_FUNC_IDX,
++
++      /* RDS threshold reached irq handler */
++      FM_RDS_START_IDX,
++      FM_RDS_SEND_RDS_GETCMD_IDX,
++      FM_RDS_HANDLE_RDS_GETCMD_RESP_IDX,
++      FM_RDS_FINISH_IDX,
++
++      /* Tune operation ended irq handler */
++      FM_HW_TUNE_OP_ENDED_IDX,
++
++      /* TX power enable irq handler */
++      FM_HW_POWER_ENB_IDX,
++
++      /* Low RSSI irq handler */
++      FM_LOW_RSSI_START_IDX,
++      FM_AF_JUMP_SETPI_IDX,
++      FM_AF_JUMP_HANDLE_SETPI_RESP_IDX,
++      FM_AF_JUMP_SETPI_MASK_IDX,
++      FM_AF_JUMP_HANDLE_SETPI_MASK_RESP_IDX,
++      FM_AF_JUMP_SET_AF_FREQ_IDX,
++      FM_AF_JUMP_HANDLE_SET_AFFREQ_RESP_IDX,
++      FM_AF_JUMP_ENABLE_INT_IDX,
++      FM_AF_JUMP_ENABLE_INT_RESP_IDX,
++      FM_AF_JUMP_START_AFJUMP_IDX,
++      FM_AF_JUMP_HANDLE_START_AFJUMP_RESP_IDX,
++      FM_AF_JUMP_RD_FREQ_IDX,
++      FM_AF_JUMP_RD_FREQ_RESP_IDX,
++      FM_LOW_RSSI_FINISH_IDX,
++
++      /* Interrupt process post action */
++      FM_SEND_INTMSK_CMD_IDX,
++      FM_HANDLE_INTMSK_CMD_RESP_IDX,
++};
++
++/* FM interrupt handler table */
++static int_handler_prototype int_handler_table[] = {
++      fm_irq_send_flag_getcmd,
++      fm_irq_handle_flag_getcmd_resp,
++      fm_irq_handle_hw_malfunction,
++      fm_irq_handle_rds_start, /* RDS threshold reached irq handler */
++      fm_irq_send_rdsdata_getcmd,
++      fm_irq_handle_rdsdata_getcmd_resp,
++      fm_irq_handle_rds_finish,
++      fm_irq_handle_tune_op_ended,
++      fm_irq_handle_power_enb, /* TX power enable irq handler */
++      fm_irq_handle_low_rssi_start,
++      fm_irq_afjump_set_pi,
++      fm_irq_handle_set_pi_resp,
++      fm_irq_afjump_set_pimask,
++      fm_irq_handle_set_pimask_resp,
++      fm_irq_afjump_setfreq,
++      fm_irq_handle_setfreq_resp,
++      fm_irq_afjump_enableint,
++      fm_irq_afjump_enableint_resp,
++      fm_irq_start_afjump,
++      fm_irq_handle_start_afjump_resp,
++      fm_irq_afjump_rd_freq,
++      fm_irq_afjump_rd_freq_resp,
++      fm_irq_handle_low_rssi_finish,
++      fm_irq_send_intmsk_cmd, /* Interrupt process post action */
++      fm_irq_handle_intmsk_cmd_resp
++};
++
++long (*g_st_write) (struct sk_buff *skb);
++static struct completion wait_for_fmdrv_reg_comp;
++
++static inline void fm_irq_call(struct fmdev *fmdev)
++{
++      fmdev->irq_info.handlers[fmdev->irq_info.stage](fmdev);
++}
++
++/* Continue next function in interrupt handler table */
++static inline void fm_irq_call_stage(struct fmdev *fmdev, u8 stage)
++{
++      fmdev->irq_info.stage = stage;
++      fm_irq_call(fmdev);
++}
++
++static inline void fm_irq_timeout_stage(struct fmdev *fmdev, u8 stage)
++{
++      fmdev->irq_info.stage = stage;
++      mod_timer(&fmdev->irq_info.timer, jiffies + FM_DRV_TX_TIMEOUT);
++}
++
++#ifdef FM_DUMP_TXRX_PKT
++ /* To dump outgoing FM Channel-8 packets */
++inline void dump_tx_skb_data(struct sk_buff *skb)
++{
++      int len, len_org;
++      u8 index;
++      struct fm_cmd_msg_hdr *cmd_hdr;
++
++      cmd_hdr = (struct fm_cmd_msg_hdr *)skb->data;
++      printk(KERN_INFO "<<%shdr:%02x len:%02x opcode:%02x type:%s dlen:%02x",
++             fm_cb(skb)->completion ? " " : "*", cmd_hdr->hdr,
++             cmd_hdr->len, cmd_hdr->op,
++             cmd_hdr->rd_wr ? "RD" : "WR", cmd_hdr->dlen);
++
++      len_org = skb->len - FM_CMD_MSG_HDR_SIZE;
++      if (len_org > 0) {
++              printk("\n   data(%d): ", cmd_hdr->dlen);
++              len = min(len_org, 14);
++              for (index = 0; index < len; index++)
++                      printk("%x ",
++                             skb->data[FM_CMD_MSG_HDR_SIZE + index]);
++              printk("%s", (len_org > 14) ? ".." : "");
++      }
++      printk("\n");
++}
++
++ /* To dump incoming FM Channel-8 packets */
++inline void dump_rx_skb_data(struct sk_buff *skb)
++{
++      int len, len_org;
++      u8 index;
++      struct fm_event_msg_hdr *evt_hdr;
++
++      evt_hdr = (struct fm_event_msg_hdr *)skb->data;
++      printk(KERN_INFO ">> hdr:%02x len:%02x sts:%02x numhci:%02x "
++          "opcode:%02x type:%s dlen:%02x", evt_hdr->hdr, evt_hdr->len,
++          evt_hdr->status, evt_hdr->num_fm_hci_cmds, evt_hdr->op,
++          (evt_hdr->rd_wr) ? "RD" : "WR", evt_hdr->dlen);
++
++      len_org = skb->len - FM_EVT_MSG_HDR_SIZE;
++      if (len_org > 0) {
++              printk("\n   data(%d): ", evt_hdr->dlen);
++              len = min(len_org, 14);
++              for (index = 0; index < len; index++)
++                      printk("%x ",
++                             skb->data[FM_EVT_MSG_HDR_SIZE + index]);
++              printk("%s", (len_org > 14) ? ".." : "");
++      }
++      printk("\n");
++}
++#endif
++
++void fmc_update_region_info(struct fmdev *fmdev, u8 region_to_set)
++{
++      fmdev->rx.region = region_configs[region_to_set];
++}
++
++/*
++ * FM common sub-module will schedule this tasklet whenever it receives
++ * FM packet from ST driver.
++ */
++static void recv_tasklet(unsigned long arg)
++{
++      struct fmdev *fmdev;
++      struct fm_irq *irq_info;
++      struct fm_event_msg_hdr *evt_hdr;
++      struct sk_buff *skb;
++      u8 num_fm_hci_cmds;
++      unsigned long flags;
++
++      fmdev = (struct fmdev *)arg;
++      irq_info = &fmdev->irq_info;
++      /* Process all packets in the RX queue */
++      while ((skb = skb_dequeue(&fmdev->rx_q))) {
++              if (skb->len < sizeof(struct fm_event_msg_hdr)) {
++                      fmerr("skb(%p) has only %d bytes"
++                              "atleast need %d bytes to decode\n", skb,
++                              skb->len, sizeof(struct fm_event_msg_hdr));
++                      kfree_skb(skb);
++                      continue;
++              }
++
++              evt_hdr = (void *)skb->data;
++              num_fm_hci_cmds = evt_hdr->num_fm_hci_cmds;
++
++              /* FM interrupt packet? */
++              if (evt_hdr->op == FM_INTERRUPT) {
++                      /* FM interrupt handler started already? */
++                      if (!test_bit(FM_INTTASK_RUNNING, &fmdev->flag)) {
++                              set_bit(FM_INTTASK_RUNNING, &fmdev->flag);
++                              if (irq_info->stage != 0) {
++                                      fmerr("Inval stage resetting to zero\n");
++                                      irq_info->stage = 0;
++                              }
++
++                              /*
++                               * Execute first function in interrupt handler
++                               * table.
++                               */
++                              irq_info->handlers[irq_info->stage](fmdev);
++                      } else {
++                              set_bit(FM_INTTASK_SCHEDULE_PENDING, &fmdev->flag);
++                      }
++                      kfree_skb(skb);
++              }
++              /* Anyone waiting for this with completion handler? */
++              else if (evt_hdr->op == fmdev->pre_op && fmdev->resp_comp != NULL) {
++
++                      spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
++                      fmdev->resp_skb = skb;
++                      spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
++                      complete(fmdev->resp_comp);
++
++                      fmdev->resp_comp = NULL;
++                      atomic_set(&fmdev->tx_cnt, 1);
++              }
++              /* Is this for interrupt handler? */
++              else if (evt_hdr->op == fmdev->pre_op && fmdev->resp_comp == NULL) {
++                      if (fmdev->resp_skb != NULL)
++                              fmerr("Response SKB ptr not NULL\n");
++
++                      spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
++                      fmdev->resp_skb = skb;
++                      spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
++
++                      /* Execute interrupt handler where state index points */
++                      irq_info->handlers[irq_info->stage](fmdev);
++
++                      kfree_skb(skb);
++                      atomic_set(&fmdev->tx_cnt, 1);
++              } else {
++                      fmerr("Nobody claimed SKB(%p),purging\n", skb);
++              }
++
++              /*
++               * Check flow control field. If Num_FM_HCI_Commands field is
++               * not zero, schedule FM TX tasklet.
++               */
++              if (num_fm_hci_cmds && atomic_read(&fmdev->tx_cnt))
++                      if (!skb_queue_empty(&fmdev->tx_q))
++                              tasklet_schedule(&fmdev->tx_task);
++      }
++}
++
++/* FM send tasklet: is scheduled when FM packet has to be sent to chip */
++static void send_tasklet(unsigned long arg)
++{
++      struct fmdev *fmdev;
++      struct sk_buff *skb;
++      int len;
++
++      fmdev = (struct fmdev *)arg;
++
++      if (!atomic_read(&fmdev->tx_cnt))
++              return;
++
++      /* Check, is there any timeout happenned to last transmitted packet */
++      if ((jiffies - fmdev->last_tx_jiffies) > FM_DRV_TX_TIMEOUT) {
++              fmerr("TX timeout occurred\n");
++              atomic_set(&fmdev->tx_cnt, 1);
++      }
++
++      /* Send queued FM TX packets */
++      skb = skb_dequeue(&fmdev->tx_q);
++      if (!skb)
++              return;
++
++      atomic_dec(&fmdev->tx_cnt);
++      fmdev->pre_op = fm_cb(skb)->fm_op;
++
++      if (fmdev->resp_comp != NULL)
++              fmerr("Response completion handler is not NULL\n");
++
++      fmdev->resp_comp = fm_cb(skb)->completion;
++
++      /* Write FM packet to ST driver */
++      len = g_st_write(skb);
++      if (len < 0) {
++              kfree_skb(skb);
++              fmdev->resp_comp = NULL;
++              fmerr("TX tasklet failed to send skb(%p)\n", skb);
++              atomic_set(&fmdev->tx_cnt, 1);
++      } else {
++              fmdev->last_tx_jiffies = jiffies;
++      }
++}
++
++/*
++ * Queues FM Channel-8 packet to FM TX queue and schedules FM TX tasklet for
++ * transmission
++ */
++static u32 fm_send_cmd(struct fmdev *fmdev, u8 fm_op, u16 type,       void *payload,
++              int payload_len, struct completion *wait_completion)
++{
++      struct sk_buff *skb;
++      struct fm_cmd_msg_hdr *hdr;
++      int size;
++
++      if (fm_op >= FM_INTERRUPT) {
++              fmerr("Invalid fm opcode - %d\n", fm_op);
++              return -EINVAL;
++      }
++      if (test_bit(FM_FW_DW_INPROGRESS, &fmdev->flag) && payload == NULL) {
++              fmerr("Payload data is NULL during fw download\n");
++              return -EINVAL;
++      }
++      if (!test_bit(FM_FW_DW_INPROGRESS, &fmdev->flag))
++              size =
++                  FM_CMD_MSG_HDR_SIZE + ((payload == NULL) ? 0 : payload_len);
++      else
++              size = payload_len;
++
++      skb = alloc_skb(size, GFP_ATOMIC);
++      if (!skb) {
++              fmerr("No memory to create new SKB\n");
++              return -ENOMEM;
++      }
++      /*
++       * Don't fill FM header info for the commands which come from
++       * FM firmware file.
++       */
++      if (!test_bit(FM_FW_DW_INPROGRESS, &fmdev->flag) ||
++                      test_bit(FM_INTTASK_RUNNING, &fmdev->flag)) {
++              /* Fill command header info */
++              hdr = (struct fm_cmd_msg_hdr *)skb_put(skb, FM_CMD_MSG_HDR_SIZE);
++              hdr->hdr = FM_PKT_LOGICAL_CHAN_NUMBER;  /* 0x08 */
++
++              /* 3 (fm_opcode,rd_wr,dlen) + payload len) */
++              hdr->len = ((payload == NULL) ? 0 : payload_len) + 3;
++
++              /* FM opcode */
++              hdr->op = fm_op;
++
++              /* read/write type */
++              hdr->rd_wr = type;
++              hdr->dlen = payload_len;
++              fm_cb(skb)->fm_op = fm_op;
++
++              /*
++               * If firmware download has finished and the command is
++               * not a read command then payload is != NULL - a write
++               * command with u16 payload - convert to be16
++               */
++              if (payload != NULL)
++                      *(u16 *)payload = cpu_to_be16(*(u16 *)payload);
++
++      } else if (payload != NULL) {
++              fm_cb(skb)->fm_op = *((u8 *)payload + 2);
++      }
++      if (payload != NULL)
++              memcpy(skb_put(skb, payload_len), payload, payload_len);
++
++      fm_cb(skb)->completion = wait_completion;
++      skb_queue_tail(&fmdev->tx_q, skb);
++      tasklet_schedule(&fmdev->tx_task);
++
++      return 0;
++}
++
++/* Sends FM Channel-8 command to the chip and waits for the response */
++u32 fmc_send_cmd(struct fmdev *fmdev, u8 fm_op, u16 type, void *payload,
++              unsigned int payload_len, void *response, int *response_len)
++{
++      struct sk_buff *skb;
++      struct fm_event_msg_hdr *evt_hdr;
++      unsigned long flags;
++      u32 ret;
++
++      init_completion(&fmdev->maintask_comp);
++      ret = fm_send_cmd(fmdev, fm_op, type, payload, payload_len,
++                          &fmdev->maintask_comp);
++      if (ret)
++              return ret;
++
++      ret = wait_for_completion_timeout(&fmdev->maintask_comp, FM_DRV_TX_TIMEOUT);
++      if (!ret) {
++              fmerr("Timeout(%d sec),didn't get reg"
++                         "completion signal from RX tasklet\n",
++                         jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
++              return -ETIMEDOUT;
++      }
++      if (!fmdev->resp_skb) {
++              fmerr("Reponse SKB is missing\n");
++              return -EFAULT;
++      }
++      spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
++      skb = fmdev->resp_skb;
++      fmdev->resp_skb = NULL;
++      spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
++
++      evt_hdr = (void *)skb->data;
++      if (evt_hdr->status != 0) {
++              fmerr("Received event pkt status(%d) is not zero\n",
++                         evt_hdr->status);
++              kfree_skb(skb);
++              return -EIO;
++      }
++      /* Send response data to caller */
++      if (response != NULL && response_len != NULL && evt_hdr->dlen) {
++              /* Skip header info and copy only response data */
++              skb_pull(skb, sizeof(struct fm_event_msg_hdr));
++              memcpy(response, skb->data, evt_hdr->dlen);
++              *response_len = evt_hdr->dlen;
++      } else if (response_len != NULL && evt_hdr->dlen == 0) {
++              *response_len = 0;
++      }
++      kfree_skb(skb);
++
++      return 0;
++}
++
++/* --- Helper functions used in FM interrupt handlers ---*/
++static inline u32 check_cmdresp_status(struct fmdev *fmdev,
++              struct sk_buff **skb)
++{
++      struct fm_event_msg_hdr *fm_evt_hdr;
++      unsigned long flags;
++
++      del_timer(&fmdev->irq_info.timer);
++
++      spin_lock_irqsave(&fmdev->resp_skb_lock, flags);
++      *skb = fmdev->resp_skb;
++      fmdev->resp_skb = NULL;
++      spin_unlock_irqrestore(&fmdev->resp_skb_lock, flags);
++
++      fm_evt_hdr = (void *)(*skb)->data;
++      if (fm_evt_hdr->status != 0) {
++              fmerr("irq: opcode %x response status is not zero "
++                              "Initiating irq recovery process\n",
++                              fm_evt_hdr->op);
++
++              mod_timer(&fmdev->irq_info.timer, jiffies + FM_DRV_TX_TIMEOUT);
++              return -1;
++      }
++
++      return 0;
++}
++
++static inline void fm_irq_common_cmd_resp_helper(struct fmdev *fmdev, u8 stage)
++{
++      struct sk_buff *skb;
++
++      if (!check_cmdresp_status(fmdev, &skb))
++              fm_irq_call_stage(fmdev, stage);
++}
++
++/*
++ * Interrupt process timeout handler.
++ * One of the irq handler did not get proper response from the chip. So take
++ * recovery action here. FM interrupts are disabled in the beginning of
++ * interrupt process. Therefore reset stage index to re-enable default
++ * interrupts. So that next interrupt will be processed as usual.
++ */
++static void int_timeout_handler(unsigned long data)
++{
++      struct fmdev *fmdev;
++      struct fm_irq *fmirq;
++
++      fmdbg("irq: timeout,trying to re-enable fm interrupts\n");
++      fmdev = (struct fmdev *)data;
++      fmirq = &fmdev->irq_info;
++      fmirq->retry++;
++
++      if (fmirq->retry > FM_IRQ_TIMEOUT_RETRY_MAX) {
++              /* Stop recovery action (interrupt reenable process) and
++               * reset stage index & retry count values */
++              fmirq->stage = 0;
++              fmirq->retry = 0;
++              fmerr("Recovery action failed during"
++                              "irq processing, max retry reached\n");
++              return;
++      }
++      fm_irq_call_stage(fmdev, FM_SEND_INTMSK_CMD_IDX);
++}
++
++/* --------- FM interrupt handlers ------------*/
++static void fm_irq_send_flag_getcmd(struct fmdev *fmdev)
++{
++      u16 flag;
++
++      /* Send FLAG_GET command , to know the source of interrupt */
++      if (!fm_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, sizeof(flag), NULL))
++              fm_irq_timeout_stage(fmdev, FM_HANDLE_FLAG_GETCMD_RESP_IDX);
++}
++
++static void fm_irq_handle_flag_getcmd_resp(struct fmdev *fmdev)
++{
++      struct sk_buff *skb;
++      struct fm_event_msg_hdr *fm_evt_hdr;
++
++      if (check_cmdresp_status(fmdev, &skb))
++              return;
++
++      fm_evt_hdr = (void *)skb->data;
++
++      /* Skip header info and copy only response data */
++      skb_pull(skb, sizeof(struct fm_event_msg_hdr));
++      memcpy(&fmdev->irq_info.flag, skb->data, fm_evt_hdr->dlen);
++
++      fmdev->irq_info.flag = be16_to_cpu(fmdev->irq_info.flag);
++      fmdbg("irq: flag register(0x%x)\n", fmdev->irq_info.flag);
++
++      /* Continue next function in interrupt handler table */
++      fm_irq_call_stage(fmdev, FM_HW_MAL_FUNC_IDX);
++}
++
++static void fm_irq_handle_hw_malfunction(struct fmdev *fmdev)
++{
++      if (fmdev->irq_info.flag & FM_MAL_EVENT & fmdev->irq_info.mask)
++              fmerr("irq: HW MAL int received - do nothing\n");
++
++      /* Continue next function in interrupt handler table */
++      fm_irq_call_stage(fmdev, FM_RDS_START_IDX);
++}
++
++static void fm_irq_handle_rds_start(struct fmdev *fmdev)
++{
++      if (fmdev->irq_info.flag & FM_RDS_EVENT & fmdev->irq_info.mask) {
++              fmdbg("irq: rds threshold reached\n");
++              fmdev->irq_info.stage = FM_RDS_SEND_RDS_GETCMD_IDX;
++      } else {
++              /* Continue next function in interrupt handler table */
++              fmdev->irq_info.stage = FM_HW_TUNE_OP_ENDED_IDX;
++      }
++
++      fm_irq_call(fmdev);
++}
++
++static void fm_irq_send_rdsdata_getcmd(struct fmdev *fmdev)
++{
++      /* Send the command to read RDS data from the chip */
++      if (!fm_send_cmd(fmdev, RDS_DATA_GET, REG_RD, NULL,
++                          (FM_RX_RDS_FIFO_THRESHOLD * 3), NULL))
++              fm_irq_timeout_stage(fmdev, FM_RDS_HANDLE_RDS_GETCMD_RESP_IDX);
++}
++
++/* Keeps track of current RX channel AF (Alternate Frequency) */
++static void fm_rx_update_af_cache(struct fmdev *fmdev, u8 af)
++{
++      struct tuned_station_info *stat_info = &fmdev->rx.stat_info;
++      u8 reg_idx = fmdev->rx.region.fm_band;
++      u8 index;
++      u32 freq;
++
++      /* First AF indicates the number of AF follows. Reset the list */
++      if ((af >= FM_RDS_1_AF_FOLLOWS) && (af <= FM_RDS_25_AF_FOLLOWS)) {
++              fmdev->rx.stat_info.af_list_max = (af - FM_RDS_1_AF_FOLLOWS + 1);
++              fmdev->rx.stat_info.afcache_size = 0;
++              fmdbg("No of expected AF : %d\n", fmdev->rx.stat_info.af_list_max);
++              return;
++      }
++
++      if (af < FM_RDS_MIN_AF)
++              return;
++      if (reg_idx == FM_BAND_EUROPE_US && af > FM_RDS_MAX_AF)
++              return;
++      if (reg_idx == FM_BAND_JAPAN && af > FM_RDS_MAX_AF_JAPAN)
++              return;
++
++      freq = fmdev->rx.region.bot_freq + (af * 100);
++      if (freq == fmdev->rx.freq) {
++              fmdbg("Current freq(%d) is matching with received AF(%d)\n",
++                              fmdev->rx.freq, freq);
++              return;
++      }
++      /* Do check in AF cache */
++      for (index = 0; index < stat_info->afcache_size; index++) {
++              if (stat_info->af_cache[index] == freq)
++                      break;
++      }
++      /* Reached the limit of the list - ignore the next AF */
++      if (index == stat_info->af_list_max) {
++              fmdbg("AF cache is full\n");
++              return;
++      }
++      /*
++       * If we reached the end of the list then this AF is not
++       * in the list - add it.
++       */
++      if (index == stat_info->afcache_size) {
++              fmdbg("Storing AF %d to cache index %d\n", freq, index);
++              stat_info->af_cache[index] = freq;
++              stat_info->afcache_size++;
++      }
++}
++
++/*
++ * Converts RDS buffer data from big endian format
++ * to little endian format.
++ */
++static void fm_rdsparse_swapbytes(struct fmdev *fmdev,
++              struct fm_rdsdata_format *rds_format)
++{
++      u8 byte1;
++      u8 index = 0;
++      u8 *rds_buff;
++
++      /*
++       * Since in Orca the 2 RDS Data bytes are in little endian and
++       * in Dolphin they are in big endian, the parsing of the RDS data
++       * is chip dependent
++       */
++      if (fmdev->asci_id != 0x6350) {
++              rds_buff = &rds_format->data.groupdatabuff.buff[0];
++              while (index + 1 < FM_RX_RDS_INFO_FIELD_MAX) {
++                      byte1 = rds_buff[index];
++                      rds_buff[index] = rds_buff[index + 1];
++                      rds_buff[index + 1] = byte1;
++                      index += 2;
++              }
++      }
++}
++
++static void fm_irq_handle_rdsdata_getcmd_resp(struct fmdev *fmdev)
++{
++      struct sk_buff *skb;
++      struct fm_rdsdata_format rds_fmt;
++      struct fm_rds *rds = &fmdev->rx.rds;
++      unsigned long group_idx, flags;
++      u8 *rds_data, meta_data, tmpbuf[3];
++      u8 type, blk_idx;
++      u16 cur_picode;
++      u32 rds_len;
++
++      if (check_cmdresp_status(fmdev, &skb))
++              return;
++
++      /* Skip header info */
++      skb_pull(skb, sizeof(struct fm_event_msg_hdr));
++      rds_data = skb->data;
++      rds_len = skb->len;
++
++      /* Parse the RDS data */
++      while (rds_len >= FM_RDS_BLK_SIZE) {
++              meta_data = rds_data[2];
++              /* Get the type: 0=A, 1=B, 2=C, 3=C', 4=D, 5=E */
++              type = (meta_data & 0x07);
++
++              /* Transform the blk type into index sequence (0, 1, 2, 3, 4) */
++              blk_idx = (type <= FM_RDS_BLOCK_C ? type : (type - 1));
++              fmdbg("Block index:%d(%s)\n", blk_idx,
++                         (meta_data & FM_RDS_STATUS_ERR_MASK) ? "Bad" : "Ok");
++
++              if ((meta_data & FM_RDS_STATUS_ERR_MASK) != 0)
++                      break;
++
++              if (blk_idx < FM_RDS_BLK_IDX_A || blk_idx > FM_RDS_BLK_IDX_D) {
++                      fmdbg("Block sequence mismatch\n");
++                      rds->last_blk_idx = -1;
++                      break;
++              }
++
++              /* Skip checkword (control) byte and copy only data byte */
++              memcpy(&rds_fmt.data.groupdatabuff.
++                              buff[blk_idx * (FM_RDS_BLK_SIZE - 1)],
++                              rds_data, (FM_RDS_BLK_SIZE - 1));
++
++              rds->last_blk_idx = blk_idx;
++
++              /* If completed a whole group then handle it */
++              if (blk_idx == FM_RDS_BLK_IDX_D) {
++                      fmdbg("Good block received\n");
++                      fm_rdsparse_swapbytes(fmdev, &rds_fmt);
++
++                      /*
++                       * Extract PI code and store in local cache.
++                       * We need this during AF switch processing.
++                       */
++                      cur_picode = be16_to_cpu(rds_fmt.data.groupgeneral.pidata);
++                      if (fmdev->rx.stat_info.picode != cur_picode)
++                              fmdev->rx.stat_info.picode = cur_picode;
++
++                      fmdbg("picode:%d\n", cur_picode);
++
++                      group_idx = (rds_fmt.data.groupgeneral.blk_b[0] >> 3);
++                      fmdbg("(fmdrv):Group:%ld%s\n", group_idx/2,
++                                      (group_idx % 2) ? "B" : "A");
++
++                      group_idx = 1 << (rds_fmt.data.groupgeneral.blk_b[0] >> 3);
++                      if (group_idx == FM_RDS_GROUP_TYPE_MASK_0A) {
++                              fm_rx_update_af_cache(fmdev, rds_fmt.data.group0A.af[0]);
++                              fm_rx_update_af_cache(fmdev, rds_fmt.data.group0A.af[1]);
++                      }
++              }
++              rds_len -= FM_RDS_BLK_SIZE;
++              rds_data += FM_RDS_BLK_SIZE;
++      }
++
++      /* Copy raw rds data to internal rds buffer */
++      rds_data = skb->data;
++      rds_len = skb->len;
++
++      spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
++      while (rds_len > 0) {
++              /*
++               * Fill RDS buffer as per V4L2 specification.
++               * Store control byte
++               */
++              type = (rds_data[2] & 0x07);
++              blk_idx = (type <= FM_RDS_BLOCK_C ? type : (type - 1));
++              tmpbuf[2] = blk_idx;    /* Offset name */
++              tmpbuf[2] |= blk_idx << 3;      /* Received offset */
++
++              /* Store data byte */
++              tmpbuf[0] = rds_data[0];
++              tmpbuf[1] = rds_data[1];
++
++              memcpy(&rds->buff[rds->wr_idx], &tmpbuf, FM_RDS_BLK_SIZE);
++              rds->wr_idx = (rds->wr_idx + FM_RDS_BLK_SIZE) % rds->buf_size;
++
++              /* Check for overflow & start over */
++              if (rds->wr_idx == rds->rd_idx) {
++                      fmdbg("RDS buffer overflow\n");
++                      rds->wr_idx = 0;
++                      rds->rd_idx = 0;
++                      break;
++              }
++              rds_len -= FM_RDS_BLK_SIZE;
++              rds_data += FM_RDS_BLK_SIZE;
++      }
++      spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
++
++      /* Wakeup read queue */
++      if (rds->wr_idx != rds->rd_idx)
++              wake_up_interruptible(&rds->read_queue);
++
++      fm_irq_call_stage(fmdev, FM_RDS_FINISH_IDX);
++}
++
++static void fm_irq_handle_rds_finish(struct fmdev *fmdev)
++{
++      fm_irq_call_stage(fmdev, FM_HW_TUNE_OP_ENDED_IDX);
++}
++
++static void fm_irq_handle_tune_op_ended(struct fmdev *fmdev)
++{
++      if (fmdev->irq_info.flag & (FM_FR_EVENT | FM_BL_EVENT) & fmdev->
++          irq_info.mask) {
++              fmdbg("irq: tune ended/bandlimit reached\n");
++              if (test_and_clear_bit(FM_AF_SWITCH_INPROGRESS, &fmdev->flag)) {
++                      fmdev->irq_info.stage = FM_AF_JUMP_RD_FREQ_IDX;
++              } else {
++                      complete(&fmdev->maintask_comp);
++                      fmdev->irq_info.stage = FM_HW_POWER_ENB_IDX;
++              }
++      } else
++              fmdev->irq_info.stage = FM_HW_POWER_ENB_IDX;
++
++      fm_irq_call(fmdev);
++}
++
++static void fm_irq_handle_power_enb(struct fmdev *fmdev)
++{
++      if (fmdev->irq_info.flag & FM_POW_ENB_EVENT) {
++              fmdbg("irq: Power Enabled/Disabled\n");
++              complete(&fmdev->maintask_comp);
++      }
++
++      fm_irq_call_stage(fmdev, FM_LOW_RSSI_START_IDX);
++}
++
++static void fm_irq_handle_low_rssi_start(struct fmdev *fmdev)
++{
++      if ((fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON) &&
++          (fmdev->irq_info.flag & FM_LEV_EVENT & fmdev->irq_info.mask) &&
++          (fmdev->rx.freq != FM_UNDEFINED_FREQ) &&
++          (fmdev->rx.stat_info.afcache_size != 0)) {
++              fmdbg("irq: rssi level has fallen below threshold level\n");
++
++              /* Disable further low RSSI interrupts */
++              fmdev->irq_info.mask &= ~FM_LEV_EVENT;
++
++              fmdev->rx.afjump_idx = 0;
++              fmdev->rx.freq_before_jump = fmdev->rx.freq;
++              fmdev->irq_info.stage = FM_AF_JUMP_SETPI_IDX;
++      } else {
++              /* Continue next function in interrupt handler table */
++              fmdev->irq_info.stage = FM_SEND_INTMSK_CMD_IDX;
++      }
++
++      fm_irq_call(fmdev);
++}
++
++static void fm_irq_afjump_set_pi(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      /* Set PI code - must be updated if the AF list is not empty */
++      payload = fmdev->rx.stat_info.picode;
++      if (!fm_send_cmd(fmdev, RDS_PI_SET, REG_WR, &payload, sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_HANDLE_SETPI_RESP_IDX);
++}
++
++static void fm_irq_handle_set_pi_resp(struct fmdev *fmdev)
++{
++      fm_irq_common_cmd_resp_helper(fmdev, FM_AF_JUMP_SETPI_MASK_IDX);
++}
++
++/*
++ * Set PI mask.
++ * 0xFFFF = Enable PI code matching
++ * 0x0000 = Disable PI code matching
++ */
++static void fm_irq_afjump_set_pimask(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      payload = 0x0000;
++      if (!fm_send_cmd(fmdev, RDS_PI_MASK_SET, REG_WR, &payload, sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_HANDLE_SETPI_MASK_RESP_IDX);
++}
++
++static void fm_irq_handle_set_pimask_resp(struct fmdev *fmdev)
++{
++      fm_irq_common_cmd_resp_helper(fmdev, FM_AF_JUMP_SET_AF_FREQ_IDX);
++}
++
++static void fm_irq_afjump_setfreq(struct fmdev *fmdev)
++{
++      u16 frq_index;
++      u16 payload;
++
++      fmdbg("Swtich to %d KHz\n", fmdev->rx.stat_info.af_cache[fmdev->rx.afjump_idx]);
++      frq_index = (fmdev->rx.stat_info.af_cache[fmdev->rx.afjump_idx] -
++           fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
++
++      payload = frq_index;
++      if (!fm_send_cmd(fmdev, AF_FREQ_SET, REG_WR, &payload, sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_HANDLE_SET_AFFREQ_RESP_IDX);
++}
++
++static void fm_irq_handle_setfreq_resp(struct fmdev *fmdev)
++{
++      fm_irq_common_cmd_resp_helper(fmdev, FM_AF_JUMP_ENABLE_INT_IDX);
++}
++
++static void fm_irq_afjump_enableint(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      /* Enable FR (tuning operation ended) interrupt */
++      payload = FM_FR_EVENT;
++      if (!fm_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload, sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_ENABLE_INT_RESP_IDX);
++}
++
++static void fm_irq_afjump_enableint_resp(struct fmdev *fmdev)
++{
++      fm_irq_common_cmd_resp_helper(fmdev, FM_AF_JUMP_START_AFJUMP_IDX);
++}
++
++static void fm_irq_start_afjump(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      payload = FM_TUNER_AF_JUMP_MODE;
++      if (!fm_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_HANDLE_START_AFJUMP_RESP_IDX);
++}
++
++static void fm_irq_handle_start_afjump_resp(struct fmdev *fmdev)
++{
++      struct sk_buff *skb;
++
++      if (check_cmdresp_status(fmdev, &skb))
++              return;
++
++      fmdev->irq_info.stage = FM_SEND_FLAG_GETCMD_IDX;
++      set_bit(FM_AF_SWITCH_INPROGRESS, &fmdev->flag);
++      clear_bit(FM_INTTASK_RUNNING, &fmdev->flag);
++}
++
++static void fm_irq_afjump_rd_freq(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      if (!fm_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_AF_JUMP_RD_FREQ_RESP_IDX);
++}
++
++static void fm_irq_afjump_rd_freq_resp(struct fmdev *fmdev)
++{
++      struct sk_buff *skb;
++      u16 read_freq;
++      u32 curr_freq, jumped_freq;
++
++      if (check_cmdresp_status(fmdev, &skb))
++              return;
++
++      /* Skip header info and copy only response data */
++      skb_pull(skb, sizeof(struct fm_event_msg_hdr));
++      memcpy(&read_freq, skb->data, sizeof(read_freq));
++      read_freq = be16_to_cpu(read_freq);
++      curr_freq = fmdev->rx.region.bot_freq + ((u32)read_freq * FM_FREQ_MUL);
++
++      jumped_freq = fmdev->rx.stat_info.af_cache[fmdev->rx.afjump_idx];
++
++      /* If the frequency was changed the jump succeeded */
++      if ((curr_freq != fmdev->rx.freq_before_jump) && (curr_freq == jumped_freq)) {
++              fmdbg("Successfully switched to alternate freq %d\n", curr_freq);
++              fmdev->rx.freq = curr_freq;
++              fm_rx_reset_rds_cache(fmdev);
++
++              /* AF feature is on, enable low level RSSI interrupt */
++              if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
++                      fmdev->irq_info.mask |= FM_LEV_EVENT;
++
++              fmdev->irq_info.stage = FM_LOW_RSSI_FINISH_IDX;
++      } else {                /* jump to the next freq in the AF list */
++              fmdev->rx.afjump_idx++;
++
++              /* If we reached the end of the list - stop searching */
++              if (fmdev->rx.afjump_idx >= fmdev->rx.stat_info.afcache_size) {
++                      fmdbg("AF switch processing failed\n");
++                      fmdev->irq_info.stage = FM_LOW_RSSI_FINISH_IDX;
++              } else {        /* AF List is not over - try next one */
++
++                      fmdbg("Trying next freq in AF cache\n");
++                      fmdev->irq_info.stage = FM_AF_JUMP_SETPI_IDX;
++              }
++      }
++      fm_irq_call(fmdev);
++}
++
++static void fm_irq_handle_low_rssi_finish(struct fmdev *fmdev)
++{
++      fm_irq_call_stage(fmdev, FM_SEND_INTMSK_CMD_IDX);
++}
++
++static void fm_irq_send_intmsk_cmd(struct fmdev *fmdev)
++{
++      u16 payload;
++
++      /* Re-enable FM interrupts */
++      payload = fmdev->irq_info.mask;
++
++      if (!fm_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL))
++              fm_irq_timeout_stage(fmdev, FM_HANDLE_INTMSK_CMD_RESP_IDX);
++}
++
++static void fm_irq_handle_intmsk_cmd_resp(struct fmdev *fmdev)
++{
++      struct sk_buff *skb;
++
++      if (check_cmdresp_status(fmdev, &skb))
++              return;
++      /*
++       * This is last function in interrupt table to be executed.
++       * So, reset stage index to 0.
++       */
++      fmdev->irq_info.stage = FM_SEND_FLAG_GETCMD_IDX;
++
++      /* Start processing any pending interrupt */
++      if (test_and_clear_bit(FM_INTTASK_SCHEDULE_PENDING, &fmdev->flag))
++              fmdev->irq_info.handlers[fmdev->irq_info.stage](fmdev);
++      else
++              clear_bit(FM_INTTASK_RUNNING, &fmdev->flag);
++}
++
++/* Returns availability of RDS data in internel buffer */
++u32 fmc_is_rds_data_available(struct fmdev *fmdev, struct file *file,
++                              struct poll_table_struct *pts)
++{
++      poll_wait(file, &fmdev->rx.rds.read_queue, pts);
++      if (fmdev->rx.rds.rd_idx != fmdev->rx.rds.wr_idx)
++              return 0;
++
++      return -EAGAIN;
++}
++
++/* Copies RDS data from internal buffer to user buffer */
++u32 fmc_transfer_rds_from_internal_buff(struct fmdev *fmdev, struct file *file,
++              u8 __user *buf, size_t count)
++{
++      u32 block_count;
++      unsigned long flags;
++      int ret;
++
++      if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx) {
++              if (file->f_flags & O_NONBLOCK)
++                      return -EWOULDBLOCK;
++
++              ret = wait_event_interruptible(fmdev->rx.rds.read_queue,
++                              (fmdev->rx.rds.wr_idx != fmdev->rx.rds.rd_idx));
++              if (ret)
++                      return -EINTR;
++      }
++
++      /* Calculate block count from byte count */
++      count /= 3;
++      block_count = 0;
++      ret = 0;
++
++      spin_lock_irqsave(&fmdev->rds_buff_lock, flags);
++
++      while (block_count < count) {
++              if (fmdev->rx.rds.wr_idx == fmdev->rx.rds.rd_idx)
++                      break;
++
++              if (copy_to_user(buf, &fmdev->rx.rds.buff[fmdev->rx.rds.rd_idx],
++                                      FM_RDS_BLK_SIZE))
++                      break;
++
++              fmdev->rx.rds.rd_idx += FM_RDS_BLK_SIZE;
++              if (fmdev->rx.rds.rd_idx >= fmdev->rx.rds.buf_size)
++                      fmdev->rx.rds.rd_idx = 0;
++
++              block_count++;
++              buf += FM_RDS_BLK_SIZE;
++              ret += FM_RDS_BLK_SIZE;
++      }
++      spin_unlock_irqrestore(&fmdev->rds_buff_lock, flags);
++      return ret;
++}
++
++u32 fmc_set_freq(struct fmdev *fmdev, u32 freq_to_set)
++{
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              return fm_rx_set_freq(fmdev, freq_to_set);
++
++      case FM_MODE_TX:
++              return fm_tx_set_freq(fmdev, freq_to_set);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++u32 fmc_get_freq(struct fmdev *fmdev, u32 *cur_tuned_frq)
++{
++      if (fmdev->rx.freq == FM_UNDEFINED_FREQ) {
++              fmerr("RX frequency is not set\n");
++              return -EPERM;
++      }
++      if (cur_tuned_frq == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              *cur_tuned_frq = fmdev->rx.freq;
++              return 0;
++
++      case FM_MODE_TX:
++              *cur_tuned_frq = 0;     /* TODO : Change this later */
++              return 0;
++
++      default:
++              return -EINVAL;
++      }
++
++}
++
++u32 fmc_set_region(struct fmdev *fmdev, u8 region_to_set)
++{
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              return fm_rx_set_region(fmdev, region_to_set);
++
++      case FM_MODE_TX:
++              return fm_tx_set_region(fmdev, region_to_set);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++u32 fmc_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)
++{
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              return fm_rx_set_mute_mode(fmdev, mute_mode_toset);
++
++      case FM_MODE_TX:
++              return fm_tx_set_mute_mode(fmdev, mute_mode_toset);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++u32 fmc_set_stereo_mono(struct fmdev *fmdev, u16 mode)
++{
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              return fm_rx_set_stereo_mono(fmdev, mode);
++
++      case FM_MODE_TX:
++              return fm_tx_set_stereo_mono(fmdev, mode);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++u32 fmc_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)
++{
++      switch (fmdev->curr_fmmode) {
++      case FM_MODE_RX:
++              return fm_rx_set_rds_mode(fmdev, rds_en_dis);
++
++      case FM_MODE_TX:
++              return fm_tx_set_rds_mode(fmdev, rds_en_dis);
++
++      default:
++              return -EINVAL;
++      }
++}
++
++/* Sends power off command to the chip */
++static u32 fm_power_down(struct fmdev *fmdev)
++{
++      u16 payload;
++      u32 ret;
++
++      if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
++              fmerr("FM core is not ready\n");
++              return -EPERM;
++      }
++      if (fmdev->curr_fmmode == FM_MODE_OFF) {
++              fmdbg("FM chip is already in OFF state\n");
++              return 0;
++      }
++
++      payload = 0x0;
++      ret = fmc_send_cmd(fmdev, FM_POWER_MODE, REG_WR, &payload,
++              sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return fmc_release(fmdev);
++}
++
++/* Reads init command from FM firmware file and loads to the chip */
++static u32 fm_download_firmware(struct fmdev *fmdev, const u8 *fw_name)
++{
++      const struct firmware *fw_entry;
++      struct bts_header *fw_header;
++      struct bts_action *action;
++      struct bts_action_delay *delay;
++      u8 *fw_data;
++      int ret, fw_len, cmd_cnt;
++
++      cmd_cnt = 0;
++      set_bit(FM_FW_DW_INPROGRESS, &fmdev->flag);
++
++      ret = request_firmware(&fw_entry, fw_name,
++                              &fmdev->radio_dev->dev);
++      if (ret < 0) {
++              fmerr("Unable to read firmware(%s) content\n", fw_name);
++              return ret;
++      }
++      fmdbg("Firmware(%s) length : %d bytes\n", fw_name, fw_entry->size);
++
++      fw_data = (void *)fw_entry->data;
++      fw_len = fw_entry->size;
++
++      fw_header = (struct bts_header *)fw_data;
++      if (fw_header->magic != FM_FW_FILE_HEADER_MAGIC) {
++              fmerr("%s not a legal TI firmware file\n", fw_name);
++              ret = -EINVAL;
++              goto rel_fw;
++      }
++      fmdbg("FW(%s) magic number : 0x%x\n", fw_name, fw_header->magic);
++
++      /* Skip file header info , we already verified it */
++      fw_data += sizeof(struct bts_header);
++      fw_len -= sizeof(struct bts_header);
++
++      while (fw_data && fw_len > 0) {
++              action = (struct bts_action *)fw_data;
++
++              switch (action->type) {
++              case ACTION_SEND_COMMAND:       /* Send */
++                      if (fmc_send_cmd(fmdev, 0, 0, action->data,
++                                              action->size, NULL, NULL))
++                              goto rel_fw;
++
++                      cmd_cnt++;
++                      break;
++
++              case ACTION_DELAY:      /* Delay */
++                      delay = (struct bts_action_delay *)action->data;
++                      mdelay(delay->msec);
++                      break;
++              }
++
++              fw_data += (sizeof(struct bts_action) + (action->size));
++              fw_len -= (sizeof(struct bts_action) + (action->size));
++      }
++      fmdbg("Firmware commands(%d) loaded to chip\n", cmd_cnt);
++rel_fw:
++      release_firmware(fw_entry);
++      clear_bit(FM_FW_DW_INPROGRESS, &fmdev->flag);
++
++      return ret;
++}
++
++/* Loads default RX configuration to the chip */
++static u32 load_default_rx_configuration(struct fmdev *fmdev)
++{
++      int ret;
++
++      ret = fm_rx_set_volume(fmdev, FM_DEFAULT_RX_VOLUME);
++      if (ret < 0)
++              return ret;
++
++      return fm_rx_set_rssi_threshold(fmdev, FM_DEFAULT_RSSI_THRESHOLD);
++}
++
++/* Does FM power on sequence */
++static u32 fm_power_up(struct fmdev *fmdev, u8 mode)
++{
++      u16 payload, asic_id, asic_ver;
++      int resp_len, ret;
++      u8 fw_name[50];
++
++      if (mode >= FM_MODE_ENTRY_MAX) {
++              fmerr("Invalid firmware download option\n");
++              return -EINVAL;
++      }
++
++      /*
++       * Initialize FM common module. FM GPIO toggling is
++       * taken care in Shared Transport driver.
++       */
++      ret = fmc_prepare(fmdev);
++      if (ret < 0) {
++              fmerr("Unable to prepare FM Common\n");
++              return ret;
++      }
++
++      payload = FM_ENABLE;
++      if (fmc_send_cmd(fmdev, FM_POWER_MODE, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL))
++              goto rel;
++
++      /* Allow the chip to settle down in Channel-8 mode */
++      msleep(20);
++
++      if (fmc_send_cmd(fmdev, ASIC_ID_GET, REG_RD, NULL,
++                      sizeof(asic_id), &asic_id, &resp_len))
++              goto rel;
++
++      if (fmc_send_cmd(fmdev, ASIC_VER_GET, REG_RD, NULL,
++                      sizeof(asic_ver), &asic_ver, &resp_len))
++              goto rel;
++
++      fmdbg("ASIC ID: 0x%x , ASIC Version: %d\n",
++              be16_to_cpu(asic_id), be16_to_cpu(asic_ver));
++
++      sprintf(fw_name, "%s_%x.%d.bts", FM_FMC_FW_FILE_START,
++              be16_to_cpu(asic_id), be16_to_cpu(asic_ver));
++
++      ret = fm_download_firmware(fmdev, fw_name);
++      if (ret < 0) {
++              fmdbg("Failed to download firmware file %s\n", fw_name);
++              goto rel;
++      }
++      sprintf(fw_name, "%s_%x.%d.bts", (mode == FM_MODE_RX) ?
++                      FM_RX_FW_FILE_START : FM_TX_FW_FILE_START,
++                      be16_to_cpu(asic_id), be16_to_cpu(asic_ver));
++
++      ret = fm_download_firmware(fmdev, fw_name);
++      if (ret < 0) {
++              fmdbg("Failed to download firmware file %s\n", fw_name);
++              goto rel;
++      } else
++              return ret;
++rel:
++      return fmc_release(fmdev);
++}
++
++/* Set FM Modes(TX, RX, OFF) */
++u32 fmc_set_mode(struct fmdev *fmdev, u8 fm_mode)
++{
++      int ret = 0;
++
++      if (fm_mode >= FM_MODE_ENTRY_MAX) {
++              fmerr("Invalid FM mode\n");
++              return -EINVAL;
++      }
++      if (fmdev->curr_fmmode == fm_mode) {
++              fmdbg("Already fm is in mode(%d)\n", fm_mode);
++              return ret;
++      }
++
++      switch (fm_mode) {
++      case FM_MODE_OFF:       /* OFF Mode */
++              ret = fm_power_down(fmdev);
++              if (ret < 0) {
++                      fmerr("Failed to set OFF mode\n");
++                      return ret;
++              }
++              break;
++
++      case FM_MODE_TX:        /* TX Mode */
++      case FM_MODE_RX:        /* RX Mode */
++              /* Power down before switching to TX or RX mode */
++              if (fmdev->curr_fmmode != FM_MODE_OFF) {
++                      ret = fm_power_down(fmdev);
++                      if (ret < 0) {
++                              fmerr("Failed to set OFF mode\n");
++                              return ret;
++                      }
++                      msleep(30);
++              }
++              ret = fm_power_up(fmdev, fm_mode);
++              if (ret < 0) {
++                      fmerr("Failed to load firmware\n");
++                      return ret;
++              }
++      }
++      fmdev->curr_fmmode = fm_mode;
++
++      /* Set default configuration */
++      if (fmdev->curr_fmmode == FM_MODE_RX) {
++              fmdbg("Loading default rx configuration..\n");
++              ret = load_default_rx_configuration(fmdev);
++              if (ret < 0)
++                      fmerr("Failed to load default values\n");
++      }
++
++      return ret;
++}
++
++/* Returns current FM mode (TX, RX, OFF) */
++u32 fmc_get_mode(struct fmdev *fmdev, u8 *fmmode)
++{
++      if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
++              fmerr("FM core is not ready\n");
++              return -EPERM;
++      }
++      if (fmmode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *fmmode = fmdev->curr_fmmode;
++      return 0;
++}
++
++/* Called by ST layer when FM packet is available */
++static long fm_st_receive(void *arg, struct sk_buff *skb)
++{
++      struct fmdev *fmdev;
++
++      fmdev = (struct fmdev *)arg;
++
++      if (skb == NULL) {
++              fmerr("Invalid SKB received from ST\n");
++              return -EFAULT;
++      }
++
++      if (skb->cb[0] != FM_PKT_LOGICAL_CHAN_NUMBER) {
++              fmerr("Received SKB (%p) is not FM Channel 8 pkt\n", skb);
++              return -EINVAL;
++      }
++
++      memcpy(skb_push(skb, 1), &skb->cb[0], 1);
++      skb_queue_tail(&fmdev->rx_q, skb);
++      tasklet_schedule(&fmdev->rx_task);
++
++      return 0;
++}
++
++/*
++ * Called by ST layer to indicate protocol registration completion
++ * status.
++ */
++static void fm_st_reg_comp_cb(void *arg, char data)
++{
++      struct fmdev *fmdev;
++
++      fmdev = (struct fmdev *)arg;
++      fmdev->streg_cbdata = data;
++      complete(&wait_for_fmdrv_reg_comp);
++}
++
++/*
++ * This function will be called from FM V4L2 open function.
++ * Register with ST driver and initialize driver data.
++ */
++u32 fmc_prepare(struct fmdev *fmdev)
++{
++      static struct st_proto_s fm_st_proto;
++      u32 ret;
++
++      if (test_bit(FM_CORE_READY, &fmdev->flag)) {
++              fmdbg("FM Core is already up\n");
++              return 0;
++      }
++
++      memset(&fm_st_proto, 0, sizeof(fm_st_proto));
++      fm_st_proto.type = ST_FM;
++      fm_st_proto.recv = fm_st_receive;
++      fm_st_proto.match_packet = NULL;
++      fm_st_proto.reg_complete_cb = fm_st_reg_comp_cb;
++      fm_st_proto.write = NULL; /* TI ST driver will fill write pointer */
++      fm_st_proto.priv_data = fmdev;
++
++      ret = st_register(&fm_st_proto);
++      if (ret == -EINPROGRESS) {
++              init_completion(&wait_for_fmdrv_reg_comp);
++              fmdev->streg_cbdata = -EINPROGRESS;
++              fmdbg("%s waiting for ST reg completion signal\n", __func__);
++
++              ret = wait_for_completion_timeout(&wait_for_fmdrv_reg_comp,
++                              FM_ST_REG_TIMEOUT);
++
++              if (!ret) {
++                      fmerr("Timeout(%d sec), didn't get reg "
++                                      "completion signal from ST\n",
++                                      jiffies_to_msecs(FM_ST_REG_TIMEOUT) / 1000);
++                      return -ETIMEDOUT;
++              }
++              if (fmdev->streg_cbdata != 0) {
++                      fmerr("ST reg comp CB called with error "
++                                      "status %d\n", fmdev->streg_cbdata);
++                      return -EAGAIN;
++              }
++
++              ret = 0;
++      } else if (ret == -1) {
++              fmerr("st_register failed %d\n", ret);
++              return -EAGAIN;
++      }
++
++      if (fm_st_proto.write != NULL) {
++              g_st_write = fm_st_proto.write;
++      } else {
++              fmerr("Failed to get ST write func pointer\n");
++              ret = st_unregister(ST_FM);
++              if (ret < 0)
++                      fmerr("st_unregister failed %d\n", ret);
++              return -EAGAIN;
++      }
++
++      spin_lock_init(&fmdev->rds_buff_lock);
++      spin_lock_init(&fmdev->resp_skb_lock);
++
++      /* Initialize TX queue and TX tasklet */
++      skb_queue_head_init(&fmdev->tx_q);
++      tasklet_init(&fmdev->tx_task, send_tasklet, (unsigned long)fmdev);
++
++      /* Initialize RX Queue and RX tasklet */
++      skb_queue_head_init(&fmdev->rx_q);
++      tasklet_init(&fmdev->rx_task, recv_tasklet, (unsigned long)fmdev);
++
++      fmdev->irq_info.stage = 0;
++      atomic_set(&fmdev->tx_cnt, 1);
++      fmdev->resp_comp = NULL;
++
++      init_timer(&fmdev->irq_info.timer);
++      fmdev->irq_info.timer.function = &int_timeout_handler;
++      fmdev->irq_info.timer.data = (unsigned long)fmdev;
++      /*TODO: add FM_STIC_EVENT later */
++      fmdev->irq_info.mask = FM_MAL_EVENT;
++
++      /* Region info */
++      memcpy(&fmdev->rx.region, &region_configs[default_radio_region],
++                      sizeof(struct region_info));
++
++      fmdev->rx.mute_mode = FM_MUTE_OFF;
++      fmdev->rx.rf_depend_mute = FM_RX_RF_DEPENDENT_MUTE_OFF;
++      fmdev->rx.rds.flag = FM_RDS_DISABLE;
++      fmdev->rx.freq = FM_UNDEFINED_FREQ;
++      fmdev->rx.rds_mode = FM_RDS_SYSTEM_RDS;
++      fmdev->rx.af_mode = FM_RX_RDS_AF_SWITCH_MODE_OFF;
++      fmdev->irq_info.retry = 0;
++
++      fm_rx_reset_rds_cache(fmdev);
++      init_waitqueue_head(&fmdev->rx.rds.read_queue);
++
++      fm_rx_reset_station_info(fmdev);
++      set_bit(FM_CORE_READY, &fmdev->flag);
++
++      return ret;
++}
++
++/*
++ * This function will be called from FM V4L2 release function.
++ * Unregister from ST driver.
++ */
++u32 fmc_release(struct fmdev *fmdev)
++{
++      u32 ret;
++
++      if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
++              fmdbg("FM Core is already down\n");
++              return 0;
++      }
++      /* Sevice pending read */
++      wake_up_interruptible(&fmdev->rx.rds.read_queue);
++
++      tasklet_kill(&fmdev->tx_task);
++      tasklet_kill(&fmdev->rx_task);
++
++      skb_queue_purge(&fmdev->tx_q);
++      skb_queue_purge(&fmdev->rx_q);
++
++      fmdev->resp_comp = NULL;
++      fmdev->rx.freq = 0;
++
++      ret = st_unregister(ST_FM);
++      if (ret < 0)
++              fmerr("Failed to de-register FM from ST %d\n", ret);
++      else
++              fmdbg("Successfully unregistered from ST\n");
++
++      clear_bit(FM_CORE_READY, &fmdev->flag);
++      return ret;
++}
++
++/*
++ * Module init function. Ask FM V4L module to register video device.
++ * Allocate memory for FM driver context and RX RDS buffer.
++ */
++static int __init fm_drv_init(void)
++{
++      struct fmdev *fmdev = NULL;
++      u32 ret = -ENOMEM;
++
++      fmdbg("FM driver version %s\n", FM_DRV_VERSION);
++
++      fmdev = kzalloc(sizeof(struct fmdev), GFP_KERNEL);
++      if (NULL == fmdev) {
++              fmerr("Can't allocate operation structure memory\n");
++              return ret;
++      }
++      fmdev->rx.rds.buf_size = default_rds_buf * FM_RDS_BLK_SIZE;
++      fmdev->rx.rds.buff = kzalloc(fmdev->rx.rds.buf_size, GFP_KERNEL);
++      if (NULL == fmdev->rx.rds.buff) {
++              fmerr("Can't allocate rds ring buffer\n");
++              goto rel_dev;
++      }
++
++      ret = fm_v4l2_init_video_device(fmdev, radio_nr);
++      if (ret < 0)
++              goto rel_rdsbuf;
++
++      fmdev->irq_info.handlers = int_handler_table;
++      fmdev->curr_fmmode = FM_MODE_OFF;
++      fmdev->tx_data.pwr_lvl = FM_PWR_LVL_DEF;
++      fmdev->tx_data.preemph = FM_TX_PREEMPH_50US;
++      return ret;
++
++rel_rdsbuf:
++      kfree(fmdev->rx.rds.buff);
++rel_dev:
++      kfree(fmdev);
++
++      return ret;
++}
++
++/* Module exit function. Ask FM V4L module to unregister video device */
++static void __exit fm_drv_exit(void)
++{
++      struct fmdev *fmdev = NULL;
++
++      fmdev = fm_v4l2_deinit_video_device();
++      if (fmdev != NULL) {
++              kfree(fmdev->rx.rds.buff);
++              kfree(fmdev);
++      }
++}
++
++module_init(fm_drv_init);
++module_exit(fm_drv_exit);
++
++/* ------------- Module Info ------------- */
++MODULE_AUTHOR("Manjunatha Halli <manjunatha_halli@ti.com>");
++MODULE_DESCRIPTION("FM Driver for TI's Connectivity chip. " FM_DRV_VERSION);
++MODULE_VERSION(FM_DRV_VERSION);
++MODULE_LICENSE("GPL");
+diff --git a/drivers/media/radio/wl128x/fmdrv_common.h b/drivers/media/radio/wl128x/fmdrv_common.h
+new file mode 100644
+index 0000000..e5091f4
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_common.h
+@@ -0,0 +1,402 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  FM Common module header file
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#ifndef _FMDRV_COMMON_H
++#define _FMDRV_COMMON_H
++
++#define FM_ST_REG_TIMEOUT   msecs_to_jiffies(6000)    /* 6 sec */
++#define FM_PKT_LOGICAL_CHAN_NUMBER  0x08   /* Logical channel 8 */
++
++#define REG_RD       0x1
++#define REG_WR      0x0
++
++struct fm_reg_table {
++      u8 opcode;
++      u8 type;
++      u8 *name;
++};
++
++#define STEREO_GET               0
++#define RSSI_LVL_GET             1
++#define IF_COUNT_GET             2
++#define FLAG_GET                 3
++#define RDS_SYNC_GET             4
++#define RDS_DATA_GET             5
++#define FREQ_SET                 10
++#define AF_FREQ_SET              11
++#define MOST_MODE_SET            12
++#define MOST_BLEND_SET           13
++#define DEMPH_MODE_SET           14
++#define SEARCH_LVL_SET           15
++#define BAND_SET                 16
++#define MUTE_STATUS_SET          17
++#define RDS_PAUSE_LVL_SET        18
++#define RDS_PAUSE_DUR_SET        19
++#define RDS_MEM_SET              20
++#define RDS_BLK_B_SET            21
++#define RDS_MSK_B_SET            22
++#define RDS_PI_MASK_SET          23
++#define RDS_PI_SET               24
++#define RDS_SYSTEM_SET           25
++#define INT_MASK_SET             26
++#define SEARCH_DIR_SET           27
++#define VOLUME_SET               28
++#define AUDIO_ENABLE_SET         29
++#define PCM_MODE_SET             30
++#define I2S_MODE_CONFIG_SET      31
++#define POWER_SET                32
++#define INTX_CONFIG_SET          33
++#define PULL_EN_SET              34
++#define HILO_SET                 35
++#define SWITCH2FREF              36
++#define FREQ_DRIFT_REPORT        37
++
++#define PCE_GET                  40
++#define FIRM_VER_GET             41
++#define ASIC_VER_GET             42
++#define ASIC_ID_GET              43
++#define MAN_ID_GET               44
++#define TUNER_MODE_SET           45
++#define STOP_SEARCH              46
++#define RDS_CNTRL_SET            47
++
++#define WRITE_HARDWARE_REG       100
++#define CODE_DOWNLOAD            101
++#define RESET                    102
++
++#define FM_POWER_MODE            254
++#define FM_INTERRUPT             255
++
++/* Transmitter API */
++
++#define CHANL_SET                55
++#define CHANL_BW_SET          56
++#define REF_SET                  57
++#define POWER_ENB_SET            90
++#define POWER_ATT_SET            58
++#define POWER_LEV_SET            59
++#define AUDIO_DEV_SET            60
++#define PILOT_DEV_SET            61
++#define RDS_DEV_SET              62
++#define TX_BAND_SET              65
++#define PUPD_SET                 91
++#define AUDIO_IO_SET             63
++#define PREMPH_SET               64
++#define MONO_SET                 66
++#define MUTE                     92
++#define MPX_LMT_ENABLE           67
++#define PI_SET                   93
++#define ECC_SET                  69
++#define PTY                      70
++#define AF                       71
++#define DISPLAY_MODE             74
++#define RDS_REP_SET              77
++#define RDS_CONFIG_DATA_SET      98
++#define RDS_DATA_SET             99
++#define RDS_DATA_ENB             94
++#define TA_SET                   78
++#define TP_SET                   79
++#define DI_SET                   80
++#define MS_SET                   81
++#define PS_SCROLL_SPEED          82
++#define TX_AUDIO_LEVEL_TEST      96
++#define TX_AUDIO_LEVEL_TEST_THRESHOLD    73
++#define TX_AUDIO_INPUT_LEVEL_RANGE_SET   54
++#define RX_ANTENNA_SELECT        87
++#define I2C_DEV_ADDR_SET         86
++#define REF_ERR_CALIB_PARAM_SET          88
++#define REF_ERR_CALIB_PERIODICITY_SET    89
++#define SOC_INT_TRIGGER                  52
++#define SOC_AUDIO_PATH_SET               83
++#define SOC_PCMI_OVERRIDE                84
++#define SOC_I2S_OVERRIDE         85
++#define RSSI_BLOCK_SCAN_FREQ_SET 95
++#define RSSI_BLOCK_SCAN_START    97
++#define RSSI_BLOCK_SCAN_DATA_GET  5
++#define READ_FMANT_TUNE_VALUE            104
++
++/* SKB helpers */
++struct fm_skb_cb {
++      __u8 fm_op;
++      struct completion *completion;
++};
++
++#define fm_cb(skb) ((struct fm_skb_cb *)(skb->cb))
++
++/* FM Channel-8 command message format */
++struct fm_cmd_msg_hdr {
++      __u8 hdr;               /* Logical Channel-8 */
++      __u8 len;               /* Number of bytes follows */
++      __u8 op;                /* FM Opcode */
++      __u8 rd_wr;             /* Read/Write command */
++      __u8 dlen;              /* Length of payload */
++} __attribute__ ((packed));
++
++#define FM_CMD_MSG_HDR_SIZE    5      /* sizeof(struct fm_cmd_msg_hdr) */
++
++/* FM Channel-8 event messgage format */
++struct fm_event_msg_hdr {
++      __u8 header;            /* Logical Channel-8 */
++      __u8 len;               /* Number of bytes follows */
++      __u8 status;            /* Event status */
++      __u8 num_fm_hci_cmds;   /* Number of pkts the host allowed to send */
++      __u8 op;                /* FM Opcode */
++      __u8 rd_wr;             /* Read/Write command */
++      __u8 dlen;              /* Length of payload */
++} __attribute__ ((packed));
++
++#define FM_EVT_MSG_HDR_SIZE     7     /* sizeof(struct fm_event_msg_hdr) */
++
++/* TI's magic number in firmware file */
++#define FM_FW_FILE_HEADER_MAGIC            0x42535442
++
++#define FM_ENABLE   1
++#define FM_DISABLE  0
++
++/* FLAG_GET register bits */
++#define FM_FR_EVENT           (1 << 0)
++#define FM_BL_EVENT           (1 << 1)
++#define FM_RDS_EVENT          (1 << 2)
++#define FM_BBLK_EVENT         (1 << 3)
++#define FM_LSYNC_EVENT                (1 << 4)
++#define FM_LEV_EVENT          (1 << 5)
++#define FM_IFFR_EVENT         (1 << 6)
++#define FM_PI_EVENT           (1 << 7)
++#define FM_PD_EVENT           (1 << 8)
++#define FM_STIC_EVENT         (1 << 9)
++#define FM_MAL_EVENT          (1 << 10)
++#define FM_POW_ENB_EVENT      (1 << 11)
++
++/*
++ * Firmware files of FM. ASIC ID and ASIC version will be appened to this,
++ * later.
++ */
++#define FM_FMC_FW_FILE_START      ("fmc_ch8")
++#define FM_RX_FW_FILE_START       ("fm_rx_ch8")
++#define FM_TX_FW_FILE_START       ("fm_tx_ch8")
++
++#define FM_UNDEFINED_FREQ                0xFFFFFFFF
++
++/* Band types */
++#define FM_BAND_EUROPE_US     0
++#define FM_BAND_JAPAN         1
++
++/* Seek directions */
++#define FM_SEARCH_DIRECTION_DOWN      0
++#define FM_SEARCH_DIRECTION_UP                1
++
++/* Tunner modes */
++#define FM_TUNER_STOP_SEARCH_MODE     0
++#define FM_TUNER_PRESET_MODE          1
++#define FM_TUNER_AUTONOMOUS_SEARCH_MODE       2
++#define FM_TUNER_AF_JUMP_MODE         3
++
++/* Min and Max volume */
++#define FM_RX_VOLUME_MIN      0
++#define FM_RX_VOLUME_MAX      70
++
++/* Volume gain step */
++#define FM_RX_VOLUME_GAIN_STEP        0x370
++
++/* Mute modes */
++#define FM_MUTE_OFF           0
++#define       FM_MUTE_ON              1
++#define       FM_MUTE_ATTENUATE       2
++
++#define FM_RX_UNMUTE_MODE             0x00
++#define FM_RX_RF_DEP_MODE             0x01
++#define FM_RX_AC_MUTE_MODE            0x02
++#define FM_RX_HARD_MUTE_LEFT_MODE     0x04
++#define FM_RX_HARD_MUTE_RIGHT_MODE    0x08
++#define FM_RX_SOFT_MUTE_FORCE_MODE    0x10
++
++/* RF dependent mute mode */
++#define FM_RX_RF_DEPENDENT_MUTE_ON    1
++#define FM_RX_RF_DEPENDENT_MUTE_OFF   0
++
++/* RSSI threshold min and max */
++#define FM_RX_RSSI_THRESHOLD_MIN      -128
++#define FM_RX_RSSI_THRESHOLD_MAX      127
++
++/* Stereo/Mono mode */
++#define FM_STEREO_MODE                0
++#define FM_MONO_MODE          1
++#define FM_STEREO_SOFT_BLEND  1
++
++/* FM RX De-emphasis filter modes */
++#define FM_RX_EMPHASIS_FILTER_50_USEC 0
++#define FM_RX_EMPHASIS_FILTER_75_USEC 1
++
++/* FM RDS modes */
++#define FM_RDS_DISABLE        0
++#define FM_RDS_ENABLE 1
++
++#define FM_NO_PI_CODE 0
++
++/* FM and RX RDS block enable/disable  */
++#define FM_RX_PWR_SET_FM_ON_RDS_OFF           0x1
++#define FM_RX_PWR_SET_FM_AND_RDS_BLK_ON               0x3
++#define FM_RX_PWR_SET_FM_AND_RDS_BLK_OFF      0x0
++
++/* RX RDS */
++#define FM_RX_RDS_FLUSH_FIFO          0x1
++#define FM_RX_RDS_FIFO_THRESHOLD      64      /* tuples */
++#define FM_RDS_BLK_SIZE               3       /* 3 bytes */
++
++/* RDS block types */
++#define FM_RDS_BLOCK_A                0
++#define FM_RDS_BLOCK_B                1
++#define FM_RDS_BLOCK_C                2
++#define FM_RDS_BLOCK_Ctag     3
++#define FM_RDS_BLOCK_D                4
++#define FM_RDS_BLOCK_E                5
++
++#define FM_RDS_BLK_IDX_A              0
++#define FM_RDS_BLK_IDX_B              1
++#define FM_RDS_BLK_IDX_C              2
++#define FM_RDS_BLK_IDX_D              3
++#define FM_RDS_BLK_IDX_UNKNOWN        0xF0
++
++#define FM_RDS_STATUS_ERR_MASK        0x18
++
++/*
++ * Represents an RDS group type & version.
++ * There are 15 groups, each group has 2 versions: A and B.
++ */
++#define FM_RDS_GROUP_TYPE_MASK_0A         ((unsigned long)1<<0)
++#define FM_RDS_GROUP_TYPE_MASK_0B         ((unsigned long)1<<1)
++#define FM_RDS_GROUP_TYPE_MASK_1A         ((unsigned long)1<<2)
++#define FM_RDS_GROUP_TYPE_MASK_1B         ((unsigned long)1<<3)
++#define FM_RDS_GROUP_TYPE_MASK_2A         ((unsigned long)1<<4)
++#define FM_RDS_GROUP_TYPE_MASK_2B         ((unsigned long)1<<5)
++#define FM_RDS_GROUP_TYPE_MASK_3A         ((unsigned long)1<<6)
++#define FM_RDS_GROUP_TYPE_MASK_3B           ((unsigned long)1<<7)
++#define FM_RDS_GROUP_TYPE_MASK_4A         ((unsigned long)1<<8)
++#define FM_RDS_GROUP_TYPE_MASK_4B         ((unsigned long)1<<9)
++#define FM_RDS_GROUP_TYPE_MASK_5A         ((unsigned long)1<<10)
++#define FM_RDS_GROUP_TYPE_MASK_5B         ((unsigned long)1<<11)
++#define FM_RDS_GROUP_TYPE_MASK_6A         ((unsigned long)1<<12)
++#define FM_RDS_GROUP_TYPE_MASK_6B         ((unsigned long)1<<13)
++#define FM_RDS_GROUP_TYPE_MASK_7A         ((unsigned long)1<<14)
++#define FM_RDS_GROUP_TYPE_MASK_7B         ((unsigned long)1<<15)
++#define FM_RDS_GROUP_TYPE_MASK_8A           ((unsigned long)1<<16)
++#define FM_RDS_GROUP_TYPE_MASK_8B         ((unsigned long)1<<17)
++#define FM_RDS_GROUP_TYPE_MASK_9A         ((unsigned long)1<<18)
++#define FM_RDS_GROUP_TYPE_MASK_9B         ((unsigned long)1<<19)
++#define FM_RDS_GROUP_TYPE_MASK_10A        ((unsigned long)1<<20)
++#define FM_RDS_GROUP_TYPE_MASK_10B        ((unsigned long)1<<21)
++#define FM_RDS_GROUP_TYPE_MASK_11A        ((unsigned long)1<<22)
++#define FM_RDS_GROUP_TYPE_MASK_11B        ((unsigned long)1<<23)
++#define FM_RDS_GROUP_TYPE_MASK_12A        ((unsigned long)1<<24)
++#define FM_RDS_GROUP_TYPE_MASK_12B        ((unsigned long)1<<25)
++#define FM_RDS_GROUP_TYPE_MASK_13A        ((unsigned long)1<<26)
++#define FM_RDS_GROUP_TYPE_MASK_13B        ((unsigned long)1<<27)
++#define FM_RDS_GROUP_TYPE_MASK_14A        ((unsigned long)1<<28)
++#define FM_RDS_GROUP_TYPE_MASK_14B        ((unsigned long)1<<29)
++#define FM_RDS_GROUP_TYPE_MASK_15A        ((unsigned long)1<<30)
++#define FM_RDS_GROUP_TYPE_MASK_15B        ((unsigned long)1<<31)
++
++/* RX Alternate Frequency info */
++#define FM_RDS_MIN_AF                   1
++#define FM_RDS_MAX_AF                 204
++#define FM_RDS_MAX_AF_JAPAN           140
++#define FM_RDS_1_AF_FOLLOWS           225
++#define FM_RDS_25_AF_FOLLOWS          249
++
++/* RDS system type (RDS/RBDS) */
++#define FM_RDS_SYSTEM_RDS             0
++#define FM_RDS_SYSTEM_RBDS            1
++
++/* AF on/off */
++#define FM_RX_RDS_AF_SWITCH_MODE_ON   1
++#define FM_RX_RDS_AF_SWITCH_MODE_OFF  0
++
++/* Retry count when interrupt process goes wrong */
++#define FM_IRQ_TIMEOUT_RETRY_MAX      5       /* 5 times */
++
++/* Audio IO set values */
++#define FM_RX_AUDIO_ENABLE_I2S        0x01
++#define FM_RX_AUDIO_ENABLE_ANALOG     0x02
++#define FM_RX_AUDIO_ENABLE_I2S_AND_ANALOG     0x03
++#define FM_RX_AUDIO_ENABLE_DISABLE    0x00
++
++/* HI/LO set values */
++#define FM_RX_IFFREQ_TO_HI_SIDE               0x0
++#define FM_RX_IFFREQ_TO_LO_SIDE               0x1
++#define FM_RX_IFFREQ_HILO_AUTOMATIC   0x2
++
++/*
++ * Default RX mode configuration. Chip will be configured
++ * with this default values after loading RX firmware.
++ */
++#define FM_DEFAULT_RX_VOLUME          10
++#define FM_DEFAULT_RSSI_THRESHOLD     3
++
++/* Range for TX power level in units for dB/uV */
++#define FM_PWR_LVL_LOW                        91
++#define FM_PWR_LVL_HIGH                       122
++
++/* Chip specific default TX power level value */
++#define FM_PWR_LVL_DEF                        4
++
++/* FM TX Pre-emphasis filter values */
++#define FM_TX_PREEMPH_OFF             1
++#define FM_TX_PREEMPH_50US            0
++#define FM_TX_PREEMPH_75US            2
++
++/* FM TX antenna impedence values */
++#define FM_TX_ANT_IMP_50              0
++#define FM_TX_ANT_IMP_200             1
++#define FM_TX_ANT_IMP_500             2
++
++/* Functions exported by FM common sub-module */
++u32 fmc_prepare(struct fmdev *);
++u32 fmc_release(struct fmdev *);
++
++void fmc_update_region_info(struct fmdev *, u8);
++u32 fmc_send_cmd(struct fmdev *, u8, u16,
++                              void *, unsigned int, void *, int *);
++u32 fmc_is_rds_data_available(struct fmdev *, struct file *,
++                              struct poll_table_struct *);
++u32 fmc_transfer_rds_from_internal_buff(struct fmdev *, struct file *,
++                                      u8 __user *, size_t);
++
++u32 fmc_set_freq(struct fmdev *, u32);
++u32 fmc_set_mode(struct fmdev *, u8);
++u32 fmc_set_region(struct fmdev *, u8);
++u32 fmc_set_mute_mode(struct fmdev *, u8);
++u32 fmc_set_stereo_mono(struct fmdev *, u16);
++u32 fmc_set_rds_mode(struct fmdev *, u8);
++
++u32 fmc_get_freq(struct fmdev *, u32 *);
++u32 fmc_get_region(struct fmdev *, u8 *);
++u32 fmc_get_mode(struct fmdev *, u8 *);
++
++/*
++ * channel spacing
++ */
++#define FM_CHANNEL_SPACING_50KHZ 1
++#define FM_CHANNEL_SPACING_100KHZ 2
++#define FM_CHANNEL_SPACING_200KHZ 4
++#define FM_FREQ_MUL 50
++
++#endif
++
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0010-drivers-media-radio-wl128x-FM-driver-RX-sources.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0010-drivers-media-radio-wl128x-FM-driver-RX-sources.patch
new file mode 100644 (file)
index 0000000..8899a31
--- /dev/null
@@ -0,0 +1,938 @@
+From ba32e1ae2a43f33dcfd459c1456d4e612da885db Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:24 +0000
+Subject: [PATCH 10/15] drivers:media:radio: wl128x: FM driver RX sources
+
+This has implementation for FM RX functionality.
+It communicates with FM V4l2 module and FM common module
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/fmdrv_rx.c |  847 +++++++++++++++++++++++++++++++++
+ drivers/media/radio/wl128x/fmdrv_rx.h |   59 +++
+ 2 files changed, 906 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_rx.c
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_rx.h
+
+diff --git a/drivers/media/radio/wl128x/fmdrv_rx.c b/drivers/media/radio/wl128x/fmdrv_rx.c
+new file mode 100644
+index 0000000..ec529b5
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_rx.c
+@@ -0,0 +1,847 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  This sub-module of FM driver implements FM RX functionality.
++ *
++ *  Copyright (C) 2011 Texas Instruments
++ *  Author: Raja Mani <raja_mani@ti.com>
++ *  Author: Manjunatha Halli <manjunatha_halli@ti.com>
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#include "fmdrv.h"
++#include "fmdrv_common.h"
++#include "fmdrv_rx.h"
++
++void fm_rx_reset_rds_cache(struct fmdev *fmdev)
++{
++      fmdev->rx.rds.flag = FM_RDS_DISABLE;
++      fmdev->rx.rds.last_blk_idx = 0;
++      fmdev->rx.rds.wr_idx = 0;
++      fmdev->rx.rds.rd_idx = 0;
++
++      if (fmdev->rx.af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
++              fmdev->irq_info.mask |= FM_LEV_EVENT;
++}
++
++void fm_rx_reset_station_info(struct fmdev *fmdev)
++{
++      fmdev->rx.stat_info.picode = FM_NO_PI_CODE;
++      fmdev->rx.stat_info.afcache_size = 0;
++      fmdev->rx.stat_info.af_list_max = 0;
++}
++
++u32 fm_rx_set_freq(struct fmdev *fmdev, u32 freq)
++{
++      unsigned long timeleft;
++      u16 payload, curr_frq, intr_flag;
++      u32 curr_frq_in_khz;
++      u32 ret, resp_len;
++
++      if (freq < fmdev->rx.region.bot_freq || freq > fmdev->rx.region.top_freq) {
++              fmerr("Invalid frequency %d\n", freq);
++              return -EINVAL;
++      }
++
++      /* Set audio enable */
++      payload = FM_RX_AUDIO_ENABLE_I2S_AND_ANALOG;
++
++      ret = fmc_send_cmd(fmdev, AUDIO_ENABLE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Set hilo to automatic selection */
++      payload = FM_RX_IFFREQ_HILO_AUTOMATIC;
++      ret = fmc_send_cmd(fmdev, HILO_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Calculate frequency index and set*/
++      payload = (freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
++
++      ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Read flags - just to clear any pending interrupts if we had */
++      ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Enable FR, BL interrupts */
++      intr_flag = fmdev->irq_info.mask;
++      fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT);
++      payload = fmdev->irq_info.mask;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Start tune */
++      payload = FM_TUNER_PRESET_MODE;
++      ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              goto exit;
++
++      /* Wait for tune ended interrupt */
++      init_completion(&fmdev->maintask_comp);
++      timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
++                      FM_DRV_TX_TIMEOUT);
++      if (!timeleft) {
++              fmerr("Timeout(%d sec),didn't get tune ended int\n",
++                         jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
++              ret = -ETIMEDOUT;
++              goto exit;
++      }
++
++      /* Read freq back to confirm */
++      ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2, &curr_frq, &resp_len);
++      if (ret < 0)
++              goto exit;
++
++      curr_frq = be16_to_cpu(curr_frq);
++      curr_frq_in_khz = (fmdev->rx.region.bot_freq + ((u32)curr_frq * FM_FREQ_MUL));
++
++      if (curr_frq_in_khz != freq) {
++              pr_info("Frequency is set to (%d) but "
++                         "requested freq is (%d)\n", curr_frq_in_khz, freq);
++      }
++
++      /* Update local cache  */
++      fmdev->rx.freq = curr_frq_in_khz;
++exit:
++      /* Re-enable default FM interrupts */
++      fmdev->irq_info.mask = intr_flag;
++      payload = fmdev->irq_info.mask;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Reset RDS cache and current station pointers */
++      fm_rx_reset_rds_cache(fmdev);
++      fm_rx_reset_station_info(fmdev);
++
++      return ret;
++}
++
++static u32 fm_rx_set_channel_spacing(struct fmdev *fmdev, u32 spacing)
++{
++      u16 payload;
++      u32 ret;
++
++      if (spacing > 0 && spacing <= 50000)
++              spacing = FM_CHANNEL_SPACING_50KHZ;
++      else if (spacing > 50000 && spacing <= 100000)
++              spacing = FM_CHANNEL_SPACING_100KHZ;
++      else
++              spacing = FM_CHANNEL_SPACING_200KHZ;
++
++      /* set channel spacing */
++      payload = spacing;
++      ret = fmc_send_cmd(fmdev, CHANL_BW_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.region.chanl_space = spacing * FM_FREQ_MUL;
++
++      return ret;
++}
++
++u32 fm_rx_seek(struct fmdev *fmdev, u32 seek_upward,
++              u32 wrap_around, u32 spacing)
++{
++      u32 resp_len;
++      u16 curr_frq, next_frq, last_frq;
++      u16 payload, int_reason, intr_flag;
++      u16 offset, space_idx;
++      unsigned long timeleft;
++      u32 ret;
++
++      /* Set channel spacing */
++      ret = fm_rx_set_channel_spacing(fmdev, spacing);
++      if (ret < 0) {
++              fmerr("Failed to set channel spacing\n");
++              return ret;
++      }
++
++      /* Read the current frequency from chip */
++      ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL,
++                      sizeof(curr_frq), &curr_frq, &resp_len);
++      if (ret < 0)
++              return ret;
++
++      curr_frq = be16_to_cpu(curr_frq);
++      last_frq = (fmdev->rx.region.top_freq - fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
++
++      /* Check the offset in order to be aligned to the channel spacing*/
++      space_idx = fmdev->rx.region.chanl_space / FM_FREQ_MUL;
++      offset = curr_frq % space_idx;
++
++      next_frq = seek_upward ? curr_frq + space_idx /* Seek Up */ :
++                              curr_frq - space_idx /* Seek Down */ ;
++
++      /*
++       * Add or subtract offset in order to stay aligned to the channel
++       * spacing.
++       */
++      if ((short)next_frq < 0)
++              next_frq = last_frq - offset;
++      else if (next_frq > last_frq)
++              next_frq = 0 + offset;
++
++again:
++      /* Set calculated next frequency to perform seek */
++      payload = next_frq;
++      ret = fmc_send_cmd(fmdev, FREQ_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Set search direction (0:Seek Down, 1:Seek Up) */
++      payload = (seek_upward ? FM_SEARCH_DIRECTION_UP : FM_SEARCH_DIRECTION_DOWN);
++      ret = fmc_send_cmd(fmdev, SEARCH_DIR_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Read flags - just to clear any pending interrupts if we had */
++      ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2, NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Enable FR, BL interrupts */
++      intr_flag = fmdev->irq_info.mask;
++      fmdev->irq_info.mask = (FM_FR_EVENT | FM_BL_EVENT);
++      payload = fmdev->irq_info.mask;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Start seek */
++      payload = FM_TUNER_AUTONOMOUS_SEARCH_MODE;
++      ret = fmc_send_cmd(fmdev, TUNER_MODE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Wait for tune ended/band limit reached interrupt */
++      init_completion(&fmdev->maintask_comp);
++      timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
++                      FM_DRV_RX_SEEK_TIMEOUT);
++      if (!timeleft) {
++              fmerr("Timeout(%d sec),didn't get tune ended int\n",
++                         jiffies_to_msecs(FM_DRV_RX_SEEK_TIMEOUT) / 1000);
++              return -ETIMEDOUT;
++      }
++
++      int_reason = fmdev->irq_info.flag & (FM_TUNE_COMPLETE | FM_BAND_LIMIT);
++
++      /* Re-enable default FM interrupts */
++      fmdev->irq_info.mask = intr_flag;
++      payload = fmdev->irq_info.mask;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      if (int_reason & FM_BL_EVENT) {
++              if (wrap_around == 0) {
++                      fmdev->rx.freq = seek_upward ?
++                              fmdev->rx.region.top_freq :
++                              fmdev->rx.region.bot_freq;
++              } else {
++                      fmdev->rx.freq = seek_upward ?
++                              fmdev->rx.region.bot_freq :
++                              fmdev->rx.region.top_freq;
++                      /* Calculate frequency index to write */
++                      next_frq = (fmdev->rx.freq -
++                                      fmdev->rx.region.bot_freq) / FM_FREQ_MUL;
++                      goto again;
++              }
++      } else {
++              /* Read freq to know where operation tune operation stopped */
++              ret = fmc_send_cmd(fmdev, FREQ_SET, REG_RD, NULL, 2,
++                              &curr_frq, &resp_len);
++              if (ret < 0)
++                      return ret;
++
++              curr_frq = be16_to_cpu(curr_frq);
++              fmdev->rx.freq = (fmdev->rx.region.bot_freq +
++                              ((u32)curr_frq * FM_FREQ_MUL));
++
++      }
++      /* Reset RDS cache and current station pointers */
++      fm_rx_reset_rds_cache(fmdev);
++      fm_rx_reset_station_info(fmdev);
++
++      return ret;
++}
++
++u32 fm_rx_set_volume(struct fmdev *fmdev, u16 vol_to_set)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (vol_to_set < FM_RX_VOLUME_MIN || vol_to_set > FM_RX_VOLUME_MAX) {
++              fmerr("Volume is not within(%d-%d) range\n",
++                         FM_RX_VOLUME_MIN, FM_RX_VOLUME_MAX);
++              return -EINVAL;
++      }
++      vol_to_set *= FM_RX_VOLUME_GAIN_STEP;
++
++      payload = vol_to_set;
++      ret = fmc_send_cmd(fmdev, VOLUME_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.volume = vol_to_set;
++      return ret;
++}
++
++/* Get volume */
++u32 fm_rx_get_volume(struct fmdev *fmdev, u16 *curr_vol)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_vol == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_vol = fmdev->rx.volume / FM_RX_VOLUME_GAIN_STEP;
++
++      return 0;
++}
++
++/* To get current band's bottom and top frequency */
++u32 fm_rx_get_band_freq_range(struct fmdev *fmdev, u32 *bot_freq, u32 *top_freq)
++{
++      if (bot_freq != NULL)
++              *bot_freq = fmdev->rx.region.bot_freq;
++
++      if (top_freq != NULL)
++              *top_freq = fmdev->rx.region.top_freq;
++
++      return 0;
++}
++
++/* Returns current band index (0-Europe/US; 1-Japan) */
++void fm_rx_get_region(struct fmdev *fmdev, u8 *region)
++{
++      *region = fmdev->rx.region.fm_band;
++}
++
++/* Sets band (0-Europe/US; 1-Japan) */
++u32 fm_rx_set_region(struct fmdev *fmdev, u8 region_to_set)
++{
++      u16 payload;
++      u32 new_frq = 0;
++      u32 ret;
++
++      if (region_to_set != FM_BAND_EUROPE_US &&
++          region_to_set != FM_BAND_JAPAN) {
++              fmerr("Invalid band\n");
++              return -EINVAL;
++      }
++
++      if (fmdev->rx.region.fm_band == region_to_set) {
++              fmerr("Requested band is already configured\n");
++              return 0;
++      }
++
++      /* Send cmd to set the band  */
++      payload = (u16)region_to_set;
++      ret = fmc_send_cmd(fmdev, BAND_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmc_update_region_info(fmdev, region_to_set);
++
++      /* Check whether current RX frequency is within band boundary */
++      if (fmdev->rx.freq < fmdev->rx.region.bot_freq)
++              new_frq = fmdev->rx.region.bot_freq;
++      else if (fmdev->rx.freq > fmdev->rx.region.top_freq)
++              new_frq = fmdev->rx.region.top_freq;
++
++      if (new_frq) {
++              fmdbg("Current freq is not within band limit boundary,"
++                              "switching to %d KHz\n", new_frq);
++               /* Current RX frequency is not in range. So, update it */
++              ret = fm_rx_set_freq(fmdev, new_frq);
++      }
++
++      return ret;
++}
++
++/* Reads current mute mode (Mute Off/On/Attenuate)*/
++u32 fm_rx_get_mute_mode(struct fmdev *fmdev, u8 *curr_mute_mode)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_mute_mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_mute_mode = fmdev->rx.mute_mode;
++
++      return 0;
++}
++
++static u32 fm_config_rx_mute_reg(struct fmdev *fmdev)
++{
++      u16 payload, muteval;
++      u32 ret;
++
++      muteval = 0;
++      switch (fmdev->rx.mute_mode) {
++      case FM_MUTE_ON:
++              muteval = FM_RX_AC_MUTE_MODE;
++              break;
++
++      case FM_MUTE_OFF:
++              muteval = FM_RX_UNMUTE_MODE;
++              break;
++
++      case FM_MUTE_ATTENUATE:
++              muteval = FM_RX_SOFT_MUTE_FORCE_MODE;
++              break;
++      }
++      if (fmdev->rx.rf_depend_mute == FM_RX_RF_DEPENDENT_MUTE_ON)
++              muteval |= FM_RX_RF_DEP_MODE;
++      else
++              muteval &= ~FM_RX_RF_DEP_MODE;
++
++      payload = muteval;
++      ret = fmc_send_cmd(fmdev, MUTE_STATUS_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++/* Configures mute mode (Mute Off/On/Attenuate) */
++u32 fm_rx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)
++{
++      u8 org_state;
++      u32 ret;
++
++      if (fmdev->rx.mute_mode == mute_mode_toset)
++              return 0;
++
++      org_state = fmdev->rx.mute_mode;
++      fmdev->rx.mute_mode = mute_mode_toset;
++
++      ret = fm_config_rx_mute_reg(fmdev);
++      if (ret < 0) {
++              fmdev->rx.mute_mode = org_state;
++              return ret;
++      }
++
++      return 0;
++}
++
++/* Gets RF dependent soft mute mode enable/disable status */
++u32 fm_rx_get_rfdepend_softmute(struct fmdev *fmdev, u8 *curr_mute_mode)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_mute_mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_mute_mode = fmdev->rx.rf_depend_mute;
++
++      return 0;
++}
++
++/* Sets RF dependent soft mute mode */
++u32 fm_rx_set_rfdepend_softmute(struct fmdev *fmdev, u8 rfdepend_mute)
++{
++      u8 org_state;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_ON &&
++          rfdepend_mute != FM_RX_RF_DEPENDENT_MUTE_OFF) {
++              fmerr("Invalid RF dependent soft mute\n");
++              return -EINVAL;
++      }
++      if (fmdev->rx.rf_depend_mute == rfdepend_mute)
++              return 0;
++
++      org_state = fmdev->rx.rf_depend_mute;
++      fmdev->rx.rf_depend_mute = rfdepend_mute;
++
++      ret = fm_config_rx_mute_reg(fmdev);
++      if (ret < 0) {
++              fmdev->rx.rf_depend_mute = org_state;
++              return ret;
++      }
++
++      return 0;
++}
++
++/* Returns the signal strength level of current channel */
++u32 fm_rx_get_rssi_level(struct fmdev *fmdev, u16 *rssilvl)
++{
++      u16 curr_rssi_lel;
++      u32 resp_len;
++      u32 ret;
++
++      if (rssilvl == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++      /* Read current RSSI level */
++      ret = fmc_send_cmd(fmdev, RSSI_LVL_GET, REG_RD, NULL, 2,
++                      &curr_rssi_lel, &resp_len);
++      if (ret < 0)
++              return ret;
++
++      *rssilvl = be16_to_cpu(curr_rssi_lel);
++
++      return 0;
++}
++
++/*
++ * Sets the signal strength level that once reached
++ * will stop the auto search process
++ */
++u32 fm_rx_set_rssi_threshold(struct fmdev *fmdev, short rssi_lvl_toset)
++{
++      u16 payload;
++      u32 ret;
++
++      if (rssi_lvl_toset < FM_RX_RSSI_THRESHOLD_MIN ||
++                      rssi_lvl_toset > FM_RX_RSSI_THRESHOLD_MAX) {
++              fmerr("Invalid RSSI threshold level\n");
++              return -EINVAL;
++      }
++      payload = (u16)rssi_lvl_toset;
++      ret = fmc_send_cmd(fmdev, SEARCH_LVL_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.rssi_threshold = rssi_lvl_toset;
++
++      return 0;
++}
++
++/* Returns current RX RSSI threshold value */
++u32 fm_rx_get_rssi_threshold(struct fmdev *fmdev, short *curr_rssi_lvl)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_rssi_lvl == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_rssi_lvl = fmdev->rx.rssi_threshold;
++
++      return 0;
++}
++
++/* Sets RX stereo/mono modes */
++u32 fm_rx_set_stereo_mono(struct fmdev *fmdev, u16 mode)
++{
++      u16 payload;
++      u32 ret;
++
++      if (mode != FM_STEREO_MODE && mode != FM_MONO_MODE) {
++              fmerr("Invalid mode\n");
++              return -EINVAL;
++      }
++
++      /* Set stereo/mono mode */
++      payload = (u16)mode;
++      ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Set stereo blending mode */
++      payload = FM_STEREO_SOFT_BLEND;
++      ret = fmc_send_cmd(fmdev, MOST_BLEND_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++/* Gets current RX stereo/mono mode */
++u32 fm_rx_get_stereo_mono(struct fmdev *fmdev, u16 *mode)
++{
++      u16 curr_mode;
++      u32 ret, resp_len;
++
++      if (mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      ret = fmc_send_cmd(fmdev, MOST_MODE_SET, REG_RD, NULL, 2,
++                      &curr_mode, &resp_len);
++      if (ret < 0)
++              return ret;
++
++      *mode = be16_to_cpu(curr_mode);
++
++      return 0;
++}
++
++/* Choose RX de-emphasis filter mode (50us/75us) */
++u32 fm_rx_set_deemphasis_mode(struct fmdev *fmdev, u16 mode)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (mode != FM_RX_EMPHASIS_FILTER_50_USEC &&
++                      mode != FM_RX_EMPHASIS_FILTER_75_USEC) {
++              fmerr("Invalid rx de-emphasis mode (%d)\n", mode);
++              return -EINVAL;
++      }
++
++      payload = mode;
++      ret = fmc_send_cmd(fmdev, DEMPH_MODE_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.deemphasis_mode = mode;
++
++      return 0;
++}
++
++/* Gets current RX de-emphasis filter mode */
++u32 fm_rx_get_deemph_mode(struct fmdev *fmdev, u16 *curr_deemphasis_mode)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_deemphasis_mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_deemphasis_mode = fmdev->rx.deemphasis_mode;
++
++      return 0;
++}
++
++/* Enable/Disable RX RDS */
++u32 fm_rx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)
++{
++      u16 payload;
++      u32 ret;
++
++      if (rds_en_dis != FM_RDS_ENABLE && rds_en_dis != FM_RDS_DISABLE) {
++              fmerr("Invalid rds option\n");
++              return -EINVAL;
++      }
++
++      if (rds_en_dis == FM_RDS_ENABLE
++          && fmdev->rx.rds.flag == FM_RDS_DISABLE) {
++              /* Turn on RX RDS and RDS circuit */
++              payload = FM_RX_PWR_SET_FM_AND_RDS_BLK_ON;
++              ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload,
++                              sizeof(payload), NULL, NULL);
++              if (ret < 0)
++                      return ret;
++
++              /* Clear and reset RDS FIFO */
++              payload = FM_RX_RDS_FLUSH_FIFO;
++              ret = fmc_send_cmd(fmdev, RDS_CNTRL_SET, REG_WR, &payload,
++              sizeof(payload), NULL, NULL);
++              if (ret < 0)
++                      return ret;
++
++              /* Read flags - just to clear any pending interrupts. */
++              ret = fmc_send_cmd(fmdev, FLAG_GET, REG_RD, NULL, 2,
++                              NULL, NULL);
++              if (ret < 0)
++                      return ret;
++
++              /* Set RDS FIFO threshold value */
++              payload = FM_RX_RDS_FIFO_THRESHOLD;
++              ret = fmc_send_cmd(fmdev, RDS_MEM_SET, REG_WR, &payload,
++              sizeof(payload), NULL, NULL);
++              if (ret < 0)
++                      return ret;
++
++              /* Enable RDS interrupt */
++              fmdev->irq_info.mask |= FM_RDS_EVENT;
++              payload = fmdev->irq_info.mask;
++              ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                              sizeof(payload), NULL, NULL);
++              if (ret < 0) {
++                      fmdev->irq_info.mask &= ~FM_RDS_EVENT;
++                      return ret;
++              }
++
++              /* Update our local flag */
++              fmdev->rx.rds.flag = FM_RDS_ENABLE;
++      } else if (rds_en_dis == FM_RDS_DISABLE
++                 && fmdev->rx.rds.flag == FM_RDS_ENABLE) {
++              /* Turn off RX RDS */
++              payload = FM_RX_PWR_SET_FM_ON_RDS_OFF;
++              ret = fmc_send_cmd(fmdev, POWER_SET, REG_WR, &payload,
++                              sizeof(payload), NULL, NULL);
++              if (ret < 0)
++                      return ret;
++
++              /* Reset RDS pointers */
++              fmdev->rx.rds.last_blk_idx = 0;
++              fmdev->rx.rds.wr_idx = 0;
++              fmdev->rx.rds.rd_idx = 0;
++              fm_rx_reset_station_info(fmdev);
++
++              /* Update RDS local cache */
++              fmdev->irq_info.mask &= ~(FM_RDS_EVENT);
++              fmdev->rx.rds.flag = FM_RDS_DISABLE;
++      }
++
++      return 0;
++}
++
++/* Returns current RX RDS enable/disable status */
++u32 fm_rx_get_rds_mode(struct fmdev *fmdev, u8 *curr_rds_en_dis)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (curr_rds_en_dis == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *curr_rds_en_dis = fmdev->rx.rds.flag;
++
++      return 0;
++}
++
++/* Sets RDS operation mode (RDS/RDBS) */
++u32 fm_rx_set_rds_system(struct fmdev *fmdev, u8 rds_mode)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (rds_mode != FM_RDS_SYSTEM_RDS && rds_mode != FM_RDS_SYSTEM_RBDS) {
++              fmerr("Invalid rds mode\n");
++              return -EINVAL;
++      }
++      /* Set RDS operation mode */
++      payload = (u16)rds_mode;
++      ret = fmc_send_cmd(fmdev, RDS_SYSTEM_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.rds_mode = rds_mode;
++
++      return 0;
++}
++
++/* Returns current RDS operation mode */
++u32 fm_rx_get_rds_system(struct fmdev *fmdev, u8 *rds_mode)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (rds_mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *rds_mode = fmdev->rx.rds_mode;
++
++      return 0;
++}
++
++/* Configures Alternate Frequency switch mode */
++u32 fm_rx_set_af_switch(struct fmdev *fmdev, u8 af_mode)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (af_mode != FM_RX_RDS_AF_SWITCH_MODE_ON &&
++          af_mode != FM_RX_RDS_AF_SWITCH_MODE_OFF) {
++              fmerr("Invalid af mode\n");
++              return -EINVAL;
++      }
++      /* Enable/disable low RSSI interrupt based on af_mode */
++      if (af_mode == FM_RX_RDS_AF_SWITCH_MODE_ON)
++              fmdev->irq_info.mask |= FM_LEV_EVENT;
++      else
++              fmdev->irq_info.mask &= ~FM_LEV_EVENT;
++
++      payload = fmdev->irq_info.mask;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->rx.af_mode = af_mode;
++
++      return 0;
++}
++
++/* Returns Alternate Frequency switch status */
++u32 fm_rx_get_af_switch(struct fmdev *fmdev, u8 *af_mode)
++{
++      if (fmdev->curr_fmmode != FM_MODE_RX)
++              return -EPERM;
++
++      if (af_mode == NULL) {
++              fmerr("Invalid memory\n");
++              return -ENOMEM;
++      }
++
++      *af_mode = fmdev->rx.af_mode;
++
++      return 0;
++}
+diff --git a/drivers/media/radio/wl128x/fmdrv_rx.h b/drivers/media/radio/wl128x/fmdrv_rx.h
+new file mode 100644
+index 0000000..329e62f
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_rx.h
+@@ -0,0 +1,59 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  FM RX module header.
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#ifndef _FMDRV_RX_H
++#define _FMDRV_RX_H
++
++u32 fm_rx_set_freq(struct fmdev *, u32);
++u32 fm_rx_set_mute_mode(struct fmdev *, u8);
++u32 fm_rx_set_stereo_mono(struct fmdev *, u16);
++u32 fm_rx_set_rds_mode(struct fmdev *, u8);
++u32 fm_rx_set_rds_system(struct fmdev *, u8);
++u32 fm_rx_set_volume(struct fmdev *, u16);
++u32 fm_rx_set_rssi_threshold(struct fmdev *, short);
++u32 fm_rx_set_region(struct fmdev *, u8);
++u32 fm_rx_set_rfdepend_softmute(struct fmdev *, u8);
++u32 fm_rx_set_deemphasis_mode(struct fmdev *, u16);
++u32 fm_rx_set_af_switch(struct fmdev *, u8);
++
++void fm_rx_reset_rds_cache(struct fmdev *);
++void fm_rx_reset_station_info(struct fmdev *);
++
++u32 fm_rx_seek(struct fmdev *, u32, u32, u32);
++
++u32 fm_rx_get_rds_mode(struct fmdev *, u8 *);
++u32 fm_rx_get_rds_system(struct fmdev *, u8 *);
++u32 fm_rx_get_mute_mode(struct fmdev *, u8 *);
++u32 fm_rx_get_volume(struct fmdev *, u16 *);
++u32 fm_rx_get_band_freq_range(struct fmdev *,
++                                      u32 *, u32 *);
++u32 fm_rx_get_stereo_mono(struct fmdev *, u16 *);
++u32 fm_rx_get_rssi_level(struct fmdev *, u16 *);
++u32 fm_rx_get_rssi_threshold(struct fmdev *, short *);
++u32 fm_rx_get_rfdepend_softmute(struct fmdev *, u8 *);
++u32 fm_rx_get_deemph_mode(struct fmdev *, u16 *);
++u32 fm_rx_get_af_switch(struct fmdev *, u8 *);
++void fm_rx_get_region(struct fmdev *, u8 *);
++
++u32 fm_rx_set_chanl_spacing(struct fmdev *, u8);
++u32 fm_rx_get_chanl_spacing(struct fmdev *, u8 *);
++#endif
++
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0011-drivers-media-radio-wl128x-FM-driver-TX-sources.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0011-drivers-media-radio-wl128x-FM-driver-TX-sources.patch
new file mode 100644 (file)
index 0000000..52b409f
--- /dev/null
@@ -0,0 +1,494 @@
+From 87d399bad67bdff67c1601fbb8e54deb5e0cf7e0 Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:25 +0000
+Subject: [PATCH 11/15] drivers:media:radio: wl128x: FM driver TX sources
+
+This has implementation for FM TX functionality.
+It communicates with FM V4l2 module and FM common module.
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/fmdrv_tx.c |  425 +++++++++++++++++++++++++++++++++
+ drivers/media/radio/wl128x/fmdrv_tx.h |   37 +++
+ 2 files changed, 462 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_tx.c
+ create mode 100644 drivers/media/radio/wl128x/fmdrv_tx.h
+
+diff --git a/drivers/media/radio/wl128x/fmdrv_tx.c b/drivers/media/radio/wl128x/fmdrv_tx.c
+new file mode 100644
+index 0000000..be54068
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_tx.c
+@@ -0,0 +1,425 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  This sub-module of FM driver implements FM TX functionality.
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#include <linux/delay.h>
++#include "fmdrv.h"
++#include "fmdrv_common.h"
++#include "fmdrv_tx.h"
++
++u32 fm_tx_set_stereo_mono(struct fmdev *fmdev, u16 mode)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->tx_data.aud_mode == mode)
++              return 0;
++
++      fmdbg("stereo mode: %d\n", mode);
++
++      /* Set Stereo/Mono mode */
++      payload = (1 - mode);
++      ret = fmc_send_cmd(fmdev, MONO_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fmdev->tx_data.aud_mode = mode;
++
++      return ret;
++}
++
++static u32 set_rds_text(struct fmdev *fmdev, u8 *rds_text)
++{
++      u16 payload;
++      u32 ret;
++
++      ret = fmc_send_cmd(fmdev, RDS_DATA_SET, REG_WR, rds_text,
++                      strlen(rds_text), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Scroll mode */
++      payload = (u16)0x1;
++      ret = fmc_send_cmd(fmdev, DISPLAY_MODE, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++static u32 set_rds_data_mode(struct fmdev *fmdev, u8 mode)
++{
++      u16 payload;
++      u32 ret;
++
++      /* Setting unique PI TODO: how unique? */
++      payload = (u16)0xcafe;
++      ret = fmc_send_cmd(fmdev, PI_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Set decoder id */
++      payload = (u16)0xa;
++      ret = fmc_send_cmd(fmdev, DI_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* TODO: RDS_MODE_GET? */
++      return 0;
++}
++
++static u32 set_rds_len(struct fmdev *fmdev, u8 type, u16 len)
++{
++      u16 payload;
++      u32 ret;
++
++      len |= type << 8;
++      payload = len;
++      ret = fmc_send_cmd(fmdev, RDS_CONFIG_DATA_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* TODO: LENGTH_GET? */
++      return 0;
++}
++
++u32 fm_tx_set_rds_mode(struct fmdev *fmdev, u8 rds_en_dis)
++{
++      u16 payload;
++      u32 ret;
++      u8 rds_text[] = "Zoom2\n";
++
++      fmdbg("rds_en_dis:%d(E:%d, D:%d)\n", rds_en_dis,
++                 FM_RDS_ENABLE, FM_RDS_DISABLE);
++
++      if (rds_en_dis == FM_RDS_ENABLE) {
++              /* Set RDS length */
++              set_rds_len(fmdev, 0, strlen(rds_text));
++
++              /* Set RDS text */
++              set_rds_text(fmdev, rds_text);
++
++              /* Set RDS mode */
++              set_rds_data_mode(fmdev, 0x0);
++      }
++
++      /* Send command to enable RDS */
++      if (rds_en_dis == FM_RDS_ENABLE)
++              payload = 0x01;
++      else
++              payload = 0x00;
++
++      ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      if (rds_en_dis == FM_RDS_ENABLE) {
++              /* Set RDS length */
++              set_rds_len(fmdev, 0, strlen(rds_text));
++
++              /* Set RDS text */
++              set_rds_text(fmdev, rds_text);
++      }
++      fmdev->tx_data.rds.flag = rds_en_dis;
++
++      return 0;
++}
++
++u32 fm_tx_set_radio_text(struct fmdev *fmdev, u8 *rds_text, u8 rds_type)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++
++      fm_tx_set_rds_mode(fmdev, 0);
++
++      /* Set RDS length */
++      set_rds_len(fmdev, rds_type, strlen(rds_text));
++
++      /* Set RDS text */
++      set_rds_text(fmdev, rds_text);
++
++      /* Set RDS mode */
++      set_rds_data_mode(fmdev, 0x0);
++
++      payload = 1;
++      ret = fmc_send_cmd(fmdev, RDS_DATA_ENB, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++u32 fm_tx_set_af(struct fmdev *fmdev, u32 af)
++{
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++
++      fmdbg("AF: %d\n", af);
++
++      af = (af - 87500) / 100;
++      payload = (u16)af;
++      ret = fmc_send_cmd(fmdev, TA_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++u32 fm_tx_set_region(struct fmdev *fmdev, u8 region)
++{
++      u16 payload;
++      u32 ret;
++
++      if (region != FM_BAND_EUROPE_US && region != FM_BAND_JAPAN) {
++              fmerr("Invalid band\n");
++              return -EINVAL;
++      }
++
++      /* Send command to set the band */
++      payload = (u16)region;
++      ret = fmc_send_cmd(fmdev, TX_BAND_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++u32 fm_tx_set_mute_mode(struct fmdev *fmdev, u8 mute_mode_toset)
++{
++      u16 payload;
++      u32 ret;
++
++      fmdbg("tx: mute mode %d\n", mute_mode_toset);
++
++      payload = mute_mode_toset;
++      ret = fmc_send_cmd(fmdev, MUTE, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      return 0;
++}
++
++/* Set TX Audio I/O */
++static u32 set_audio_io(struct fmdev *fmdev)
++{
++      struct fmtx_data *tx = &fmdev->tx_data;
++      u16 payload;
++      u32 ret;
++
++      /* Set Audio I/O Enable */
++      payload = tx->audio_io;
++      ret = fmc_send_cmd(fmdev, AUDIO_IO_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* TODO: is audio set? */
++      return 0;
++}
++
++/* Start TX Transmission */
++static u32 enable_xmit(struct fmdev *fmdev, u8 new_xmit_state)
++{
++      struct fmtx_data *tx = &fmdev->tx_data;
++      unsigned long timeleft;
++      u16 payload;
++      u32 ret;
++
++      /* Enable POWER_ENB interrupts */
++      payload = FM_POW_ENB_EVENT;
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Set Power Enable */
++      payload = new_xmit_state;
++      ret = fmc_send_cmd(fmdev, POWER_ENB_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* Wait for Power Enabled */
++      init_completion(&fmdev->maintask_comp);
++      timeleft = wait_for_completion_timeout(&fmdev->maintask_comp,
++                      FM_DRV_TX_TIMEOUT);
++      if (!timeleft) {
++              fmerr("Timeout(%d sec),didn't get tune ended interrupt\n",
++                         jiffies_to_msecs(FM_DRV_TX_TIMEOUT) / 1000);
++              return -ETIMEDOUT;
++      }
++
++      set_bit(FM_CORE_TX_XMITING, &fmdev->flag);
++      tx->xmit_state = new_xmit_state;
++
++      return 0;
++}
++
++/* Set TX power level */
++u32 fm_tx_set_pwr_lvl(struct fmdev *fmdev, u8 new_pwr_lvl)
++{
++      u16 payload;
++      struct fmtx_data *tx = &fmdev->tx_data;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++      fmdbg("tx: pwr_level_to_set %ld\n", (long int)new_pwr_lvl);
++
++      /* If the core isn't ready update global variable */
++      if (!test_bit(FM_CORE_READY, &fmdev->flag)) {
++              tx->pwr_lvl = new_pwr_lvl;
++              return 0;
++      }
++
++      /* Set power level: Application will specify power level value in
++       * units of dB/uV, whereas range and step are specific to FM chip.
++       * For TI's WL chips, convert application specified power level value
++       * to chip specific value by subtracting 122 from it. Refer to TI FM
++       * data sheet for details.
++       * */
++
++      payload = (FM_PWR_LVL_HIGH - new_pwr_lvl);
++      ret = fmc_send_cmd(fmdev, POWER_LEV_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      /* TODO: is the power level set? */
++      tx->pwr_lvl = new_pwr_lvl;
++
++      return 0;
++}
++
++/*
++ * Sets FM TX pre-emphasis filter value (OFF, 50us, or 75us)
++ * Convert V4L2 specified filter values to chip specific filter values.
++ */
++u32 fm_tx_set_preemph_filter(struct fmdev *fmdev, u32 preemphasis)
++{
++      struct fmtx_data *tx = &fmdev->tx_data;
++      u16 payload;
++      u32 ret;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++
++      switch (preemphasis) {
++      case V4L2_PREEMPHASIS_DISABLED:
++              payload = FM_TX_PREEMPH_OFF;
++              break;
++      case V4L2_PREEMPHASIS_50_uS:
++              payload = FM_TX_PREEMPH_50US;
++              break;
++      case V4L2_PREEMPHASIS_75_uS:
++              payload = FM_TX_PREEMPH_75US;
++              break;
++      }
++
++      ret = fmc_send_cmd(fmdev, PREMPH_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      tx->preemph = payload;
++
++      return ret;
++}
++
++/* Get the TX tuning capacitor value.*/
++u32 fm_tx_get_tune_cap_val(struct fmdev *fmdev)
++{
++      u16 curr_val;
++      u32 ret, resp_len;
++
++      if (fmdev->curr_fmmode != FM_MODE_TX)
++              return -EPERM;
++
++      ret = fmc_send_cmd(fmdev, READ_FMANT_TUNE_VALUE, REG_RD,
++                      NULL, sizeof(curr_val), &curr_val, &resp_len);
++      if (ret < 0)
++              return ret;
++
++      curr_val = be16_to_cpu(curr_val);
++
++      return curr_val;
++}
++
++/* Set TX Frequency */
++u32 fm_tx_set_freq(struct fmdev *fmdev, u32 freq_to_set)
++{
++      struct fmtx_data *tx = &fmdev->tx_data;
++      u16 payload, chanl_index;
++      u32 ret;
++
++      if (test_bit(FM_CORE_TX_XMITING, &fmdev->flag)) {
++              enable_xmit(fmdev, 0);
++              clear_bit(FM_CORE_TX_XMITING, &fmdev->flag);
++      }
++
++      /* Enable FR, BL interrupts */
++      payload = (FM_FR_EVENT | FM_BL_EVENT);
++      ret = fmc_send_cmd(fmdev, INT_MASK_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      tx->tx_frq = (unsigned long)freq_to_set;
++      fmdbg("tx: freq_to_set %ld\n", (long int)tx->tx_frq);
++
++      chanl_index = freq_to_set / 10;
++
++      /* Set current tuner channel */
++      payload = chanl_index;
++      ret = fmc_send_cmd(fmdev, CHANL_SET, REG_WR, &payload,
++                      sizeof(payload), NULL, NULL);
++      if (ret < 0)
++              return ret;
++
++      fm_tx_set_pwr_lvl(fmdev, tx->pwr_lvl);
++      fm_tx_set_preemph_filter(fmdev, tx->preemph);
++
++      tx->audio_io = 0x01;    /* I2S */
++      set_audio_io(fmdev);
++
++      enable_xmit(fmdev, 0x01);       /* Enable transmission */
++
++      tx->aud_mode = FM_STEREO_MODE;
++      tx->rds.flag = FM_RDS_DISABLE;
++
++      return 0;
++}
++
+diff --git a/drivers/media/radio/wl128x/fmdrv_tx.h b/drivers/media/radio/wl128x/fmdrv_tx.h
+new file mode 100644
+index 0000000..e393a2b
+--- /dev/null
++++ b/drivers/media/radio/wl128x/fmdrv_tx.h
+@@ -0,0 +1,37 @@
++/*
++ *  FM Driver for Connectivity chip of Texas Instruments.
++ *  FM TX module header.
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#ifndef _FMDRV_TX_H
++#define _FMDRV_TX_H
++
++u32 fm_tx_set_freq(struct fmdev *, u32);
++u32 fm_tx_set_pwr_lvl(struct fmdev *, u8);
++u32 fm_tx_set_region(struct fmdev *, u8);
++u32 fm_tx_set_mute_mode(struct fmdev *, u8);
++u32 fm_tx_set_stereo_mono(struct fmdev *, u16);
++u32 fm_tx_set_rds_mode(struct fmdev *, u8);
++u32 fm_tx_set_radio_text(struct fmdev *, u8 *, u8);
++u32 fm_tx_set_af(struct fmdev *, u32);
++u32 fm_tx_set_preemph_filter(struct fmdev *, u32);
++u32 fm_tx_get_tune_cap_val(struct fmdev *);
++
++#endif
++
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0012-drivers-media-radio-wl128x-Kconfig-Makefile-for-wl12.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0012-drivers-media-radio-wl128x-Kconfig-Makefile-for-wl12.patch
new file mode 100644 (file)
index 0000000..2e5fda8
--- /dev/null
@@ -0,0 +1,52 @@
+From 949d2c98bb76cc321e5ef5a96a632d831e5953bf Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:26 +0000
+Subject: [PATCH 12/15] drivers:media:radio: wl128x: Kconfig & Makefile for wl128x driver
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/wl128x/Kconfig  |   17 +++++++++++++++++
+ drivers/media/radio/wl128x/Makefile |    6 ++++++
+ 2 files changed, 23 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/media/radio/wl128x/Kconfig
+ create mode 100644 drivers/media/radio/wl128x/Makefile
+
+diff --git a/drivers/media/radio/wl128x/Kconfig b/drivers/media/radio/wl128x/Kconfig
+new file mode 100644
+index 0000000..749f67b
+--- /dev/null
++++ b/drivers/media/radio/wl128x/Kconfig
+@@ -0,0 +1,17 @@
++#
++# TI's wl128x FM driver based on TI's ST driver.
++#
++menu "Texas Instruments WL128x FM driver (ST based)"
++config RADIO_WL128X
++      tristate "Texas Instruments WL128x FM Radio"
++      depends on VIDEO_V4L2 && RFKILL
++      select TI_ST
++      help
++      Choose Y here if you have this FM radio chip.
++
++      In order to control your radio card, you will need to use programs
++      that are compatible with the Video For Linux 2 API.  Information on
++      this API and pointers to "v4l2" programs may be found at
++      <file:Documentation/video4linux/API.html>.
++
++endmenu
+diff --git a/drivers/media/radio/wl128x/Makefile b/drivers/media/radio/wl128x/Makefile
+new file mode 100644
+index 0000000..32a0ead
+--- /dev/null
++++ b/drivers/media/radio/wl128x/Makefile
+@@ -0,0 +1,6 @@
++#
++# Makefile for TI's shared transport driver based wl128x
++# FM radio.
++#
++obj-$(CONFIG_RADIO_WL128X)    += fm_drv.o
++fm_drv-objs           := fmdrv_common.o fmdrv_rx.o fmdrv_tx.o fmdrv_v4l2.o
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0013-drivers-media-radio-Update-Kconfig-and-Makefile-for-.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0013-drivers-media-radio-Update-Kconfig-and-Makefile-for-.patch
new file mode 100644 (file)
index 0000000..ea257a4
--- /dev/null
@@ -0,0 +1,38 @@
+From 340d2fa4ff21c43309e70cc6b4a88f9ee6c23d95 Mon Sep 17 00:00:00 2001
+From: Manjunatha Halli <manjunatha_halli@ti.com>
+Date: Tue, 11 Jan 2011 11:31:27 +0000
+Subject: [PATCH 13/15] drivers:media:radio: Update Kconfig and Makefile for wl128x FM driver.
+
+Signed-off-by: Manjunatha Halli <manjunatha_halli@ti.com>
+Reviewed-by: Hans Verkuil <hverkuil@xs4all.nl>
+---
+ drivers/media/radio/Kconfig  |    3 +++
+ drivers/media/radio/Makefile |    1 +
+ 2 files changed, 4 insertions(+), 0 deletions(-)
+
+diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
+index 83567b8..4529bc7 100644
+--- a/drivers/media/radio/Kconfig
++++ b/drivers/media/radio/Kconfig
+@@ -452,4 +452,7 @@ config RADIO_TIMBERDALE
+         found behind the Timberdale FPGA on the Russellville board.
+         Enabling this driver will automatically select the DSP and tuner.
++# TI's ST based wl128x FM radio
++source "drivers/media/radio/wl128x/Kconfig"
++
+ endif # RADIO_ADAPTERS
+diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
+index f615583..b71f448 100644
+--- a/drivers/media/radio/Makefile
++++ b/drivers/media/radio/Makefile
+@@ -26,5 +26,6 @@ obj-$(CONFIG_RADIO_TEA5764) += radio-tea5764.o
+ obj-$(CONFIG_RADIO_SAA7706H) += saa7706h.o
+ obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
+ obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o
++obj-$(CONFIG_RADIO_WL128X) += wl128x/
+ EXTRA_CFLAGS += -Isound
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0014-drivers-misc-ti-st-change-protocol-parse-logic.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0014-drivers-misc-ti-st-change-protocol-parse-logic.patch
new file mode 100644 (file)
index 0000000..c16dc45
--- /dev/null
@@ -0,0 +1,900 @@
+From 46b2c4077bedb96a38cdceff88f2c9b0a9923a8c Mon Sep 17 00:00:00 2001
+From: Pavan Savoy <pavan_savoy@ti.com>
+Date: Tue, 4 Jan 2011 10:59:47 +0000
+Subject: [PATCH 14/15] drivers:misc:ti-st: change protocol parse logic
+
+TI shared transport driver had to specifically know the
+protocol headers for each type of data it can receive to
+properly re-assemble data if its fragmented during UART
+transaction or fragment if the data is an assembly of
+
+different protocol data.
+
+Now the individual protocol drivers provide enough header
+information for shared transport driver to do this in a
+generic way applicable for all protocols.
+
+Signed-off-by: Pavan Savoy <pavan_savoy@ti.com>
+---
+ drivers/misc/ti-st/st_core.c |  355 +++++++++++++-----------------------------
+ drivers/misc/ti-st/st_kim.c  |   56 ++++----
+ include/linux/ti_wilink_st.h |   40 ++++--
+ 3 files changed, 167 insertions(+), 284 deletions(-)
+
+diff --git a/drivers/misc/ti-st/st_core.c b/drivers/misc/ti-st/st_core.c
+index f9aad06..84d73c5 100644
+--- a/drivers/misc/ti-st/st_core.c
++++ b/drivers/misc/ti-st/st_core.c
+@@ -25,10 +25,9 @@
+ #include <linux/init.h>
+ #include <linux/tty.h>
+-/* understand BT, FM and GPS for now */
+-#include <net/bluetooth/bluetooth.h>
+-#include <net/bluetooth/hci_core.h>
+-#include <net/bluetooth/hci.h>
++#include <linux/seq_file.h>
++#include <linux/skbuff.h>
++
+ #include <linux/ti_wilink_st.h>
+ /* function pointer pointing to either,
+@@ -38,21 +37,20 @@
+ void (*st_recv) (void*, const unsigned char*, long);
+ /********************************************************************/
+-#if 0
+-/* internal misc functions */
+-bool is_protocol_list_empty(void)
++static void add_channel_to_table(struct st_data_s *st_gdata,
++              struct st_proto_s *new_proto)
+ {
+-      unsigned char i = 0;
+-      pr_debug(" %s ", __func__);
+-      for (i = 0; i < ST_MAX; i++) {
+-              if (st_gdata->list[i] != NULL)
+-                      return ST_NOTEMPTY;
+-              /* not empty */
+-      }
+-      /* list empty */
+-      return ST_EMPTY;
++      pr_info("%s: id %d\n", __func__, new_proto->chnl_id);
++      /* list now has the channel id as index itself */
++      st_gdata->list[new_proto->chnl_id] = new_proto;
++}
++
++static void remove_channel_from_table(struct st_data_s *st_gdata,
++              struct st_proto_s *proto)
++{
++      pr_info("%s: id %d\n", __func__, proto->chnl_id);
++      st_gdata->list[proto->chnl_id] = NULL;
+ }
+-#endif
+ /* can be called in from
+  * -- KIM (during fw download)
+@@ -82,15 +80,15 @@ int st_int_write(struct st_data_s *st_gdata,
+  * push the skb received to relevant
+  * protocol stacks
+  */
+-void st_send_frame(enum proto_type protoid, struct st_data_s *st_gdata)
++void st_send_frame(unsigned char chnl_id, struct st_data_s *st_gdata)
+ {
+-      pr_info(" %s(prot:%d) ", __func__, protoid);
++      pr_info(" %s(prot:%d) ", __func__, chnl_id);
+       if (unlikely
+           (st_gdata == NULL || st_gdata->rx_skb == NULL
+-           || st_gdata->list[protoid] == NULL)) {
+-              pr_err("protocol %d not registered, no data to send?",
+-                         protoid);
++           || st_gdata->list[chnl_id] == NULL)) {
++              pr_err("chnl_id %d not registered, no data to send?",
++                         chnl_id);
+               kfree_skb(st_gdata->rx_skb);
+               return;
+       }
+@@ -99,17 +97,17 @@ void st_send_frame(enum proto_type protoid, struct st_data_s *st_gdata)
+        * - should be just skb_queue_tail for the
+        *   protocol stack driver
+        */
+-      if (likely(st_gdata->list[protoid]->recv != NULL)) {
++      if (likely(st_gdata->list[chnl_id]->recv != NULL)) {
+               if (unlikely
+-                      (st_gdata->list[protoid]->recv
+-                      (st_gdata->list[protoid]->priv_data, st_gdata->rx_skb)
++                      (st_gdata->list[chnl_id]->recv
++                      (st_gdata->list[chnl_id]->priv_data, st_gdata->rx_skb)
+                            != 0)) {
+-                      pr_err(" proto stack %d's ->recv failed", protoid);
++                      pr_err(" proto stack %d's ->recv failed", chnl_id);
+                       kfree_skb(st_gdata->rx_skb);
+                       return;
+               }
+       } else {
+-              pr_err(" proto stack %d's ->recv null", protoid);
++              pr_err(" proto stack %d's ->recv null", chnl_id);
+               kfree_skb(st_gdata->rx_skb);
+       }
+       return;
+@@ -124,7 +122,7 @@ void st_reg_complete(struct st_data_s *st_gdata, char err)
+ {
+       unsigned char i = 0;
+       pr_info(" %s ", __func__);
+-      for (i = 0; i < ST_MAX; i++) {
++      for (i = 0; i < ST_MAX_CHANNELS; i++) {
+               if (likely(st_gdata != NULL && st_gdata->list[i] != NULL &&
+                          st_gdata->list[i]->reg_complete_cb != NULL))
+                       st_gdata->list[i]->reg_complete_cb
+@@ -133,7 +131,7 @@ void st_reg_complete(struct st_data_s *st_gdata, char err)
+ }
+ static inline int st_check_data_len(struct st_data_s *st_gdata,
+-      int protoid, int len)
++      unsigned char chnl_id, int len)
+ {
+       int room = skb_tailroom(st_gdata->rx_skb);
+@@ -144,7 +142,7 @@ static inline int st_check_data_len(struct st_data_s *st_gdata,
+                * has zero length payload. So, ask ST CORE to
+                * forward the packet to protocol driver (BT/FM/GPS)
+                */
+-              st_send_frame(protoid, st_gdata);
++              st_send_frame(chnl_id, st_gdata);
+       } else if (len > room) {
+               /* Received packet's payload length is larger.
+@@ -157,7 +155,7 @@ static inline int st_check_data_len(struct st_data_s *st_gdata,
+               /* Packet header has non-zero payload length and
+                * we have enough space in created skb. Lets read
+                * payload data */
+-              st_gdata->rx_state = ST_BT_W4_DATA;
++              st_gdata->rx_state = ST_W4_DATA;
+               st_gdata->rx_count = len;
+               return len;
+       }
+@@ -167,6 +165,7 @@ static inline int st_check_data_len(struct st_data_s *st_gdata,
+       st_gdata->rx_state = ST_W4_PACKET_TYPE;
+       st_gdata->rx_skb = NULL;
+       st_gdata->rx_count = 0;
++      st_gdata->rx_chnl = 0;
+       return 0;
+ }
+@@ -208,13 +207,10 @@ void st_int_recv(void *disc_data,
+       const unsigned char *data, long count)
+ {
+       char *ptr;
+-      struct hci_event_hdr *eh;
+-      struct hci_acl_hdr *ah;
+-      struct hci_sco_hdr *sh;
+-      struct fm_event_hdr *fm;
+-      struct gps_event_hdr *gps;
+-      int len = 0, type = 0, dlen = 0;
+-      static enum proto_type protoid = ST_MAX;
++      struct st_proto_s *proto;
++      unsigned short payload_len = 0;
++      int len = 0, type = 0;
++      unsigned char *plen;
+       struct st_data_s *st_gdata = (struct st_data_s *)disc_data;
+       ptr = (char *)data;
+@@ -242,64 +238,36 @@ void st_int_recv(void *disc_data,
+                       /* Check ST RX state machine , where are we? */
+                       switch (st_gdata->rx_state) {
+-
+-                              /* Waiting for complete packet ? */
+-                      case ST_BT_W4_DATA:
++                      /* Waiting for complete packet ? */
++                      case ST_W4_DATA:
+                               pr_debug("Complete pkt received");
+-
+                               /* Ask ST CORE to forward
+                                * the packet to protocol driver */
+-                              st_send_frame(protoid, st_gdata);
++                              st_send_frame(st_gdata->rx_chnl, st_gdata);
+                               st_gdata->rx_state = ST_W4_PACKET_TYPE;
+                               st_gdata->rx_skb = NULL;
+-                              protoid = ST_MAX;       /* is this required ? */
+-                              continue;
+-
+-                              /* Waiting for Bluetooth event header ? */
+-                      case ST_BT_W4_EVENT_HDR:
+-                              eh = (struct hci_event_hdr *)st_gdata->rx_skb->
+-                                  data;
+-
+-                              pr_debug("Event header: evt 0x%2.2x"
+-                                         "plen %d", eh->evt, eh->plen);
+-
+-                              st_check_data_len(st_gdata, protoid, eh->plen);
+-                              continue;
+-
+-                              /* Waiting for Bluetooth acl header ? */
+-                      case ST_BT_W4_ACL_HDR:
+-                              ah = (struct hci_acl_hdr *)st_gdata->rx_skb->
+-                                  data;
+-                              dlen = __le16_to_cpu(ah->dlen);
+-
+-                              pr_info("ACL header: dlen %d", dlen);
+-
+-                              st_check_data_len(st_gdata, protoid, dlen);
+-                              continue;
+-
+-                              /* Waiting for Bluetooth sco header ? */
+-                      case ST_BT_W4_SCO_HDR:
+-                              sh = (struct hci_sco_hdr *)st_gdata->rx_skb->
+-                                  data;
+-
+-                              pr_info("SCO header: dlen %d", sh->dlen);
+-
+-                              st_check_data_len(st_gdata, protoid, sh->dlen);
+-                              continue;
+-                      case ST_FM_W4_EVENT_HDR:
+-                              fm = (struct fm_event_hdr *)st_gdata->rx_skb->
+-                                  data;
+-                              pr_info("FM Header: ");
+-                              st_check_data_len(st_gdata, ST_FM, fm->plen);
+                               continue;
+-                              /* TODO : Add GPS packet machine logic here */
+-                      case ST_GPS_W4_EVENT_HDR:
+-                              /* [0x09 pkt hdr][R/W byte][2 byte len] */
+-                              gps = (struct gps_event_hdr *)st_gdata->rx_skb->
+-                                   data;
+-                              pr_info("GPS Header: ");
+-                              st_check_data_len(st_gdata, ST_GPS, gps->plen);
++                      /* parse the header to know details */
++                      case ST_W4_HEADER:
++                              proto = st_gdata->list[st_gdata->rx_chnl];
++                              plen =
++                              &st_gdata->rx_skb->data
++                              [proto->offset_len_in_hdr];
++                              pr_info("plen pointing to %x\n", *plen);
++                              if (proto->len_size == 1)/* 1 byte len field */
++                                      payload_len = *(unsigned char *)plen;
++                              else if (proto->len_size == 2)
++                                      payload_len =
++                                      __le16_to_cpu(*(unsigned short *)plen);
++                              else
++                                      pr_info("%s: invalid length "
++                                      "for id %d\n",
++                                      __func__, proto->chnl_id);
++                              st_check_data_len(st_gdata, proto->chnl_id,
++                                              payload_len);
++                              pr_info("off %d, pay len %d\n",
++                                      proto->offset_len_in_hdr, payload_len);
+                               continue;
+                       }       /* end of switch rx_state */
+               }
+@@ -308,51 +276,6 @@ void st_int_recv(void *disc_data,
+               /* Check first byte of packet and identify module
+                * owner (BT/FM/GPS) */
+               switch (*ptr) {
+-
+-                      /* Bluetooth event packet? */
+-              case HCI_EVENT_PKT:
+-                      pr_info("Event packet");
+-                      st_gdata->rx_state = ST_BT_W4_EVENT_HDR;
+-                      st_gdata->rx_count = HCI_EVENT_HDR_SIZE;
+-                      type = HCI_EVENT_PKT;
+-                      protoid = ST_BT;
+-                      break;
+-
+-                      /* Bluetooth acl packet? */
+-              case HCI_ACLDATA_PKT:
+-                      pr_info("ACL packet");
+-                      st_gdata->rx_state = ST_BT_W4_ACL_HDR;
+-                      st_gdata->rx_count = HCI_ACL_HDR_SIZE;
+-                      type = HCI_ACLDATA_PKT;
+-                      protoid = ST_BT;
+-                      break;
+-
+-                      /* Bluetooth sco packet? */
+-              case HCI_SCODATA_PKT:
+-                      pr_info("SCO packet");
+-                      st_gdata->rx_state = ST_BT_W4_SCO_HDR;
+-                      st_gdata->rx_count = HCI_SCO_HDR_SIZE;
+-                      type = HCI_SCODATA_PKT;
+-                      protoid = ST_BT;
+-                      break;
+-
+-                      /* Channel 8(FM) packet? */
+-              case ST_FM_CH8_PKT:
+-                      pr_info("FM CH8 packet");
+-                      type = ST_FM_CH8_PKT;
+-                      st_gdata->rx_state = ST_FM_W4_EVENT_HDR;
+-                      st_gdata->rx_count = FM_EVENT_HDR_SIZE;
+-                      protoid = ST_FM;
+-                      break;
+-
+-                      /* Channel 9(GPS) packet? */
+-              case 0x9:       /*ST_LL_GPS_CH9_PKT */
+-                      pr_info("GPS CH9 packet");
+-                      type = 0x9;     /* ST_LL_GPS_CH9_PKT; */
+-                      protoid = ST_GPS;
+-                      st_gdata->rx_state = ST_GPS_W4_EVENT_HDR;
+-                      st_gdata->rx_count = 3; /* GPS_EVENT_HDR_SIZE -1*/
+-                      break;
+               case LL_SLEEP_IND:
+               case LL_SLEEP_ACK:
+               case LL_WAKE_UP_IND:
+@@ -373,57 +296,22 @@ void st_int_recv(void *disc_data,
+                       continue;
+                       /* Unknow packet? */
+               default:
+-                      pr_err("Unknown packet type %2.2x", (__u8) *ptr);
+-                      ptr++;
+-                      count--;
+-                      continue;
++                      type = *ptr;
++                      st_gdata->rx_skb = alloc_skb(
++                                      st_gdata->list[type]->max_frame_size,
++                                      GFP_ATOMIC);
++                      skb_reserve(st_gdata->rx_skb,
++                                      st_gdata->list[type]->reserve);
++                      /* next 2 required for BT only */
++                      st_gdata->rx_skb->cb[0] = type; /*pkt_type*/
++                      st_gdata->rx_skb->cb[1] = 0; /*incoming*/
++                      st_gdata->rx_chnl = *ptr;
++                      st_gdata->rx_state = ST_W4_HEADER;
++                      st_gdata->rx_count = st_gdata->list[type]->hdr_len;
++                      pr_info("rx_count %ld\n", st_gdata->rx_count);
+               };
+               ptr++;
+               count--;
+-
+-              switch (protoid) {
+-              case ST_BT:
+-                      /* Allocate new packet to hold received data */
+-                      st_gdata->rx_skb =
+-                          bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC);
+-                      if (!st_gdata->rx_skb) {
+-                              pr_err("Can't allocate mem for new packet");
+-                              st_gdata->rx_state = ST_W4_PACKET_TYPE;
+-                              st_gdata->rx_count = 0;
+-                              return;
+-                      }
+-                      bt_cb(st_gdata->rx_skb)->pkt_type = type;
+-                      break;
+-              case ST_FM:     /* for FM */
+-                      st_gdata->rx_skb =
+-                          alloc_skb(FM_MAX_FRAME_SIZE, GFP_ATOMIC);
+-                      if (!st_gdata->rx_skb) {
+-                              pr_err("Can't allocate mem for new packet");
+-                              st_gdata->rx_state = ST_W4_PACKET_TYPE;
+-                              st_gdata->rx_count = 0;
+-                              return;
+-                      }
+-                      /* place holder 0x08 */
+-                      skb_reserve(st_gdata->rx_skb, 1);
+-                      st_gdata->rx_skb->cb[0] = ST_FM_CH8_PKT;
+-                      break;
+-              case ST_GPS:
+-                      /* for GPS */
+-                      st_gdata->rx_skb =
+-                          alloc_skb(100 /*GPS_MAX_FRAME_SIZE */ , GFP_ATOMIC);
+-                      if (!st_gdata->rx_skb) {
+-                              pr_err("Can't allocate mem for new packet");
+-                              st_gdata->rx_state = ST_W4_PACKET_TYPE;
+-                              st_gdata->rx_count = 0;
+-                              return;
+-                      }
+-                      /* place holder 0x09 */
+-                      skb_reserve(st_gdata->rx_skb, 1);
+-                      st_gdata->rx_skb->cb[0] = 0x09; /*ST_GPS_CH9_PKT; */
+-                      break;
+-              case ST_MAX:
+-                      break;
+-              }
+       }
+       pr_debug("done %s", __func__);
+       return;
+@@ -565,20 +453,28 @@ long st_register(struct st_proto_s *new_proto)
+       unsigned long flags = 0;
+       st_kim_ref(&st_gdata, 0);
+-      pr_info("%s(%d) ", __func__, new_proto->type);
++      pr_info("%s(%d) ", __func__, new_proto->chnl_id);
+       if (st_gdata == NULL || new_proto == NULL || new_proto->recv == NULL
+           || new_proto->reg_complete_cb == NULL) {
+               pr_err("gdata/new_proto/recv or reg_complete_cb not ready");
++              if (st_gdata == NULL)
++                      pr_err("error 1\n");
++              if (new_proto == NULL)
++                      pr_err("error 2\n");
++              if (new_proto->recv == NULL)
++                      pr_err("error 3\n");
++              if (new_proto->reg_complete_cb == NULL)
++                      pr_err("erro 4\n");
+               return -1;
+       }
+-      if (new_proto->type < ST_BT || new_proto->type >= ST_MAX) {
+-              pr_err("protocol %d not supported", new_proto->type);
++      if (new_proto->chnl_id >= ST_MAX_CHANNELS) {
++              pr_err("chnl_id %d not supported", new_proto->chnl_id);
+               return -EPROTONOSUPPORT;
+       }
+-      if (st_gdata->list[new_proto->type] != NULL) {
+-              pr_err("protocol %d already registered", new_proto->type);
++      if (st_gdata->list[new_proto->chnl_id] != NULL) {
++              pr_err("chnl_id %d already registered", new_proto->chnl_id);
+               return -EALREADY;
+       }
+@@ -586,11 +482,11 @@ long st_register(struct st_proto_s *new_proto)
+       spin_lock_irqsave(&st_gdata->lock, flags);
+       if (test_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state)) {
+-              pr_info(" ST_REG_IN_PROGRESS:%d ", new_proto->type);
++              pr_info(" ST_REG_IN_PROGRESS:%d ", new_proto->chnl_id);
+               /* fw download in progress */
+-              st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE);
++              st_kim_chip_toggle(new_proto->chnl_id, KIM_GPIO_ACTIVE);
+-              st_gdata->list[new_proto->type] = new_proto;
++              add_channel_to_table(st_gdata, new_proto);
+               st_gdata->protos_registered++;
+               new_proto->write = st_write;
+@@ -598,7 +494,7 @@ long st_register(struct st_proto_s *new_proto)
+               spin_unlock_irqrestore(&st_gdata->lock, flags);
+               return -EINPROGRESS;
+       } else if (st_gdata->protos_registered == ST_EMPTY) {
+-              pr_info(" protocol list empty :%d ", new_proto->type);
++              pr_info(" chnl_id list empty :%d ", new_proto->chnl_id);
+               set_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state);
+               st_recv = st_kim_recv;
+@@ -622,9 +518,9 @@ long st_register(struct st_proto_s *new_proto)
+                       return -1;
+               }
+-              /* the protocol might require other gpios to be toggled
++              /* the chnl_id might require other gpios to be toggled
+                */
+-              st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE);
++              st_kim_chip_toggle(new_proto->chnl_id, KIM_GPIO_ACTIVE);
+               clear_bit(ST_REG_IN_PROGRESS, &st_gdata->st_state);
+               st_recv = st_int_recv;
+@@ -642,14 +538,14 @@ long st_register(struct st_proto_s *new_proto)
+               /* check for already registered once more,
+                * since the above check is old
+                */
+-              if (st_gdata->list[new_proto->type] != NULL) {
++              if (st_gdata->list[new_proto->chnl_id] != NULL) {
+                       pr_err(" proto %d already registered ",
+-                                 new_proto->type);
++                                 new_proto->chnl_id);
+                       return -EALREADY;
+               }
+               spin_lock_irqsave(&st_gdata->lock, flags);
+-              st_gdata->list[new_proto->type] = new_proto;
++              add_channel_to_table(st_gdata, new_proto);
+               st_gdata->protos_registered++;
+               new_proto->write = st_write;
+               spin_unlock_irqrestore(&st_gdata->lock, flags);
+@@ -657,22 +553,7 @@ long st_register(struct st_proto_s *new_proto)
+       }
+       /* if fw is already downloaded & new stack registers protocol */
+       else {
+-              switch (new_proto->type) {
+-              case ST_BT:
+-                      /* do nothing */
+-                      break;
+-              case ST_FM:
+-              case ST_GPS:
+-                      st_kim_chip_toggle(new_proto->type, KIM_GPIO_ACTIVE);
+-                      break;
+-              case ST_MAX:
+-              default:
+-                      pr_err("%d protocol not supported",
+-                                 new_proto->type);
+-                      spin_unlock_irqrestore(&st_gdata->lock, flags);
+-                      return -EPROTONOSUPPORT;
+-              }
+-              st_gdata->list[new_proto->type] = new_proto;
++              add_channel_to_table(st_gdata, new_proto);
+               st_gdata->protos_registered++;
+               new_proto->write = st_write;
+@@ -680,48 +561,48 @@ long st_register(struct st_proto_s *new_proto)
+               spin_unlock_irqrestore(&st_gdata->lock, flags);
+               return err;
+       }
+-      pr_debug("done %s(%d) ", __func__, new_proto->type);
++      pr_debug("done %s(%d) ", __func__, new_proto->chnl_id);
+ }
+ EXPORT_SYMBOL_GPL(st_register);
+ /* to unregister a protocol -
+  * to be called from protocol stack driver
+  */
+-long st_unregister(enum proto_type type)
++long st_unregister(struct st_proto_s *proto)
+ {
+       long err = 0;
+       unsigned long flags = 0;
+       struct st_data_s        *st_gdata;
+-      pr_debug("%s: %d ", __func__, type);
++      pr_debug("%s: %d ", __func__, proto->chnl_id);
+       st_kim_ref(&st_gdata, 0);
+-      if (type < ST_BT || type >= ST_MAX) {
+-              pr_err(" protocol %d not supported", type);
++      if (proto->chnl_id >= ST_MAX_CHANNELS) {
++              pr_err(" chnl_id %d not supported", proto->chnl_id);
+               return -EPROTONOSUPPORT;
+       }
+       spin_lock_irqsave(&st_gdata->lock, flags);
+-      if (st_gdata->list[type] == NULL) {
+-              pr_err(" protocol %d not registered", type);
++      if (st_gdata->list[proto->chnl_id] == NULL) {
++              pr_err(" chnl_id %d not registered", proto->chnl_id);
+               spin_unlock_irqrestore(&st_gdata->lock, flags);
+               return -EPROTONOSUPPORT;
+       }
+       st_gdata->protos_registered--;
+-      st_gdata->list[type] = NULL;
++      remove_channel_from_table(st_gdata, proto);
+       /* kim ignores BT in the below function
+        * and handles the rest, BT is toggled
+        * only in kim_start and kim_stop
+        */
+-      st_kim_chip_toggle(type, KIM_GPIO_INACTIVE);
++      st_kim_chip_toggle(proto->chnl_id, KIM_GPIO_INACTIVE);
+       spin_unlock_irqrestore(&st_gdata->lock, flags);
+       if ((st_gdata->protos_registered == ST_EMPTY) &&
+           (!test_bit(ST_REG_PENDING, &st_gdata->st_state))) {
+-              pr_info(" all protocols unregistered ");
++              pr_info(" all chnl_ids unregistered ");
+               /* stop traffic on tty */
+               if (st_gdata->tty) {
+@@ -729,7 +610,7 @@ long st_unregister(enum proto_type type)
+                       stop_tty(st_gdata->tty);
+               }
+-              /* all protocols now unregistered */
++              /* all chnl_ids now unregistered */
+               st_kim_stop(st_gdata->kim_data);
+               /* disable ST LL */
+               st_ll_disable(st_gdata);
+@@ -745,7 +626,7 @@ long st_write(struct sk_buff *skb)
+ {
+       struct st_data_s *st_gdata;
+ #ifdef DEBUG
+-      enum proto_type protoid = ST_MAX;
++      unsigned char chnl_id = ST_MAX_CHANNELS;
+ #endif
+       long len;
+@@ -756,22 +637,10 @@ long st_write(struct sk_buff *skb)
+               return -1;
+       }
+ #ifdef DEBUG                  /* open-up skb to read the 1st byte */
+-      switch (skb->data[0]) {
+-      case HCI_COMMAND_PKT:
+-      case HCI_ACLDATA_PKT:
+-      case HCI_SCODATA_PKT:
+-              protoid = ST_BT;
+-              break;
+-      case ST_FM_CH8_PKT:
+-              protoid = ST_FM;
+-              break;
+-      case 0x09:
+-              protoid = ST_GPS;
+-              break;
+-      }
+-      if (unlikely(st_gdata->list[protoid] == NULL)) {
+-              pr_err(" protocol %d not registered, and writing? ",
+-                         protoid);
++      chnl_id = skb->data[0];
++      if (unlikely(st_gdata->list[chnl_id] == NULL)) {
++              pr_err(" chnl_id %d not registered, and writing? ",
++                         chnl_id);
+               return -1;
+       }
+ #endif
+@@ -824,7 +693,7 @@ static int st_tty_open(struct tty_struct *tty)
+ static void st_tty_close(struct tty_struct *tty)
+ {
+-      unsigned char i = ST_MAX;
++      unsigned char i = ST_MAX_CHANNELS;
+       unsigned long flags = 0;
+       struct  st_data_s *st_gdata = tty->disc_data;
+@@ -835,7 +704,7 @@ static void st_tty_close(struct tty_struct *tty)
+        * un-installed for some reason - what should be done ?
+        */
+       spin_lock_irqsave(&st_gdata->lock, flags);
+-      for (i = ST_BT; i < ST_MAX; i++) {
++      for (i = ST_BT; i < ST_MAX_CHANNELS; i++) {
+               if (st_gdata->list[i] != NULL)
+                       pr_err("%d not un-registered", i);
+               st_gdata->list[i] = NULL;
+@@ -869,7 +738,7 @@ static void st_tty_close(struct tty_struct *tty)
+ static void st_tty_receive(struct tty_struct *tty, const unsigned char *data,
+                          char *tty_flags, int count)
+ {
+-
++#define VERBOSE
+ #ifdef VERBOSE
+       print_hex_dump(KERN_DEBUG, ">in>", DUMP_PREFIX_NONE,
+               16, 1, data, count, 0);
+diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c
+index 73b6c8b..707c858 100644
+--- a/drivers/misc/ti-st/st_kim.c
++++ b/drivers/misc/ti-st/st_kim.c
+@@ -32,11 +32,7 @@
+ #include <linux/sched.h>
+ #include <linux/rfkill.h>
+-/* understand BT events for fw response */
+-#include <net/bluetooth/bluetooth.h>
+-#include <net/bluetooth/hci_core.h>
+-#include <net/bluetooth/hci.h>
+-
++#include <linux/skbuff.h>
+ #include <linux/ti_wilink_st.h>
+@@ -134,7 +130,7 @@ static inline int kim_check_data_len(struct kim_data_s *kim_gdata, int len)
+               /* Packet header has non-zero payload length and
+                * we have enough space in created skb. Lets read
+                * payload data */
+-              kim_gdata->rx_state = ST_BT_W4_DATA;
++              kim_gdata->rx_state = ST_W4_DATA;
+               kim_gdata->rx_count = len;
+               return len;
+       }
+@@ -158,8 +154,8 @@ void kim_int_recv(struct kim_data_s *kim_gdata,
+       const unsigned char *data, long count)
+ {
+       const unsigned char *ptr;
+-      struct hci_event_hdr *eh;
+       int len = 0, type = 0;
++      unsigned char *plen;
+       pr_debug("%s", __func__);
+       /* Decode received bytes here */
+@@ -183,29 +179,27 @@ void kim_int_recv(struct kim_data_s *kim_gdata,
+                       /* Check ST RX state machine , where are we? */
+                       switch (kim_gdata->rx_state) {
+                               /* Waiting for complete packet ? */
+-                      case ST_BT_W4_DATA:
++                      case ST_W4_DATA:
+                               pr_debug("Complete pkt received");
+                               validate_firmware_response(kim_gdata);
+                               kim_gdata->rx_state = ST_W4_PACKET_TYPE;
+                               kim_gdata->rx_skb = NULL;
+                               continue;
+                               /* Waiting for Bluetooth event header ? */
+-                      case ST_BT_W4_EVENT_HDR:
+-                              eh = (struct hci_event_hdr *)kim_gdata->
+-                                  rx_skb->data;
+-                              pr_debug("Event header: evt 0x%2.2x"
+-                                         "plen %d", eh->evt, eh->plen);
+-                              kim_check_data_len(kim_gdata, eh->plen);
++                      case ST_W4_HEADER:
++                              plen =
++                              (unsigned char *)&kim_gdata->rx_skb->data[1];
++                              pr_debug("event hdr: plen 0x%02x\n", *plen);
++                              kim_check_data_len(kim_gdata, *plen);
+                               continue;
+                       }       /* end of switch */
+               }               /* end of if rx_state */
+               switch (*ptr) {
+                       /* Bluetooth event packet? */
+-              case HCI_EVENT_PKT:
+-                      pr_info("Event packet");
+-                      kim_gdata->rx_state = ST_BT_W4_EVENT_HDR;
+-                      kim_gdata->rx_count = HCI_EVENT_HDR_SIZE;
+-                      type = HCI_EVENT_PKT;
++              case 0x04:
++                      kim_gdata->rx_state = ST_W4_HEADER;
++                      kim_gdata->rx_count = 2;
++                      type = *ptr;
+                       break;
+               default:
+                       pr_info("unknown packet");
+@@ -216,16 +210,18 @@ void kim_int_recv(struct kim_data_s *kim_gdata,
+               ptr++;
+               count--;
+               kim_gdata->rx_skb =
+-                  bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC);
++                      alloc_skb(1024+8, GFP_ATOMIC);
+               if (!kim_gdata->rx_skb) {
+                       pr_err("can't allocate mem for new packet");
+                       kim_gdata->rx_state = ST_W4_PACKET_TYPE;
+                       kim_gdata->rx_count = 0;
+                       return;
+               }
+-              bt_cb(kim_gdata->rx_skb)->pkt_type = type;
++              skb_reserve(kim_gdata->rx_skb, 8);
++              kim_gdata->rx_skb->cb[0] = 4;
++              kim_gdata->rx_skb->cb[1] = 0;
++
+       }
+-      pr_info("done %s", __func__);
+       return;
+ }
+@@ -398,7 +394,7 @@ void st_kim_chip_toggle(enum proto_type type, enum kim_gpio_state state)
+                       gpio_set_value(kim_gdata->gpios[ST_GPS], GPIO_LOW);
+               break;
+-      case ST_MAX:
++      case ST_MAX_CHANNELS:
+       default:
+               break;
+       }
+@@ -416,7 +412,6 @@ void st_kim_recv(void *disc_data, const unsigned char *data, long count)
+       struct st_data_s        *st_gdata = (struct st_data_s *)disc_data;
+       struct kim_data_s       *kim_gdata = st_gdata->kim_data;
+-      pr_info(" %s ", __func__);
+       /* copy to local buffer */
+       if (unlikely(data[4] == 0x01 && data[5] == 0x10 && data[0] == 0x04)) {
+               /* must be the read_ver_cmd */
+@@ -578,7 +573,7 @@ static int kim_toggle_radio(void *data, bool blocked)
+               else
+                       st_kim_chip_toggle(type, KIM_GPIO_ACTIVE);
+       break;
+-      case ST_MAX:
++      case ST_MAX_CHANNELS:
+               pr_err(" wrong proto type ");
+       break;
+       }
+@@ -664,12 +659,13 @@ static int kim_probe(struct platform_device *pdev)
+       /* refer to itself */
+       kim_gdata->core_data->kim_data = kim_gdata;
+-      for (proto = 0; proto < ST_MAX; proto++) {
++      for (proto = 0; proto < ST_MAX_CHANNELS; proto++) {
+               kim_gdata->gpios[proto] = gpios[proto];
+               pr_info(" %ld gpio to be requested", gpios[proto]);
+       }
+-      for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) {
++      for (proto = 0; (proto < ST_MAX_CHANNELS)
++                      && (gpios[proto] != -1); proto++) {
+               /* Claim the Bluetooth/FM/GPIO
+                * nShutdown gpio from the system
+                */
+@@ -704,7 +700,8 @@ static int kim_probe(struct platform_device *pdev)
+       init_completion(&kim_gdata->kim_rcvd);
+       init_completion(&kim_gdata->ldisc_installed);
+-      for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) {
++      for (proto = 0; (proto < ST_MAX_CHANNELS)
++                      && (gpios[proto] != -1); proto++) {
+               /* TODO: should all types be rfkill_type_bt ? */
+               kim_gdata->rf_protos[proto] = proto;
+               kim_gdata->rfkill[proto] = rfkill_alloc(protocol_names[proto],
+@@ -752,7 +749,8 @@ static int kim_remove(struct platform_device *pdev)
+       kim_gdata = dev_get_drvdata(&pdev->dev);
+-      for (proto = 0; (proto < ST_MAX) && (gpios[proto] != -1); proto++) {
++      for (proto = 0; (proto < ST_MAX_CHANNELS)
++              && (gpios[proto] != -1); proto++) {
+               /* Claim the Bluetooth/FM/GPIO
+                * nShutdown gpio from the system
+                */
+diff --git a/include/linux/ti_wilink_st.h b/include/linux/ti_wilink_st.h
+index 4c7be22..1674ca7 100644
+--- a/include/linux/ti_wilink_st.h
++++ b/include/linux/ti_wilink_st.h
+@@ -42,7 +42,7 @@ enum proto_type {
+       ST_BT,
+       ST_FM,
+       ST_GPS,
+-      ST_MAX,
++      ST_MAX_CHANNELS = 16,
+ };
+ /**
+@@ -62,6 +62,17 @@ enum proto_type {
+  * @priv_data: privdate data holder for the protocol drivers, sent
+  *    from the protocol drivers during registration, and sent back on
+  *    reg_complete_cb and recv.
++ * @chnl_id: channel id the protocol driver is interested in, the channel
++ *    id is nothing but the 1st byte of the packet in UART frame.
++ * @max_frame_size: size of the largest frame the protocol can receive.
++ * @hdr_len: length of the header structure of the protocol.
++ * @offset_len_in_hdr: this provides the offset of the length field in the
++ *    header structure of the protocol header, to assist ST to know
++ *    how much to receive, if the data is split across UART frames.
++ * @len_size: whether the length field inside the header is 2 bytes
++ *    or 1 byte.
++ * @reserve: the number of bytes ST needs to reserve in the skb being
++ *    prepared for the protocol driver.
+  */
+ struct st_proto_s {
+       enum proto_type type;
+@@ -70,10 +81,17 @@ struct st_proto_s {
+       void (*reg_complete_cb) (void *, char data);
+       long (*write) (struct sk_buff *skb);
+       void *priv_data;
++
++      unsigned char chnl_id;
++      unsigned short max_frame_size;
++      unsigned char hdr_len;
++      unsigned char offset_len_in_hdr;
++      unsigned char len_size;
++      unsigned char reserve;
+ };
+ extern long st_register(struct st_proto_s *);
+-extern long st_unregister(enum proto_type);
++extern long st_unregister(struct st_proto_s *);
+ /*
+@@ -114,6 +132,7 @@ extern long st_unregister(enum proto_type);
+  * @rx_skb: the skb where all data for a protocol gets accumulated,
+  *    since tty might not call receive when a complete event packet
+  *    is received, the states, count and the skb needs to be maintained.
++ * @rx_chnl: the channel ID for which the data is getting accumalated for.
+  * @txq: the list of skbs which needs to be sent onto the TTY.
+  * @tx_waitq: if the chip is not in AWAKE state, the skbs needs to be queued
+  *    up in here, PM(WAKEUP_IND) data needs to be sent and then the skbs
+@@ -135,10 +154,11 @@ struct st_data_s {
+ #define ST_TX_SENDING 1
+ #define ST_TX_WAKEUP  2
+       unsigned long tx_state;
+-      struct st_proto_s *list[ST_MAX];
++      struct st_proto_s *list[ST_MAX_CHANNELS];
+       unsigned long rx_state;
+       unsigned long rx_count;
+       struct sk_buff *rx_skb;
++      unsigned char rx_chnl;
+       struct sk_buff_head txq, tx_waitq;
+       spinlock_t lock;
+       unsigned char   protos_registered;
+@@ -243,12 +263,12 @@ struct kim_data_s {
+       struct completion kim_rcvd, ldisc_installed;
+       char resp_buffer[30];
+       const struct firmware *fw_entry;
+-      long gpios[ST_MAX];
++      long gpios[ST_MAX_CHANNELS];
+       unsigned long rx_state;
+       unsigned long rx_count;
+       struct sk_buff *rx_skb;
+-      struct rfkill *rfkill[ST_MAX];
+-      enum proto_type rf_protos[ST_MAX];
++      struct rfkill *rfkill[ST_MAX_CHANNELS];
++      enum proto_type rf_protos[ST_MAX_CHANNELS];
+       struct st_data_s *core_data;
+       struct chip_version version;
+ };
+@@ -338,12 +358,8 @@ struct hci_command {
+ /* ST LL receiver states */
+ #define ST_W4_PACKET_TYPE       0
+-#define ST_BT_W4_EVENT_HDR      1
+-#define ST_BT_W4_ACL_HDR        2
+-#define ST_BT_W4_SCO_HDR        3
+-#define ST_BT_W4_DATA           4
+-#define ST_FM_W4_EVENT_HDR      5
+-#define ST_GPS_W4_EVENT_HDR   6
++#define ST_W4_HEADER          1
++#define ST_W4_DATA            2
+ /* ST LL state machines */
+ #define ST_LL_ASLEEP               0
+-- 
+1.6.6.1
+
diff --git a/recipes/linux/linux-omap-2.6.37/wl1271/0015-Bluetooth-btwilink-driver.patch b/recipes/linux/linux-omap-2.6.37/wl1271/0015-Bluetooth-btwilink-driver.patch
new file mode 100644 (file)
index 0000000..f45149f
--- /dev/null
@@ -0,0 +1,463 @@
+From e3948bda11a3a0d938500ffbc7ef43603909cc15 Mon Sep 17 00:00:00 2001
+From: Pavan Savoy <pavan_savoy@ti.com>
+Date: Tue, 4 Jan 2011 10:59:48 +0000
+Subject: [PATCH 15/15] Bluetooth: btwilink driver
+
+-- patch description --
+
+This is the bluetooth protocol driver for the TI WiLink7 chipsets.
+Texas Instrument's WiLink chipsets combine wireless technologies
+like BT, FM, GPS and WLAN onto a single chip.
+
+This Bluetooth driver works on top of the TI_ST shared transport
+line discipline driver which also allows other drivers like
+FM V4L2 and GPS character driver to make use of the same UART interface.
+
+Kconfig and Makefile modifications to enable the Bluetooth
+driver for Texas Instrument's WiLink 7 chipset.
+
+Signed-off-by: Pavan Savoy <pavan_savoy@ti.com>
+---
+ drivers/bluetooth/Kconfig    |   10 +
+ drivers/bluetooth/Makefile   |    1 +
+ drivers/bluetooth/btwilink.c |  397 ++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 408 insertions(+), 0 deletions(-)
+ create mode 100644 drivers/bluetooth/btwilink.c
+
+diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
+index 02deef4..8e0de9a 100644
+--- a/drivers/bluetooth/Kconfig
++++ b/drivers/bluetooth/Kconfig
+@@ -219,4 +219,14 @@ config BT_ATH3K
+         Say Y here to compile support for "Atheros firmware download driver"
+         into the kernel or say M to compile it as module (ath3k).
++config BT_WILINK
++      tristate "Texas Instruments WiLink7 driver"
++      depends on TI_ST
++      help
++        This enables the Bluetooth driver for Texas Instrument's BT/FM/GPS
++        combo devices. This makes use of shared transport line discipline
++        core driver to communicate with the BT core of the combo chip.
++
++        Say Y here to compile support for Texas Instrument's WiLink7 driver
++        into the kernel or say M to compile it as module.
+ endmenu
+diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
+index 71bdf13..f4460f4 100644
+--- a/drivers/bluetooth/Makefile
++++ b/drivers/bluetooth/Makefile
+@@ -18,6 +18,7 @@ obj-$(CONFIG_BT_HCIBTSDIO)   += btsdio.o
+ obj-$(CONFIG_BT_ATH3K)                += ath3k.o
+ obj-$(CONFIG_BT_MRVL)         += btmrvl.o
+ obj-$(CONFIG_BT_MRVL_SDIO)    += btmrvl_sdio.o
++obj-$(CONFIG_BT_WILINK)               += btwilink.o
+ btmrvl-y                      := btmrvl_main.o
+ btmrvl-$(CONFIG_DEBUG_FS)     += btmrvl_debugfs.o
+diff --git a/drivers/bluetooth/btwilink.c b/drivers/bluetooth/btwilink.c
+new file mode 100644
+index 0000000..0201aca
+--- /dev/null
++++ b/drivers/bluetooth/btwilink.c
+@@ -0,0 +1,397 @@
++/*
++ *  Texas Instrument's Bluetooth Driver For Shared Transport.
++ *
++ *  Bluetooth Driver acts as interface between HCI core and
++ *  TI Shared Transport Layer.
++ *
++ *  Copyright (C) 2009-2010 Texas Instruments
++ *  Author: Raja Mani <raja_mani@ti.com>
++ *    Pavan Savoy <pavan_savoy@ti.com>
++ *
++ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
++ *
++ */
++
++#include <linux/platform_device.h>
++#include <net/bluetooth/bluetooth.h>
++#include <net/bluetooth/hci_core.h>
++
++#include <linux/ti_wilink_st.h>
++
++/* Bluetooth Driver Version */
++#define VERSION               "1.0"
++
++/* Number of seconds to wait for registration completion
++ * when ST returns PENDING status.
++ */
++#define BT_REGISTER_TIMEOUT   6000    /* 6 sec */
++
++/**
++ * struct ti_st - driver operation structure
++ * @hdev: hci device pointer which binds to bt driver
++ * @reg_status: ST registration callback status
++ * @st_write: write function provided by the ST driver
++ *    to be used by the driver during send_frame.
++ * @wait_reg_completion - completion sync between ti_st_open
++ *    and ti_st_registration_completion_cb.
++ */
++struct ti_st {
++      struct hci_dev *hdev;
++      char reg_status;
++      long (*st_write) (struct sk_buff *);
++      struct completion wait_reg_completion;
++};
++
++/* Increments HCI counters based on pocket ID (cmd,acl,sco) */
++static inline void ti_st_tx_complete(struct ti_st *hst, int pkt_type)
++{
++      struct hci_dev *hdev = hst->hdev;
++
++      /* Update HCI stat counters */
++      switch (pkt_type) {
++      case HCI_COMMAND_PKT:
++              hdev->stat.cmd_tx++;
++              break;
++
++      case HCI_ACLDATA_PKT:
++              hdev->stat.acl_tx++;
++              break;
++
++      case HCI_SCODATA_PKT:
++              hdev->stat.sco_tx++;
++              break;
++      }
++}
++
++/* ------- Interfaces to Shared Transport ------ */
++
++/* Called by ST layer to indicate protocol registration completion
++ * status.ti_st_open() function will wait for signal from this
++ * API when st_register() function returns ST_PENDING.
++ */
++static void st_registration_completion_cb(void *priv_data, char data)
++{
++      struct ti_st *lhst = priv_data;
++
++      /* Save registration status for use in ti_st_open() */
++      lhst->reg_status = data;
++      /* complete the wait in ti_st_open() */
++      complete(&lhst->wait_reg_completion);
++}
++
++/* Called by Shared Transport layer when receive data is
++ * available */
++static long st_receive(void *priv_data, struct sk_buff *skb)
++{
++      struct ti_st *lhst = priv_data;
++      int err;
++
++      if (!skb)
++              return -EFAULT;
++
++      if (!lhst) {
++              kfree_skb(skb);
++              return -EFAULT;
++      }
++
++      skb->dev = (void *) lhst->hdev;
++
++      /* Forward skb to HCI core layer */
++      err = hci_recv_frame(skb);
++      if (err < 0) {
++              BT_ERR("Unable to push skb to HCI core(%d)", err);
++              return err;
++      }
++
++      lhst->hdev->stat.byte_rx += skb->len;
++
++      return 0;
++}
++
++/* ------- Interfaces to HCI layer ------ */
++/* protocol structure registered with shared transport */
++static struct st_proto_s ti_st_proto[3] = {
++      {
++              .chnl_id = 0x02, /* ACL */
++              .recv = st_receive,
++              .reg_complete_cb = st_registration_completion_cb,
++              .hdr_len = 4,
++              .offset_len_in_hdr = 2,
++              .len_size = 2,
++              .reserve = 8,
++      },
++      {
++              .chnl_id = 0x03, /* SCO */
++              .recv = st_receive,
++              .reg_complete_cb = st_registration_completion_cb,
++              .hdr_len = 3,
++              .offset_len_in_hdr = 2,
++              .len_size = 1,
++              .reserve = 8,
++      },
++      {
++              .chnl_id = 0x04, /* HCI Events */
++              .recv = st_receive,
++              .reg_complete_cb = st_registration_completion_cb,
++              .hdr_len = 2,
++              .offset_len_in_hdr = 1,
++              .len_size = 1,
++              .reserve = 8,
++      },
++};
++
++/* Called from HCI core to initialize the device */
++static int ti_st_open(struct hci_dev *hdev)
++{
++      unsigned long timeleft;
++      struct ti_st *hst;
++      int err, i;
++
++      BT_DBG("%s %p", hdev->name, hdev);
++      if (test_and_set_bit(HCI_RUNNING, &hdev->flags)) {
++              BT_ERR("btwilink already opened");
++              return -EBUSY;
++      }
++
++      /* provide contexts for callbacks from ST */
++      hst = hdev->driver_data;
++
++      for (i = 0; i < 3; i++) {
++              ti_st_proto[i].priv_data = hst;
++              ti_st_proto[i].max_frame_size = HCI_MAX_FRAME_SIZE;
++
++              err = st_register(&ti_st_proto[i]);
++              if (err == -EINPROGRESS) {
++                      /* ST is busy with either protocol
++                       * registration or firmware download.
++                       */
++                      /* Prepare wait-for-completion handler data structures.
++                      */
++                      init_completion(&hst->wait_reg_completion);
++
++                      /* Reset ST registration callback status flag,
++                       * this value will be updated in
++                       * ti_st_registration_completion_cb()
++                       * function whenever it called from ST driver.
++                       */
++                      hst->reg_status = -EINPROGRESS;
++
++                      BT_DBG("waiting for registration "
++                      "completion signal from ST");
++                      timeleft = wait_for_completion_timeout
++                              (&hst->wait_reg_completion,
++                               msecs_to_jiffies(BT_REGISTER_TIMEOUT));
++                      if (!timeleft) {
++                              clear_bit(HCI_RUNNING, &hdev->flags);
++                              BT_ERR("Timeout(%d sec),didn't get reg "
++                                              "completion signal from ST",
++                                              BT_REGISTER_TIMEOUT / 1000);
++                              return -ETIMEDOUT;
++                      }
++
++                      /* Is ST registration callback
++                       * called with ERROR status? */
++                      if (hst->reg_status != 0) {
++                              clear_bit(HCI_RUNNING, &hdev->flags);
++                              BT_ERR("ST registration completed with invalid "
++                                              "status %d", hst->reg_status);
++                              return -EAGAIN;
++                      }
++                      err = 0;
++              } else if (err != 0) {
++                      clear_bit(HCI_RUNNING, &hdev->flags);
++                      BT_ERR("st_register failed %d", err);
++                      return err;
++              }
++              hst->st_write = ti_st_proto[i].write;
++              if (!hst->st_write) {
++                      BT_ERR("undefined ST write function");
++                      clear_bit(HCI_RUNNING, &hdev->flags);
++
++                      /* Undo registration with ST */
++                      err = st_unregister(&ti_st_proto[i]);
++                      if (err)
++                              BT_ERR("st_unregister() failed with "
++                              "error %d", err);
++
++                      hst->st_write = NULL;
++                      /* ti_st_proto.write is filled up by the
++                       * underlying shared transport driver
++                       * upon registration
++                       */
++                      return err;
++              }
++      }
++
++      return err;
++}
++
++/* Close device */
++static int ti_st_close(struct hci_dev *hdev)
++{
++      int err, i;
++      struct ti_st *hst = hdev->driver_data;
++
++      if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags))
++              return 0;
++
++      for (i = 0; i < 3; i++) {
++              /* continue to unregister from transport */
++              err = st_unregister(&ti_st_proto[i]);
++              if (err)
++                      BT_ERR("st_unregister() failed with error %d", err);
++      }
++
++      hst->st_write = NULL;
++
++      return err;
++}
++
++static int ti_st_send_frame(struct sk_buff *skb)
++{
++      struct hci_dev *hdev;
++      struct ti_st *hst;
++      long len;
++
++      hdev = (struct hci_dev *)skb->dev;
++
++      if (!test_bit(HCI_RUNNING, &hdev->flags))
++              return -EBUSY;
++
++      hst = hdev->driver_data;
++
++      /* Prepend skb with frame type */
++      memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
++
++      BT_DBG("%s: type %d len %d", hdev->name, bt_cb(skb)->pkt_type,
++                      skb->len);
++
++      /* Insert skb to shared transport layer's transmit queue.
++       * Freeing skb memory is taken care in shared transport layer,
++       * so don't free skb memory here.
++       */
++      len = hst->st_write(skb);
++      if (len < 0) {
++              kfree_skb(skb);
++              BT_ERR("ST write failed (%ld)", len);
++              /* Try Again, would only fail if UART has gone bad */
++              return -EAGAIN;
++      }
++
++      /* ST accepted our skb. So, Go ahead and do rest */
++      hdev->stat.byte_tx += len;
++      ti_st_tx_complete(hst, bt_cb(skb)->pkt_type);
++
++      return 0;
++}
++
++static void ti_st_destruct(struct hci_dev *hdev)
++{
++      BT_DBG("%s", hdev->name);
++      kfree(hdev->driver_data);
++}
++
++static int bt_ti_probe(struct platform_device *pdev)
++{
++      static struct ti_st *hst;
++      struct hci_dev *hdev;
++      int err;
++
++      hst = kzalloc(sizeof(struct ti_st), GFP_KERNEL);
++      if (!hst)
++              return -ENOMEM;
++
++      /* Expose "hciX" device to user space */
++      hdev = hci_alloc_dev();
++      if (!hdev) {
++              kfree(hst);
++              return -ENOMEM;
++      }
++
++      BT_DBG("hdev %p", hdev);
++
++      hst->hdev = hdev;
++      hdev->bus = HCI_UART;
++      hdev->driver_data = hst;
++      hdev->open = ti_st_open;
++      hdev->close = ti_st_close;
++      hdev->flush = NULL;
++      hdev->send = ti_st_send_frame;
++      hdev->destruct = ti_st_destruct;
++      hdev->owner = THIS_MODULE;
++
++      err = hci_register_dev(hdev);
++      if (err < 0) {
++              BT_ERR("Can't register HCI device error %d", err);
++              kfree(hst);
++              hci_free_dev(hdev);
++              return err;
++      }
++
++      BT_DBG("HCI device registered (hdev %p)", hdev);
++
++      dev_set_drvdata(&pdev->dev, hst);
++      return err;
++}
++
++static int bt_ti_remove(struct platform_device *pdev)
++{
++      struct hci_dev *hdev;
++      struct ti_st *hst = dev_get_drvdata(&pdev->dev);
++
++      if (!hst)
++              return -EFAULT;
++
++      hdev = hst->hdev;
++      ti_st_close(hdev);
++      hci_unregister_dev(hdev);
++
++      hci_free_dev(hdev);
++      kfree(hst);
++
++      dev_set_drvdata(&pdev->dev, NULL);
++      return 0;
++}
++
++static struct platform_driver btwilink_driver = {
++      .probe = bt_ti_probe,
++      .remove = bt_ti_remove,
++      .driver = {
++              .name = "btwilink",
++              .owner = THIS_MODULE,
++      },
++};
++
++/* ------- Module Init/Exit interfaces ------ */
++static int __init btwilink_init(void)
++{
++      BT_INFO("Bluetooth Driver for TI WiLink - Version %s", VERSION);
++
++      return platform_driver_register(&btwilink_driver);
++}
++
++static void __exit btwilink_exit(void)
++{
++      platform_driver_unregister(&btwilink_driver);
++}
++
++module_init(btwilink_init);
++module_exit(btwilink_exit);
++
++/* ------ Module Info ------ */
++
++MODULE_AUTHOR("Raja Mani <raja_mani@ti.com>");
++MODULE_DESCRIPTION("Bluetooth Driver for TI Shared Transport" VERSION);
++MODULE_VERSION(VERSION);
++MODULE_LICENSE("GPL");
+-- 
+1.6.6.1
+
index c6a62b7..7855ebe 100644 (file)
@@ -133,7 +133,23 @@ SRC_URI_append = " \
                   file://dvfs/0020-omap3-Add-basic-support-for-720MHz-part.patch \
                   \
                   file://new/0001-OMAP-Enable-Magic-SysRq-on-serial-console-ttyOx.patch \
-                 "
+                  \
+                  file://wl1271/0001-wl12xx-Read-MAC-address-from-NVS-file-on-HW-startup.patch \
+                  file://wl1271/0002-wl1271-11n-Support-Add-Definitions.patch \
+                  file://wl1271/0003-wl1271-11n-Support-ACX-Commands.patch \
+                  file://wl1271/0004-wl1271-11n-Support-functionality-and-configuration-a.patch \
+                  file://wl1271/0005-wl1271-set-wl-vif-only-if-add_interface-succeeded.patch \
+                  file://wl1271/0006-wl12xx-Unset-bssid-filter-ssid-and-bssid-from-firmwa.patch \
+                  file://wl1271/0007-drivers-media-radio-wl128x-FM-Driver-common-header-f.patch \
+                  file://wl1271/0008-drivers-media-radio-wl128x-FM-Driver-V4L2-sources.patch \
+                  file://wl1271/0009-drivers-media-radio-wl128x-FM-Driver-Common-sources.patch \
+                  file://wl1271/0010-drivers-media-radio-wl128x-FM-driver-RX-sources.patch \
+                  file://wl1271/0011-drivers-media-radio-wl128x-FM-driver-TX-sources.patch \
+                  file://wl1271/0012-drivers-media-radio-wl128x-Kconfig-Makefile-for-wl12.patch \
+                  file://wl1271/0013-drivers-media-radio-Update-Kconfig-and-Makefile-for-.patch \
+                  file://wl1271/0014-drivers-misc-ti-st-change-protocol-parse-logic.patch \
+                  file://wl1271/0015-Bluetooth-btwilink-driver.patch \
+                  "
 
 SRC_URI_append_usrp-e1xx = "\
                   file://usrp/0001-Add-defines-to-set-config-options-in-GPMC-per-CS-con.patch \