From 62c7d085e1f2a1f2b4d89560551eff18d703b3b1 Mon Sep 17 00:00:00 2001 From: Luciano Coelho Date: Thu, 10 Mar 2011 16:42:47 +0200 Subject: wl12xx: add new board_tcxo_clock element to the platform data This new value is a new type of clock setting that is used by wl128x chipsets. Signed-off-by: Luciano Coelho --- include/linux/wl12xx.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/wl12xx.h b/include/linux/wl12xx.h index bebb8efea0a6..eb8aacab8d4e 100644 --- a/include/linux/wl12xx.h +++ b/include/linux/wl12xx.h @@ -24,7 +24,7 @@ #ifndef _LINUX_WL12XX_H #define _LINUX_WL12XX_H -/* The board reference clock values */ +/* Reference clock values */ enum { WL12XX_REFCLOCK_19 = 0, /* 19.2 MHz */ WL12XX_REFCLOCK_26 = 1, /* 26 MHz */ @@ -32,12 +32,25 @@ enum { WL12XX_REFCLOCK_54 = 3, /* 54 MHz */ }; +/* TCXO clock values */ +enum { + WL12XX_TCXOCLOCK_19_2 = 0, /* 19.2MHz */ + WL12XX_TCXOCLOCK_26 = 1, /* 26 MHz */ + WL12XX_TCXOCLOCK_38_4 = 2, /* 38.4MHz */ + WL12XX_TCXOCLOCK_52 = 3, /* 52 MHz */ + WL12XX_TCXOCLOCK_16_368 = 4, /* 16.368 MHz */ + WL12XX_TCXOCLOCK_32_736 = 5, /* 32.736 MHz */ + WL12XX_TCXOCLOCK_16_8 = 6, /* 16.8 MHz */ + WL12XX_TCXOCLOCK_33_6 = 7, /* 33.6 MHz */ +}; + struct wl12xx_platform_data { void (*set_power)(bool enable); /* SDIO only: IRQ number if WLAN_IRQ line is used, 0 for SDIO IRQs */ int irq; bool use_eeprom; int board_ref_clock; + int board_tcxo_clock; }; #ifdef CONFIG_WL12XX_PLATFORM_DATA -- cgit v1.2.3 From d29633b40e6afc6b4276a4e381bc532cc84be104 Mon Sep 17 00:00:00 2001 From: Ido Yariv Date: Thu, 31 Mar 2011 10:06:57 +0200 Subject: wl12xx: Clean up and fix the 128x boot sequence Clean up the boot sequence code & fix the following issues: 1. Always read the registers' values and set the relevant bits instead of zeroing all other bits 2. Handle cases where wl1271_top_reg_read returns an error 3. Verify that the HW can detect the selected clock source 4. Remove 128x PG10 initialization code 5. Configure the MCS PLL to work in HP mode Signed-off-by: Ido Yariv Reviewed-by: Luciano Coelho Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/boot.c | 235 ++++++++++++++++++------------------- drivers/net/wireless/wl12xx/boot.h | 1 + include/linux/wl12xx.h | 10 +- 3 files changed, 123 insertions(+), 123 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/wireless/wl12xx/boot.c b/drivers/net/wireless/wl12xx/boot.c index 34bf2fe47dc7..b5ec2c2b6f78 100644 --- a/drivers/net/wireless/wl12xx/boot.c +++ b/drivers/net/wireless/wl12xx/boot.c @@ -523,137 +523,137 @@ static void wl1271_boot_hw_version(struct wl1271 *wl) wl->quirks |= WL12XX_QUIRK_END_OF_TRANSACTION; } -/* - * WL128x has two clocks input - TCXO and FREF. - * TCXO is the main clock of the device, while FREF is used to sync - * between the GPS and the cellular modem. - * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used - * as the WLAN/BT main clock. - */ -static int wl128x_switch_fref(struct wl1271 *wl, bool *is_ref_clk) +static int wl128x_switch_tcxo_to_fref(struct wl1271 *wl) { - u16 sys_clk_cfg_val; + u16 spare_reg; - /* if working on XTAL-only mode go directly to TCXO TO FREF SWITCH */ - if ((wl->ref_clock == CONF_REF_CLK_38_4_M_XTAL) || - (wl->ref_clock == CONF_REF_CLK_26_M_XTAL)) - return true; + /* Mask bits [2] & [8:4] in the sys_clk_cfg register */ + spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= (BIT(3) | BIT(5) | BIT(6)); + wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); - /* Read clock source FREF or TCXO */ - sys_clk_cfg_val = wl1271_top_reg_read(wl, SYS_CLK_CFG_REG); + /* Enable FREF_CLK_REQ & mux MCS and coex PLLs to FREF */ + wl1271_top_reg_write(wl, SYS_CLK_CFG_REG, + WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF); - if (sys_clk_cfg_val & PRCM_CM_EN_MUX_WLAN_FREF) { - /* if bit 3 is set - working with FREF clock */ - wl1271_debug(DEBUG_BOOT, "working with FREF clock, skip" - " to FREF"); + /* Delay execution for 15msec, to let the HW settle */ + mdelay(15); - *is_ref_clk = true; - } else { - /* if bit 3 is clear - working with TCXO clock */ - wl1271_debug(DEBUG_BOOT, "working with TCXO clock"); - - /* TCXO to FREF switch, check TXCO clock config */ - if ((wl->tcxo_clock != WL12XX_TCXOCLOCK_16_368) && - (wl->tcxo_clock != WL12XX_TCXOCLOCK_32_736)) { - /* - * not 16.368Mhz and not 32.736Mhz - skip to - * configure ELP stage - */ - wl1271_debug(DEBUG_BOOT, "NEW PLL ALGO:" - " TcxoRefClk=%d - not 16.368Mhz and not" - " 32.736Mhz - skip to configure ELP" - " stage", wl->tcxo_clock); - - *is_ref_clk = false; - } else { - wl1271_debug(DEBUG_BOOT, "NEW PLL ALGO:" - "TcxoRefClk=%d - 16.368Mhz or 32.736Mhz" - " - TCXO to FREF switch", - wl->tcxo_clock); + return 0; +} - return true; - } - } +static bool wl128x_is_tcxo_valid(struct wl1271 *wl) +{ + u16 tcxo_detection; + + tcxo_detection = wl1271_top_reg_read(wl, TCXO_CLK_DETECT_REG); + if (tcxo_detection & TCXO_DET_FAILED) + return false; - return false; + return true; } -static int wl128x_boot_clk(struct wl1271 *wl, bool *is_ref_clk) +static bool wl128x_is_fref_valid(struct wl1271 *wl) { - if (wl128x_switch_fref(wl, is_ref_clk)) { - wl1271_debug(DEBUG_BOOT, "XTAL-only mode go directly to" - " TCXO TO FREF SWITCH"); - /* TCXO to FREF switch - for PG2.0 */ - wl1271_top_reg_write(wl, WL_SPARE_REG, - WL_SPARE_MASK_8526); - - wl1271_top_reg_write(wl, SYS_CLK_CFG_REG, - WL_CLK_REQ_TYPE_PG2 | MCS_PLL_CLK_SEL_FREF); - - *is_ref_clk = true; - mdelay(15); - } + u16 fref_detection; - /* Set bit 2 in spare register to avoid illegal access */ - wl1271_top_reg_write(wl, WL_SPARE_REG, WL_SPARE_VAL); + fref_detection = wl1271_top_reg_read(wl, FREF_CLK_DETECT_REG); + if (fref_detection & FREF_CLK_DETECT_FAIL) + return false; - /* working with TCXO clock */ - if ((*is_ref_clk == false) && - ((wl->tcxo_clock == WL12XX_TCXOCLOCK_16_8) || - (wl->tcxo_clock == WL12XX_TCXOCLOCK_33_6))) { - wl1271_debug(DEBUG_BOOT, "16_8_M or 33_6_M TCXO detected"); + return true; +} - /* Manually Configure MCS PLL settings PG2.0 Only */ - wl1271_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL); - wl1271_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL); - wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, - MCS_PLL_CONFIG_REG_VAL); - } else { - int pll_config; - u16 mcs_pll_config_val; +static int wl128x_manually_configure_mcs_pll(struct wl1271 *wl) +{ + wl1271_top_reg_write(wl, MCS_PLL_M_REG, MCS_PLL_M_REG_VAL); + wl1271_top_reg_write(wl, MCS_PLL_N_REG, MCS_PLL_N_REG_VAL); + wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, MCS_PLL_CONFIG_REG_VAL); - /* - * Configure MCS PLL settings to FREF Freq - * Set the values that determine the time elapse since the PLL's - * get their enable signal until the lock indication is set - */ - wl1271_top_reg_write(wl, PLL_LOCK_COUNTERS_REG, - PLL_LOCK_COUNTERS_COEX | PLL_LOCK_COUNTERS_MCS); + return 0; +} - mcs_pll_config_val = wl1271_top_reg_read(wl, - MCS_PLL_CONFIG_REG); - /* - * Set the MCS PLL input frequency value according to the - * reference clock value detected/read - */ - if (*is_ref_clk == false) { - if ((wl->tcxo_clock == WL12XX_TCXOCLOCK_19_2) || - (wl->tcxo_clock == WL12XX_TCXOCLOCK_38_4)) - pll_config = 1; - else if ((wl->tcxo_clock == WL12XX_TCXOCLOCK_26) - || - (wl->tcxo_clock == WL12XX_TCXOCLOCK_52)) - pll_config = 2; - else - return -EINVAL; - } else { - if ((wl->ref_clock == CONF_REF_CLK_19_2_E) || - (wl->ref_clock == CONF_REF_CLK_38_4_E)) - pll_config = 1; - else if ((wl->ref_clock == CONF_REF_CLK_26_E) || - (wl->ref_clock == CONF_REF_CLK_52_E)) - pll_config = 2; - else - return -EINVAL; - } +static int wl128x_configure_mcs_pll(struct wl1271 *wl, int clk) +{ + u16 spare_reg; + u16 pll_config; + u8 input_freq; + + /* Mask bits [3:1] in the sys_clk_cfg register */ + spare_reg = wl1271_top_reg_read(wl, WL_SPARE_REG); + if (spare_reg == 0xFFFF) + return -EFAULT; + spare_reg |= BIT(2); + wl1271_top_reg_write(wl, WL_SPARE_REG, spare_reg); + + /* Handle special cases of the TCXO clock */ + if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_8 || + wl->tcxo_clock == WL12XX_TCXOCLOCK_33_6) + return wl128x_manually_configure_mcs_pll(wl); + + /* Set the input frequency according to the selected clock source */ + input_freq = (clk & 1) + 1; + + pll_config = wl1271_top_reg_read(wl, MCS_PLL_CONFIG_REG); + if (pll_config == 0xFFFF) + return -EFAULT; + pll_config |= (input_freq << MCS_SEL_IN_FREQ_SHIFT); + pll_config |= MCS_PLL_ENABLE_HP; + wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, pll_config); - mcs_pll_config_val |= (pll_config << (MCS_SEL_IN_FREQ_SHIFT)) & - (MCS_SEL_IN_FREQ_MASK); - wl1271_top_reg_write(wl, MCS_PLL_CONFIG_REG, - mcs_pll_config_val); + return 0; +} + +/* + * WL128x has two clocks input - TCXO and FREF. + * TCXO is the main clock of the device, while FREF is used to sync + * between the GPS and the cellular modem. + * In cases where TCXO is 32.736MHz or 16.368MHz, the FREF will be used + * as the WLAN/BT main clock. + */ +static int wl128x_boot_clk(struct wl1271 *wl, int *selected_clock) +{ + u16 sys_clk_cfg; + + /* For XTAL-only modes, FREF will be used after switching from TCXO */ + if (wl->ref_clock == WL12XX_REFCLOCK_26_XTAL || + wl->ref_clock == WL12XX_REFCLOCK_38_XTAL) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; } - return 0; + /* Query the HW, to determine which clock source we should use */ + sys_clk_cfg = wl1271_top_reg_read(wl, SYS_CLK_CFG_REG); + if (sys_clk_cfg == 0xFFFF) + return -EINVAL; + if (sys_clk_cfg & PRCM_CM_EN_MUX_WLAN_FREF) + goto fref_clk; + + /* If TCXO is either 32.736MHz or 16.368MHz, switch to FREF */ + if (wl->tcxo_clock == WL12XX_TCXOCLOCK_16_368 || + wl->tcxo_clock == WL12XX_TCXOCLOCK_32_736) { + if (!wl128x_switch_tcxo_to_fref(wl)) + return -EINVAL; + goto fref_clk; + } + + /* TCXO clock is selected */ + if (!wl128x_is_tcxo_valid(wl)) + return -EINVAL; + *selected_clock = wl->tcxo_clock; + goto config_mcs_pll; + +fref_clk: + /* FREF clock is selected */ + if (!wl128x_is_fref_valid(wl)) + return -EINVAL; + *selected_clock = wl->ref_clock; + +config_mcs_pll: + return wl128x_configure_mcs_pll(wl, *selected_clock); } static int wl127x_boot_clk(struct wl1271 *wl) @@ -713,10 +713,10 @@ int wl1271_load_firmware(struct wl1271 *wl) { int ret = 0; u32 tmp, clk; - bool is_ref_clk = false; + int selected_clock = -1; if (wl->chip.id == CHIP_ID_1283_PG20) { - ret = wl128x_boot_clk(wl, &is_ref_clk); + ret = wl128x_boot_clk(wl, &selected_clock); if (ret < 0) goto out; } else { @@ -741,10 +741,7 @@ int wl1271_load_firmware(struct wl1271 *wl) wl1271_debug(DEBUG_BOOT, "clk2 0x%x", clk); if (wl->chip.id == CHIP_ID_1283_PG20) { - if (is_ref_clk == false) - clk |= ((wl->tcxo_clock & 0x3) << 1) << 4; - else - clk |= ((wl->ref_clock & 0x3) << 1) << 4; + clk |= ((selected_clock & 0x3) << 1) << 4; } else { clk |= (wl->ref_clock << 1) << 4; } diff --git a/drivers/net/wireless/wl12xx/boot.h b/drivers/net/wireless/wl12xx/boot.h index 1f5ee31dc0b1..d9de64ac1442 100644 --- a/drivers/net/wireless/wl12xx/boot.h +++ b/drivers/net/wireless/wl12xx/boot.h @@ -107,6 +107,7 @@ struct wl1271_static_data { #define MCS_SEL_IN_FREQ_MASK 0x0070 #define MCS_SEL_IN_FREQ_SHIFT 4 #define MCS_PLL_CONFIG_REG_VAL 0x73 +#define MCS_PLL_ENABLE_HP (BIT(0) | BIT(1)) #define MCS_PLL_M_REG 0xD94 #define MCS_PLL_N_REG 0xD96 diff --git a/include/linux/wl12xx.h b/include/linux/wl12xx.h index eb8aacab8d4e..c1a743ea7470 100644 --- a/include/linux/wl12xx.h +++ b/include/linux/wl12xx.h @@ -26,10 +26,12 @@ /* Reference clock values */ enum { - WL12XX_REFCLOCK_19 = 0, /* 19.2 MHz */ - WL12XX_REFCLOCK_26 = 1, /* 26 MHz */ - WL12XX_REFCLOCK_38 = 2, /* 38.4 MHz */ - WL12XX_REFCLOCK_54 = 3, /* 54 MHz */ + WL12XX_REFCLOCK_19 = 0, /* 19.2 MHz */ + WL12XX_REFCLOCK_26 = 1, /* 26 MHz */ + WL12XX_REFCLOCK_38 = 2, /* 38.4 MHz */ + WL12XX_REFCLOCK_52 = 3, /* 52 MHz */ + WL12XX_REFCLOCK_38_XTAL = 4, /* 38.4 MHz, XTAL */ + WL12XX_REFCLOCK_26_XTAL = 5, /* 26 MHz, XTAL */ }; /* TCXO clock values */ -- cgit v1.2.3 From 341b7cde6ccc60672fcd7fc84dd24a1b7c0b8d94 Mon Sep 17 00:00:00 2001 From: Ido Yariv Date: Thu, 31 Mar 2011 10:07:01 +0200 Subject: wl12xx: Handle platforms without level trigger interrupts Some platforms are incapable of triggering on level interrupts. Add a platform quirks member in the platform data structure, as well as an edge interrupt quirk which can be set on such platforms. When the interrupt is requested with IRQF_TRIGGER_RISING, IRQF_ONESHOT cannot be used, as we might miss interrupts that occur after the FW status is cleared and before the threaded interrupt handler exits. Moreover, when IRQF_ONESHOT is not set, iterating more than once in the threaded interrupt handler introduces a few race conditions between this handler and the hardirq handler. Currently this is worked around by limiting the loop to one iteration only. This workaround has an impact on performance. To remove to this restriction, the race conditions will need to be addressed. Signed-off-by: Ido Yariv Signed-off-by: Luciano Coelho --- drivers/net/wireless/wl12xx/main.c | 9 +++++++++ drivers/net/wireless/wl12xx/sdio.c | 9 ++++++++- drivers/net/wireless/wl12xx/spi.c | 9 ++++++++- drivers/net/wireless/wl12xx/wl12xx.h | 3 +++ include/linux/wl12xx.h | 4 ++++ 5 files changed, 32 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c index 1feb9551ef8f..7126506611c1 100644 --- a/drivers/net/wireless/wl12xx/main.c +++ b/drivers/net/wireless/wl12xx/main.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "wl12xx.h" #include "wl12xx_80211.h" @@ -719,6 +720,13 @@ irqreturn_t wl1271_irq(int irq, void *cookie) set_bit(WL1271_FLAG_TX_PENDING, &wl->flags); cancel_work_sync(&wl->tx_work); + /* + * In case edge triggered interrupt must be used, we cannot iterate + * more than once without introducing race conditions with the hardirq. + */ + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + loopcount = 1; + mutex_lock(&wl->mutex); wl1271_debug(DEBUG_IRQ, "IRQ work"); @@ -3648,6 +3656,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void) wl->ap_ps_map = 0; wl->ap_fw_ps_map = 0; wl->quirks = 0; + wl->platform_quirks = 0; memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map)); for (i = 0; i < ACX_TX_DESCRIPTORS; i++) diff --git a/drivers/net/wireless/wl12xx/sdio.c b/drivers/net/wireless/wl12xx/sdio.c index 8246e9de4306..bcd4ad7ba90d 100644 --- a/drivers/net/wireless/wl12xx/sdio.c +++ b/drivers/net/wireless/wl12xx/sdio.c @@ -220,6 +220,7 @@ static int __devinit wl1271_probe(struct sdio_func *func, struct ieee80211_hw *hw; const struct wl12xx_platform_data *wlan_data; struct wl1271 *wl; + unsigned long irqflags; int ret; /* We are only able to handle the wlan function */ @@ -251,9 +252,15 @@ static int __devinit wl1271_probe(struct sdio_func *func, wl->irq = wlan_data->irq; wl->ref_clock = wlan_data->board_ref_clock; wl->tcxo_clock = wlan_data->board_tcxo_clock; + wl->platform_quirks = wlan_data->platform_quirks; + + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + irqflags, DRIVER_NAME, wl); if (ret < 0) { wl1271_error("request_irq() failed: %d", ret); diff --git a/drivers/net/wireless/wl12xx/spi.c b/drivers/net/wireless/wl12xx/spi.c index 7b82b5f0e490..51662bb68019 100644 --- a/drivers/net/wireless/wl12xx/spi.c +++ b/drivers/net/wireless/wl12xx/spi.c @@ -364,6 +364,7 @@ static int __devinit wl1271_probe(struct spi_device *spi) struct wl12xx_platform_data *pdata; struct ieee80211_hw *hw; struct wl1271 *wl; + unsigned long irqflags; int ret; pdata = spi->dev.platform_data; @@ -402,6 +403,12 @@ static int __devinit wl1271_probe(struct spi_device *spi) wl->ref_clock = pdata->board_ref_clock; wl->tcxo_clock = pdata->board_tcxo_clock; + wl->platform_quirks = pdata->platform_quirks; + + if (wl->platform_quirks & WL12XX_PLATFORM_QUIRK_EDGE_IRQ) + irqflags = IRQF_TRIGGER_RISING; + else + irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT; wl->irq = spi->irq; if (wl->irq < 0) { @@ -411,7 +418,7 @@ static int __devinit wl1271_probe(struct spi_device *spi) } ret = request_threaded_irq(wl->irq, wl1271_hardirq, wl1271_irq, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + irqflags, DRIVER_NAME, wl); if (ret < 0) { wl1271_error("request_irq() failed: %d", ret); diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h index 9ccbcfdd0802..fb2b79fa42b4 100644 --- a/drivers/net/wireless/wl12xx/wl12xx.h +++ b/drivers/net/wireless/wl12xx/wl12xx.h @@ -579,6 +579,9 @@ struct wl1271 { /* Quirks of specific hardware revisions */ unsigned int quirks; + + /* Platform limitations */ + unsigned int platform_quirks; }; struct wl1271_station { diff --git a/include/linux/wl12xx.h b/include/linux/wl12xx.h index c1a743ea7470..4b697395326e 100644 --- a/include/linux/wl12xx.h +++ b/include/linux/wl12xx.h @@ -53,8 +53,12 @@ struct wl12xx_platform_data { bool use_eeprom; int board_ref_clock; int board_tcxo_clock; + unsigned long platform_quirks; }; +/* Platform does not support level trigger interrupts */ +#define WL12XX_PLATFORM_QUIRK_EDGE_IRQ BIT(0) + #ifdef CONFIG_WL12XX_PLATFORM_DATA int wl12xx_set_platform_data(const struct wl12xx_platform_data *data); -- cgit v1.2.3