|  | /* | 
|  | * Broadcom SPI over PCI-SPI Host Controller, low-level hardware driver | 
|  | * | 
|  | * Copyright (C) 1999-2010, Broadcom Corporation | 
|  | * | 
|  | *      Unless you and Broadcom execute a separate written software license | 
|  | * agreement governing use of this software, this software is licensed to you | 
|  | * under the terms of the GNU General Public License version 2 (the "GPL"), | 
|  | * available at http://www.broadcom.com/licenses/GPLv2.php, with the | 
|  | * following added to such license: | 
|  | * | 
|  | *      As a special exception, the copyright holders of this software give you | 
|  | * permission to link this software with independent modules, and to copy and | 
|  | * distribute the resulting executable under terms of your choice, provided that | 
|  | * you also meet, for each linked independent module, the terms and conditions of | 
|  | * the license of that module.  An independent module is a module which is not | 
|  | * derived from this software.  The special exception does not apply to any | 
|  | * modifications of the software. | 
|  | * | 
|  | *      Notwithstanding the above, under no circumstances may you combine this | 
|  | * software in any way with any other Broadcom software provided under a license | 
|  | * other than the GPL, without Broadcom's express prior written consent. | 
|  | * | 
|  | * $Id: bcmpcispi.c,v 1.22.2.4.4.5.6.1 2010/08/13 00:26:05 Exp $ | 
|  | */ | 
|  |  | 
|  | #include <typedefs.h> | 
|  | #include <bcmutils.h> | 
|  |  | 
|  | #include <sdio.h>		/* SDIO Specs */ | 
|  | #include <bcmsdbus.h>		/* bcmsdh to/from specific controller APIs */ | 
|  | #include <sdiovar.h>		/* to get msglevel bit values */ | 
|  |  | 
|  | #include <pcicfg.h> | 
|  | #include <bcmsdspi.h> | 
|  | #include <bcmspi.h> | 
|  | #include <bcmpcispi.h>		/* BRCM PCI-SPI Host Controller Register definitions */ | 
|  |  | 
|  |  | 
|  | /* ndis_osl.h needs to do a runtime check of the osh to map | 
|  | * R_REG/W_REG to bus specific access similar to linux_osl.h. | 
|  | * Until then... | 
|  | */ | 
|  | /* linux */ | 
|  |  | 
|  | #define SPIPCI_RREG R_REG | 
|  | #define SPIPCI_WREG W_REG | 
|  |  | 
|  |  | 
|  | #define	SPIPCI_ANDREG(osh, r, v) SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) & (v))) | 
|  | #define	SPIPCI_ORREG(osh, r, v)	SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) | (v))) | 
|  |  | 
|  |  | 
|  | int bcmpcispi_dump = 0;		/* Set to dump complete trace of all SPI bus transactions */ | 
|  |  | 
|  | typedef struct spih_info_ { | 
|  | uint		bar0;		/* BAR0 of PCI Card */ | 
|  | uint		bar1;		/* BAR1 of PCI Card */ | 
|  | osl_t 		*osh;		/* osh handle */ | 
|  | spih_pciregs_t	*pciregs;	/* PCI Core Registers */ | 
|  | spih_regs_t	*regs;		/* SPI Controller Registers */ | 
|  | uint8		rev;		/* PCI Card Revision ID */ | 
|  | } spih_info_t; | 
|  |  | 
|  |  | 
|  | /* Attach to PCI-SPI Host Controller Hardware */ | 
|  | bool | 
|  | spi_hw_attach(sdioh_info_t *sd) | 
|  | { | 
|  | osl_t *osh; | 
|  | spih_info_t *si; | 
|  |  | 
|  | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  |  | 
|  | osh = sd->osh; | 
|  |  | 
|  | if ((si = (spih_info_t *)MALLOC(osh, sizeof(spih_info_t))) == NULL) { | 
|  | sd_err(("%s: out of memory, malloced %d bytes\n", __FUNCTION__, MALLOCED(osh))); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | bzero(si, sizeof(spih_info_t)); | 
|  |  | 
|  | sd->controller = si; | 
|  |  | 
|  | si->osh = sd->osh; | 
|  | si->rev = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_REV, 4) & 0xFF; | 
|  |  | 
|  | if (si->rev < 3) { | 
|  | sd_err(("Host controller %d not supported, please upgrade to rev >= 3\n", si->rev)); | 
|  | MFREE(osh, si, sizeof(spih_info_t)); | 
|  | return (FALSE); | 
|  | } | 
|  |  | 
|  | sd_err(("Attaching to Generic PCI SPI Host Controller Rev %d\n", si->rev)); | 
|  |  | 
|  | /* FPGA Revision < 3 not supported by driver anymore. */ | 
|  | ASSERT(si->rev >= 3); | 
|  |  | 
|  | si->bar0 = sd->bar0; | 
|  |  | 
|  | /* Rev < 10 PciSpiHost has 2 BARs: | 
|  | *    BAR0 = PCI Core Registers | 
|  | *    BAR1 = PciSpiHost Registers (all other cores on backplane) | 
|  | * | 
|  | * Rev 10 and up use a different PCI core which only has a single | 
|  | * BAR0 which contains the PciSpiHost Registers. | 
|  | */ | 
|  | if (si->rev < 10) { | 
|  | si->pciregs = (spih_pciregs_t *)spi_reg_map(osh, | 
|  | (uintptr)si->bar0, | 
|  | sizeof(spih_pciregs_t)); | 
|  | sd_err(("Mapped PCI Core regs to BAR0 at %p\n", si->pciregs)); | 
|  |  | 
|  | si->bar1 = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR1, 4); | 
|  | si->regs = (spih_regs_t *)spi_reg_map(osh, | 
|  | (uintptr)si->bar1, | 
|  | sizeof(spih_regs_t)); | 
|  | sd_err(("Mapped SPI Controller regs to BAR1 at %p\n", si->regs)); | 
|  | } else { | 
|  | si->regs = (spih_regs_t *)spi_reg_map(osh, | 
|  | (uintptr)si->bar0, | 
|  | sizeof(spih_regs_t)); | 
|  | sd_err(("Mapped SPI Controller regs to BAR0 at %p\n", si->regs)); | 
|  | si->pciregs = NULL; | 
|  | } | 
|  | /* Enable SPI Controller, 16.67MHz SPI Clock */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_ctrl, 0x000000d1); | 
|  |  | 
|  | /* Set extended feature register to defaults */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_ext, 0x00000000); | 
|  |  | 
|  | /* Set GPIO CS# High (de-asserted) */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_gpio_data, SPIH_CS); | 
|  |  | 
|  | /* set GPIO[0] to output for CS# */ | 
|  | /* set GPIO[1] to output for power control */ | 
|  | /* set GPIO[2] to input for card detect */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_gpio_ctrl, (SPIH_CS | SPIH_SLOT_POWER)); | 
|  |  | 
|  | /* Clear out the Read FIFO in case there is any stuff left in there from a previous run. */ | 
|  | while ((SPIPCI_RREG(osh, &si->regs->spih_stat) & SPIH_RFEMPTY) == 0) { | 
|  | SPIPCI_RREG(osh, &si->regs->spih_data); | 
|  | } | 
|  |  | 
|  | /* Wait for power to stabilize to the SDIO Card (100msec was insufficient) */ | 
|  | OSL_DELAY(250000); | 
|  |  | 
|  | /* Check card detect on FPGA Revision >= 4 */ | 
|  | if (si->rev >= 4) { | 
|  | if (SPIPCI_RREG(osh, &si->regs->spih_gpio_data) & SPIH_CARD_DETECT) { | 
|  | sd_err(("%s: no card detected in SD slot\n", __FUNCTION__)); | 
|  | spi_reg_unmap(osh, (uintptr)si->regs, sizeof(spih_regs_t)); | 
|  | if (si->pciregs) { | 
|  | spi_reg_unmap(osh, (uintptr)si->pciregs, sizeof(spih_pciregs_t)); | 
|  | } | 
|  | MFREE(osh, si, sizeof(spih_info_t)); | 
|  | return FALSE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Interrupts are level sensitive */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_int_edge, 0x80000000); | 
|  |  | 
|  | /* Interrupts are active low. */ | 
|  | SPIPCI_WREG(osh, &si->regs->spih_int_pol, 0x40000004); | 
|  |  | 
|  | /* Enable interrupts through PCI Core. */ | 
|  | if (si->pciregs) { | 
|  | SPIPCI_WREG(osh, &si->pciregs->ICR, PCI_INT_PROP_EN); | 
|  | } | 
|  |  | 
|  | sd_trace(("%s: exit\n", __FUNCTION__)); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Detach and return PCI-SPI Hardware to unconfigured state */ | 
|  | bool | 
|  | spi_hw_detach(sdioh_info_t *sd) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  | spih_pciregs_t *pciregs = si->pciregs; | 
|  |  | 
|  | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  |  | 
|  | SPIPCI_WREG(osh, ®s->spih_ctrl, 0x00000010); | 
|  | SPIPCI_WREG(osh, ®s->spih_gpio_ctrl, 0x00000000);	/* Disable GPIO for CS# */ | 
|  | SPIPCI_WREG(osh, ®s->spih_int_mask, 0x00000000);	/* Clear Intmask */ | 
|  | SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAF); | 
|  | SPIPCI_WREG(osh, ®s->spih_int_edge, 0x00000000); | 
|  | SPIPCI_WREG(osh, ®s->spih_int_pol, 0x00000000); | 
|  | SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAD); | 
|  |  | 
|  | /* Disable interrupts through PCI Core. */ | 
|  | if (si->pciregs) { | 
|  | SPIPCI_WREG(osh, &pciregs->ICR, 0x00000000); | 
|  | spi_reg_unmap(osh, (uintptr)pciregs, sizeof(spih_pciregs_t)); | 
|  | } | 
|  | spi_reg_unmap(osh, (uintptr)regs, sizeof(spih_regs_t)); | 
|  |  | 
|  | MFREE(osh, si, sizeof(spih_info_t)); | 
|  |  | 
|  | sd->controller = NULL; | 
|  |  | 
|  | sd_trace(("%s: exit\n", __FUNCTION__)); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Switch between internal (PCI) and external clock oscillator */ | 
|  | static bool | 
|  | sdspi_switch_clock(sdioh_info_t *sd, bool ext_clk) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  |  | 
|  | /* Switch to desired clock, and reset the PLL. */ | 
|  | SPIPCI_WREG(osh, ®s->spih_pll_ctrl, ext_clk ? SPIH_EXT_CLK : 0); | 
|  |  | 
|  | SPINWAIT(((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) | 
|  | != SPIH_PLL_LOCKED), 1000); | 
|  | if ((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) != SPIH_PLL_LOCKED) { | 
|  | sd_err(("%s: timeout waiting for PLL to lock\n", __FUNCTION__)); | 
|  | return (FALSE); | 
|  | } | 
|  | return (TRUE); | 
|  |  | 
|  | } | 
|  |  | 
|  | /* Configure PCI-SPI Host Controller's SPI Clock rate as a divisor into the | 
|  | * base clock rate.  The base clock is either the PCI Clock (33MHz) or the | 
|  | * external clock oscillator at U17 on the PciSpiHost. | 
|  | */ | 
|  | bool | 
|  | spi_start_clock(sdioh_info_t *sd, uint16 div) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  | uint32 t, espr, disp; | 
|  | uint32 disp_xtal_freq; | 
|  | bool	ext_clock = FALSE; | 
|  | char disp_string[5]; | 
|  |  | 
|  | if (div > 2048) { | 
|  | sd_err(("%s: divisor %d too large; using max of 2048\n", __FUNCTION__, div)); | 
|  | div = 2048; | 
|  | } else if (div & (div - 1)) {	/* Not a power of 2? */ | 
|  | /* Round up to a power of 2 */ | 
|  | while ((div + 1) & div) | 
|  | div |= div >> 1; | 
|  | div++; | 
|  | } | 
|  |  | 
|  | /* For FPGA Rev >= 5, the use of an external clock oscillator is supported. | 
|  | * If the oscillator is populated, use it to provide the SPI base clock, | 
|  | * otherwise, default to the PCI clock as the SPI base clock. | 
|  | */ | 
|  | if (si->rev >= 5) { | 
|  | uint32 clk_tick; | 
|  | /* Enable the External Clock Oscillator as PLL clock source. */ | 
|  | if (!sdspi_switch_clock(sd, TRUE)) { | 
|  | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | } | 
|  |  | 
|  | /* Check to make sure the external clock is running.  If not, then it | 
|  | * is not populated on the card, so we will default to the PCI clock. | 
|  | */ | 
|  | clk_tick = SPIPCI_RREG(osh, ®s->spih_clk_count); | 
|  | if (clk_tick == SPIPCI_RREG(osh, ®s->spih_clk_count)) { | 
|  |  | 
|  | /* Switch back to the PCI clock as the clock source. */ | 
|  | if (!sdspi_switch_clock(sd, FALSE)) { | 
|  | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | } | 
|  | } else { | 
|  | ext_clock = TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Hack to allow hot-swapping oscillators: | 
|  | * 1. Force PCI clock as clock source, using sd_divisor of 0. | 
|  | * 2. Swap oscillator | 
|  | * 3. Set desired sd_divisor (will switch to external oscillator as clock source. | 
|  | */ | 
|  | if (div == 0) { | 
|  | ext_clock = FALSE; | 
|  | div = 2; | 
|  |  | 
|  | /* Select PCI clock as the clock source. */ | 
|  | if (!sdspi_switch_clock(sd, FALSE)) { | 
|  | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | } | 
|  |  | 
|  | sd_err(("%s: Ok to hot-swap oscillators.\n", __FUNCTION__)); | 
|  | } | 
|  |  | 
|  | /* If using the external oscillator, read the clock frequency from the controller | 
|  | * The value read is in units of 10000Hz, and it's not a nice round number because | 
|  | * it is calculated by the FPGA.  So to make up for that, we round it off. | 
|  | */ | 
|  | if (ext_clock == TRUE) { | 
|  | uint32 xtal_freq; | 
|  |  | 
|  | OSL_DELAY(1000); | 
|  | xtal_freq = SPIPCI_RREG(osh, ®s->spih_xtal_freq) * 10000; | 
|  |  | 
|  | sd_info(("%s: Oscillator is %dHz\n", __FUNCTION__, xtal_freq)); | 
|  |  | 
|  |  | 
|  | disp_xtal_freq = xtal_freq / 10000; | 
|  |  | 
|  | /* Round it off to a nice number. */ | 
|  | if ((disp_xtal_freq % 100) > 50) { | 
|  | disp_xtal_freq += 100; | 
|  | } | 
|  |  | 
|  | disp_xtal_freq = (disp_xtal_freq / 100) * 100; | 
|  | } else { | 
|  | sd_err(("%s: no external oscillator installed, using PCI clock.\n", __FUNCTION__)); | 
|  | disp_xtal_freq = 3333; | 
|  | } | 
|  |  | 
|  | /* Convert the SPI Clock frequency to BCD format. */ | 
|  | sprintf(disp_string, "%04d", disp_xtal_freq / div); | 
|  |  | 
|  | disp  = (disp_string[0] - '0') << 12; | 
|  | disp |= (disp_string[1] - '0') << 8; | 
|  | disp |= (disp_string[2] - '0') << 4; | 
|  | disp |= (disp_string[3] - '0'); | 
|  |  | 
|  | /* Select the correct ESPR register value based on the divisor. */ | 
|  | switch (div) { | 
|  | case 1:		espr = 0x0; break; | 
|  | case 2:		espr = 0x1; break; | 
|  | case 4:		espr = 0x2; break; | 
|  | case 8:		espr = 0x5; break; | 
|  | case 16:	espr = 0x3; break; | 
|  | case 32:	espr = 0x4; break; | 
|  | case 64:	espr = 0x6; break; | 
|  | case 128:	espr = 0x7; break; | 
|  | case 256:	espr = 0x8; break; | 
|  | case 512:	espr = 0x9; break; | 
|  | case 1024:	espr = 0xa; break; | 
|  | case 2048:	espr = 0xb; break; | 
|  | default:	espr = 0x0; ASSERT(0); break; | 
|  | } | 
|  |  | 
|  | t = SPIPCI_RREG(osh, ®s->spih_ctrl); | 
|  | t &= ~3; | 
|  | t |= espr & 3; | 
|  | SPIPCI_WREG(osh, ®s->spih_ctrl, t); | 
|  |  | 
|  | t = SPIPCI_RREG(osh, ®s->spih_ext); | 
|  | t &= ~3; | 
|  | t |= (espr >> 2) & 3; | 
|  | SPIPCI_WREG(osh, ®s->spih_ext, t); | 
|  |  | 
|  | SPIPCI_WREG(osh, ®s->spih_hex_disp, disp); | 
|  |  | 
|  | /* For Rev 8, writing to the PLL_CTRL register resets | 
|  | * the PLL, and it can re-acquire in 200uS.  For | 
|  | * Rev 7 and older, we use a software delay to allow | 
|  | * the PLL to re-acquire, which takes more than 2mS. | 
|  | */ | 
|  | if (si->rev < 8) { | 
|  | /* Wait for clock to settle. */ | 
|  | OSL_DELAY(5000); | 
|  | } | 
|  |  | 
|  | sd_info(("%s: SPI_CTRL=0x%08x SPI_EXT=0x%08x\n", | 
|  | __FUNCTION__, | 
|  | SPIPCI_RREG(osh, ®s->spih_ctrl), | 
|  | SPIPCI_RREG(osh, ®s->spih_ext))); | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Configure PCI-SPI Host Controller High-Speed Clocking mode setting */ | 
|  | bool | 
|  | spi_controller_highspeed_mode(sdioh_info_t *sd, bool hsmode) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  |  | 
|  | if (si->rev >= 10) { | 
|  | if (hsmode) { | 
|  | SPIPCI_ORREG(osh, ®s->spih_ext, 0x10); | 
|  | } else { | 
|  | SPIPCI_ANDREG(osh, ®s->spih_ext, ~0x10); | 
|  | } | 
|  | } | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Disable device interrupt */ | 
|  | void | 
|  | spi_devintr_off(sdioh_info_t *sd) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  |  | 
|  | sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); | 
|  | if (sd->use_client_ints) { | 
|  | sd->intmask &= ~SPIH_DEV_INTR; | 
|  | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask);	/* Clear Intmask */ | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Enable device interrupt */ | 
|  | void | 
|  | spi_devintr_on(sdioh_info_t *sd) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  |  | 
|  | ASSERT(sd->lockcount == 0); | 
|  | sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); | 
|  | if (sd->use_client_ints) { | 
|  | if (SPIPCI_RREG(osh, ®s->spih_ctrl) & 0x02) { | 
|  | /* Ack in case one was pending but is no longer... */ | 
|  | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); | 
|  | } | 
|  | sd->intmask |= SPIH_DEV_INTR; | 
|  | /* Set device intr in Intmask */ | 
|  | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check to see if an interrupt belongs to the PCI-SPI Host or a SPI Device */ | 
|  | bool | 
|  | spi_check_client_intr(sdioh_info_t *sd, int *is_dev_intr) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  | bool ours = FALSE; | 
|  |  | 
|  | uint32 raw_int, cur_int; | 
|  | ASSERT(sd); | 
|  |  | 
|  | if (is_dev_intr) | 
|  | *is_dev_intr = FALSE; | 
|  | raw_int = SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | cur_int = raw_int & sd->intmask; | 
|  | if (cur_int & SPIH_DEV_INTR) { | 
|  | if (sd->client_intr_enabled && sd->use_client_ints) { | 
|  | sd->intrcount++; | 
|  | ASSERT(sd->intr_handler); | 
|  | ASSERT(sd->intr_handler_arg); | 
|  | (sd->intr_handler)(sd->intr_handler_arg); | 
|  | if (is_dev_intr) | 
|  | *is_dev_intr = TRUE; | 
|  | } else { | 
|  | sd_trace(("%s: Not ready for intr: enabled %d, handler 0x%p\n", | 
|  | __FUNCTION__, sd->client_intr_enabled, sd->intr_handler)); | 
|  | } | 
|  | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); | 
|  | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | ours = TRUE; | 
|  | } else if (cur_int & SPIH_CTLR_INTR) { | 
|  | /* Interrupt is from SPI FIFO... just clear and ack it... */ | 
|  | sd_trace(("%s: SPI CTLR interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | __FUNCTION__, raw_int, cur_int)); | 
|  |  | 
|  | /* Clear the interrupt in the SPI_STAT register */ | 
|  | SPIPCI_WREG(osh, ®s->spih_stat, 0x00000080); | 
|  |  | 
|  | /* Ack the interrupt in the interrupt controller */ | 
|  | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_CTLR_INTR); | 
|  | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  |  | 
|  | ours = TRUE; | 
|  | } else if (cur_int & SPIH_WFIFO_INTR) { | 
|  | sd_trace(("%s: SPI WR FIFO Empty interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | __FUNCTION__, raw_int, cur_int)); | 
|  |  | 
|  | /* Disable the FIFO Empty Interrupt */ | 
|  | sd->intmask &= ~SPIH_WFIFO_INTR; | 
|  | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  |  | 
|  | sd->local_intrcount++; | 
|  | sd->got_hcint = TRUE; | 
|  | ours = TRUE; | 
|  | } else { | 
|  | /* Not an error: can share interrupts... */ | 
|  | sd_trace(("%s: Not my interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | __FUNCTION__, raw_int, cur_int)); | 
|  | ours = FALSE; | 
|  | } | 
|  |  | 
|  | return ours; | 
|  | } | 
|  |  | 
|  | static void | 
|  | hexdump(char *pfx, unsigned char *msg, int msglen) | 
|  | { | 
|  | int i, col; | 
|  | char buf[80]; | 
|  |  | 
|  | ASSERT(strlen(pfx) + 49 <= sizeof(buf)); | 
|  |  | 
|  | col = 0; | 
|  |  | 
|  | for (i = 0; i < msglen; i++, col++) { | 
|  | if (col % 16 == 0) | 
|  | strcpy(buf, pfx); | 
|  | sprintf(buf + strlen(buf), "%02x", msg[i]); | 
|  | if ((col + 1) % 16 == 0) | 
|  | printf("%s\n", buf); | 
|  | else | 
|  | sprintf(buf + strlen(buf), " "); | 
|  | } | 
|  |  | 
|  | if (col % 16 != 0) | 
|  | printf("%s\n", buf); | 
|  | } | 
|  |  | 
|  | /* Send/Receive an SPI Packet */ | 
|  | void | 
|  | spi_sendrecv(sdioh_info_t *sd, uint8 *msg_out, uint8 *msg_in, int msglen) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  | uint32 count; | 
|  | uint32 spi_data_out; | 
|  | uint32 spi_data_in; | 
|  | bool yield; | 
|  |  | 
|  | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  |  | 
|  | if (bcmpcispi_dump) { | 
|  | printf("SENDRECV(len=%d)\n", msglen); | 
|  | hexdump(" OUT: ", msg_out, msglen); | 
|  | } | 
|  |  | 
|  | #ifdef BCMSDYIELD | 
|  | /* Only yield the CPU and wait for interrupt on Rev 8 and newer FPGA images. */ | 
|  | yield = ((msglen > 500) && (si->rev >= 8)); | 
|  | #else | 
|  | yield = FALSE; | 
|  | #endif /* BCMSDYIELD */ | 
|  |  | 
|  | ASSERT(msglen % 4 == 0); | 
|  |  | 
|  |  | 
|  | SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~SPIH_CS);	/* Set GPIO CS# Low (asserted) */ | 
|  |  | 
|  | for (count = 0; count < (uint32)msglen/4; count++) { | 
|  | spi_data_out = ((uint32)((uint32 *)msg_out)[count]); | 
|  | SPIPCI_WREG(osh, ®s->spih_data, spi_data_out); | 
|  | } | 
|  |  | 
|  | #ifdef BCMSDYIELD | 
|  | if (yield) { | 
|  | /* Ack the interrupt in the interrupt controller */ | 
|  | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_WFIFO_INTR); | 
|  | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  |  | 
|  | /* Enable the FIFO Empty Interrupt */ | 
|  | sd->intmask |= SPIH_WFIFO_INTR; | 
|  | sd->got_hcint = FALSE; | 
|  | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  |  | 
|  | } | 
|  | #endif /* BCMSDYIELD */ | 
|  |  | 
|  | /* Wait for write fifo to empty... */ | 
|  | SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~0x00000020);	/* Set GPIO 5 Low */ | 
|  |  | 
|  | if (yield) { | 
|  | ASSERT((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0); | 
|  | } | 
|  |  | 
|  | spi_waitbits(sd, yield); | 
|  | SPIPCI_ORREG(osh, ®s->spih_gpio_data, 0x00000020);	/* Set GPIO 5 High (de-asserted) */ | 
|  |  | 
|  | for (count = 0; count < (uint32)msglen/4; count++) { | 
|  | spi_data_in = SPIPCI_RREG(osh, ®s->spih_data); | 
|  | ((uint32 *)msg_in)[count] = spi_data_in; | 
|  | } | 
|  |  | 
|  | /* Set GPIO CS# High (de-asserted) */ | 
|  | SPIPCI_ORREG(osh, ®s->spih_gpio_data, SPIH_CS); | 
|  |  | 
|  | if (bcmpcispi_dump) { | 
|  | hexdump(" IN : ", msg_in, msglen); | 
|  | } | 
|  | } | 
|  |  | 
|  | void | 
|  | spi_spinbits(sdioh_info_t *sd) | 
|  | { | 
|  | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | osl_t *osh = si->osh; | 
|  | spih_regs_t *regs = si->regs; | 
|  | uint spin_count; /* Spin loop bound check */ | 
|  |  | 
|  | spin_count = 0; | 
|  | while ((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0) { | 
|  | if (spin_count > SPI_SPIN_BOUND) { | 
|  | sd_err(("%s: SPIH_WFEMPTY spin bits out of bound %u times \n", | 
|  | __FUNCTION__, spin_count)); | 
|  | ASSERT(FALSE); | 
|  | } | 
|  | spin_count++; | 
|  | } | 
|  |  | 
|  | /* Wait for SPI Transfer state machine to return to IDLE state. | 
|  | * The state bits are only implemented in Rev >= 5 FPGA.  These | 
|  | * bits are hardwired to 00 for Rev < 5, so this check doesn't cause | 
|  | * any problems. | 
|  | */ | 
|  | spin_count = 0; | 
|  | while ((SPIPCI_RREG(osh, ®s->spih_stat) & SPIH_STATE_MASK) != 0) { | 
|  | if (spin_count > SPI_SPIN_BOUND) { | 
|  | sd_err(("%s: SPIH_STATE_MASK spin bits out of bound %u times \n", | 
|  | __FUNCTION__, spin_count)); | 
|  | ASSERT(FALSE); | 
|  | } | 
|  | spin_count++; | 
|  | } | 
|  | } |