| Dmitry Shmidt | da65eba | 2010-05-19 18:53:11 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | * Broadcom SPI over PCI-SPI Host Controller, low-level hardware driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 1999-2010, Broadcom Corporation | 
|  | 5 | * | 
|  | 6 | *      Unless you and Broadcom execute a separate written software license | 
|  | 7 | * agreement governing use of this software, this software is licensed to you | 
|  | 8 | * under the terms of the GNU General Public License version 2 (the "GPL"), | 
|  | 9 | * available at http://www.broadcom.com/licenses/GPLv2.php, with the | 
|  | 10 | * following added to such license: | 
|  | 11 | * | 
|  | 12 | *      As a special exception, the copyright holders of this software give you | 
|  | 13 | * permission to link this software with independent modules, and to copy and | 
|  | 14 | * distribute the resulting executable under terms of your choice, provided that | 
|  | 15 | * you also meet, for each linked independent module, the terms and conditions of | 
|  | 16 | * the license of that module.  An independent module is a module which is not | 
|  | 17 | * derived from this software.  The special exception does not apply to any | 
|  | 18 | * modifications of the software. | 
|  | 19 | * | 
|  | 20 | *      Notwithstanding the above, under no circumstances may you combine this | 
|  | 21 | * software in any way with any other Broadcom software provided under a license | 
|  | 22 | * other than the GPL, without Broadcom's express prior written consent. | 
|  | 23 | * | 
|  | 24 | * $Id: bcmpcispi.c,v 1.22.2.4.4.5.6.1 2010/08/13 00:26:05 Exp $ | 
|  | 25 | */ | 
|  | 26 |  | 
|  | 27 | #include <typedefs.h> | 
|  | 28 | #include <bcmutils.h> | 
|  | 29 |  | 
|  | 30 | #include <sdio.h>		/* SDIO Specs */ | 
|  | 31 | #include <bcmsdbus.h>		/* bcmsdh to/from specific controller APIs */ | 
|  | 32 | #include <sdiovar.h>		/* to get msglevel bit values */ | 
|  | 33 |  | 
|  | 34 | #include <pcicfg.h> | 
|  | 35 | #include <bcmsdspi.h> | 
|  | 36 | #include <bcmspi.h> | 
|  | 37 | #include <bcmpcispi.h>		/* BRCM PCI-SPI Host Controller Register definitions */ | 
|  | 38 |  | 
|  | 39 |  | 
|  | 40 | /* ndis_osl.h needs to do a runtime check of the osh to map | 
|  | 41 | * R_REG/W_REG to bus specific access similar to linux_osl.h. | 
|  | 42 | * Until then... | 
|  | 43 | */ | 
|  | 44 | /* linux */ | 
|  | 45 |  | 
|  | 46 | #define SPIPCI_RREG R_REG | 
|  | 47 | #define SPIPCI_WREG W_REG | 
|  | 48 |  | 
|  | 49 |  | 
|  | 50 | #define	SPIPCI_ANDREG(osh, r, v) SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) & (v))) | 
|  | 51 | #define	SPIPCI_ORREG(osh, r, v)	SPIPCI_WREG(osh, (r), (SPIPCI_RREG(osh, r) | (v))) | 
|  | 52 |  | 
|  | 53 |  | 
|  | 54 | int bcmpcispi_dump = 0;		/* Set to dump complete trace of all SPI bus transactions */ | 
|  | 55 |  | 
|  | 56 | typedef struct spih_info_ { | 
|  | 57 | uint		bar0;		/* BAR0 of PCI Card */ | 
|  | 58 | uint		bar1;		/* BAR1 of PCI Card */ | 
|  | 59 | osl_t 		*osh;		/* osh handle */ | 
|  | 60 | spih_pciregs_t	*pciregs;	/* PCI Core Registers */ | 
|  | 61 | spih_regs_t	*regs;		/* SPI Controller Registers */ | 
|  | 62 | uint8		rev;		/* PCI Card Revision ID */ | 
|  | 63 | } spih_info_t; | 
|  | 64 |  | 
|  | 65 |  | 
|  | 66 | /* Attach to PCI-SPI Host Controller Hardware */ | 
|  | 67 | bool | 
|  | 68 | spi_hw_attach(sdioh_info_t *sd) | 
|  | 69 | { | 
|  | 70 | osl_t *osh; | 
|  | 71 | spih_info_t *si; | 
|  | 72 |  | 
|  | 73 | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  | 74 |  | 
|  | 75 | osh = sd->osh; | 
|  | 76 |  | 
|  | 77 | if ((si = (spih_info_t *)MALLOC(osh, sizeof(spih_info_t))) == NULL) { | 
|  | 78 | sd_err(("%s: out of memory, malloced %d bytes\n", __FUNCTION__, MALLOCED(osh))); | 
|  | 79 | return FALSE; | 
|  | 80 | } | 
|  | 81 |  | 
|  | 82 | bzero(si, sizeof(spih_info_t)); | 
|  | 83 |  | 
|  | 84 | sd->controller = si; | 
|  | 85 |  | 
|  | 86 | si->osh = sd->osh; | 
|  | 87 | si->rev = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_REV, 4) & 0xFF; | 
|  | 88 |  | 
|  | 89 | if (si->rev < 3) { | 
|  | 90 | sd_err(("Host controller %d not supported, please upgrade to rev >= 3\n", si->rev)); | 
|  | 91 | MFREE(osh, si, sizeof(spih_info_t)); | 
|  | 92 | return (FALSE); | 
|  | 93 | } | 
|  | 94 |  | 
|  | 95 | sd_err(("Attaching to Generic PCI SPI Host Controller Rev %d\n", si->rev)); | 
|  | 96 |  | 
|  | 97 | /* FPGA Revision < 3 not supported by driver anymore. */ | 
|  | 98 | ASSERT(si->rev >= 3); | 
|  | 99 |  | 
|  | 100 | si->bar0 = sd->bar0; | 
|  | 101 |  | 
|  | 102 | /* Rev < 10 PciSpiHost has 2 BARs: | 
|  | 103 | *    BAR0 = PCI Core Registers | 
|  | 104 | *    BAR1 = PciSpiHost Registers (all other cores on backplane) | 
|  | 105 | * | 
|  | 106 | * Rev 10 and up use a different PCI core which only has a single | 
|  | 107 | * BAR0 which contains the PciSpiHost Registers. | 
|  | 108 | */ | 
|  | 109 | if (si->rev < 10) { | 
|  | 110 | si->pciregs = (spih_pciregs_t *)spi_reg_map(osh, | 
|  | 111 | (uintptr)si->bar0, | 
|  | 112 | sizeof(spih_pciregs_t)); | 
|  | 113 | sd_err(("Mapped PCI Core regs to BAR0 at %p\n", si->pciregs)); | 
|  | 114 |  | 
|  | 115 | si->bar1 = OSL_PCI_READ_CONFIG(sd->osh, PCI_CFG_BAR1, 4); | 
|  | 116 | si->regs = (spih_regs_t *)spi_reg_map(osh, | 
|  | 117 | (uintptr)si->bar1, | 
|  | 118 | sizeof(spih_regs_t)); | 
|  | 119 | sd_err(("Mapped SPI Controller regs to BAR1 at %p\n", si->regs)); | 
|  | 120 | } else { | 
|  | 121 | si->regs = (spih_regs_t *)spi_reg_map(osh, | 
|  | 122 | (uintptr)si->bar0, | 
|  | 123 | sizeof(spih_regs_t)); | 
|  | 124 | sd_err(("Mapped SPI Controller regs to BAR0 at %p\n", si->regs)); | 
|  | 125 | si->pciregs = NULL; | 
|  | 126 | } | 
|  | 127 | /* Enable SPI Controller, 16.67MHz SPI Clock */ | 
|  | 128 | SPIPCI_WREG(osh, &si->regs->spih_ctrl, 0x000000d1); | 
|  | 129 |  | 
|  | 130 | /* Set extended feature register to defaults */ | 
|  | 131 | SPIPCI_WREG(osh, &si->regs->spih_ext, 0x00000000); | 
|  | 132 |  | 
|  | 133 | /* Set GPIO CS# High (de-asserted) */ | 
|  | 134 | SPIPCI_WREG(osh, &si->regs->spih_gpio_data, SPIH_CS); | 
|  | 135 |  | 
|  | 136 | /* set GPIO[0] to output for CS# */ | 
|  | 137 | /* set GPIO[1] to output for power control */ | 
|  | 138 | /* set GPIO[2] to input for card detect */ | 
|  | 139 | SPIPCI_WREG(osh, &si->regs->spih_gpio_ctrl, (SPIH_CS | SPIH_SLOT_POWER)); | 
|  | 140 |  | 
|  | 141 | /* Clear out the Read FIFO in case there is any stuff left in there from a previous run. */ | 
|  | 142 | while ((SPIPCI_RREG(osh, &si->regs->spih_stat) & SPIH_RFEMPTY) == 0) { | 
|  | 143 | SPIPCI_RREG(osh, &si->regs->spih_data); | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | /* Wait for power to stabilize to the SDIO Card (100msec was insufficient) */ | 
|  | 147 | OSL_DELAY(250000); | 
|  | 148 |  | 
|  | 149 | /* Check card detect on FPGA Revision >= 4 */ | 
|  | 150 | if (si->rev >= 4) { | 
|  | 151 | if (SPIPCI_RREG(osh, &si->regs->spih_gpio_data) & SPIH_CARD_DETECT) { | 
|  | 152 | sd_err(("%s: no card detected in SD slot\n", __FUNCTION__)); | 
|  | 153 | spi_reg_unmap(osh, (uintptr)si->regs, sizeof(spih_regs_t)); | 
|  | 154 | if (si->pciregs) { | 
|  | 155 | spi_reg_unmap(osh, (uintptr)si->pciregs, sizeof(spih_pciregs_t)); | 
|  | 156 | } | 
|  | 157 | MFREE(osh, si, sizeof(spih_info_t)); | 
|  | 158 | return FALSE; | 
|  | 159 | } | 
|  | 160 | } | 
|  | 161 |  | 
|  | 162 | /* Interrupts are level sensitive */ | 
|  | 163 | SPIPCI_WREG(osh, &si->regs->spih_int_edge, 0x80000000); | 
|  | 164 |  | 
|  | 165 | /* Interrupts are active low. */ | 
|  | 166 | SPIPCI_WREG(osh, &si->regs->spih_int_pol, 0x40000004); | 
|  | 167 |  | 
|  | 168 | /* Enable interrupts through PCI Core. */ | 
|  | 169 | if (si->pciregs) { | 
|  | 170 | SPIPCI_WREG(osh, &si->pciregs->ICR, PCI_INT_PROP_EN); | 
|  | 171 | } | 
|  | 172 |  | 
|  | 173 | sd_trace(("%s: exit\n", __FUNCTION__)); | 
|  | 174 | return TRUE; | 
|  | 175 | } | 
|  | 176 |  | 
|  | 177 | /* Detach and return PCI-SPI Hardware to unconfigured state */ | 
|  | 178 | bool | 
|  | 179 | spi_hw_detach(sdioh_info_t *sd) | 
|  | 180 | { | 
|  | 181 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 182 | osl_t *osh = si->osh; | 
|  | 183 | spih_regs_t *regs = si->regs; | 
|  | 184 | spih_pciregs_t *pciregs = si->pciregs; | 
|  | 185 |  | 
|  | 186 | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  | 187 |  | 
|  | 188 | SPIPCI_WREG(osh, ®s->spih_ctrl, 0x00000010); | 
|  | 189 | SPIPCI_WREG(osh, ®s->spih_gpio_ctrl, 0x00000000);	/* Disable GPIO for CS# */ | 
|  | 190 | SPIPCI_WREG(osh, ®s->spih_int_mask, 0x00000000);	/* Clear Intmask */ | 
|  | 191 | SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAF); | 
|  | 192 | SPIPCI_WREG(osh, ®s->spih_int_edge, 0x00000000); | 
|  | 193 | SPIPCI_WREG(osh, ®s->spih_int_pol, 0x00000000); | 
|  | 194 | SPIPCI_WREG(osh, ®s->spih_hex_disp, 0x0000DEAD); | 
|  | 195 |  | 
|  | 196 | /* Disable interrupts through PCI Core. */ | 
|  | 197 | if (si->pciregs) { | 
|  | 198 | SPIPCI_WREG(osh, &pciregs->ICR, 0x00000000); | 
|  | 199 | spi_reg_unmap(osh, (uintptr)pciregs, sizeof(spih_pciregs_t)); | 
|  | 200 | } | 
|  | 201 | spi_reg_unmap(osh, (uintptr)regs, sizeof(spih_regs_t)); | 
|  | 202 |  | 
|  | 203 | MFREE(osh, si, sizeof(spih_info_t)); | 
|  | 204 |  | 
|  | 205 | sd->controller = NULL; | 
|  | 206 |  | 
|  | 207 | sd_trace(("%s: exit\n", __FUNCTION__)); | 
|  | 208 | return TRUE; | 
|  | 209 | } | 
|  | 210 |  | 
|  | 211 | /* Switch between internal (PCI) and external clock oscillator */ | 
|  | 212 | static bool | 
|  | 213 | sdspi_switch_clock(sdioh_info_t *sd, bool ext_clk) | 
|  | 214 | { | 
|  | 215 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 216 | osl_t *osh = si->osh; | 
|  | 217 | spih_regs_t *regs = si->regs; | 
|  | 218 |  | 
|  | 219 | /* Switch to desired clock, and reset the PLL. */ | 
|  | 220 | SPIPCI_WREG(osh, ®s->spih_pll_ctrl, ext_clk ? SPIH_EXT_CLK : 0); | 
|  | 221 |  | 
|  | 222 | SPINWAIT(((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) | 
|  | 223 | != SPIH_PLL_LOCKED), 1000); | 
|  | 224 | if ((SPIPCI_RREG(osh, ®s->spih_pll_status) & SPIH_PLL_LOCKED) != SPIH_PLL_LOCKED) { | 
|  | 225 | sd_err(("%s: timeout waiting for PLL to lock\n", __FUNCTION__)); | 
|  | 226 | return (FALSE); | 
|  | 227 | } | 
|  | 228 | return (TRUE); | 
|  | 229 |  | 
|  | 230 | } | 
|  | 231 |  | 
|  | 232 | /* Configure PCI-SPI Host Controller's SPI Clock rate as a divisor into the | 
|  | 233 | * base clock rate.  The base clock is either the PCI Clock (33MHz) or the | 
|  | 234 | * external clock oscillator at U17 on the PciSpiHost. | 
|  | 235 | */ | 
|  | 236 | bool | 
|  | 237 | spi_start_clock(sdioh_info_t *sd, uint16 div) | 
|  | 238 | { | 
|  | 239 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 240 | osl_t *osh = si->osh; | 
|  | 241 | spih_regs_t *regs = si->regs; | 
|  | 242 | uint32 t, espr, disp; | 
|  | 243 | uint32 disp_xtal_freq; | 
|  | 244 | bool	ext_clock = FALSE; | 
|  | 245 | char disp_string[5]; | 
|  | 246 |  | 
|  | 247 | if (div > 2048) { | 
|  | 248 | sd_err(("%s: divisor %d too large; using max of 2048\n", __FUNCTION__, div)); | 
|  | 249 | div = 2048; | 
|  | 250 | } else if (div & (div - 1)) {	/* Not a power of 2? */ | 
|  | 251 | /* Round up to a power of 2 */ | 
|  | 252 | while ((div + 1) & div) | 
|  | 253 | div |= div >> 1; | 
|  | 254 | div++; | 
|  | 255 | } | 
|  | 256 |  | 
|  | 257 | /* For FPGA Rev >= 5, the use of an external clock oscillator is supported. | 
|  | 258 | * If the oscillator is populated, use it to provide the SPI base clock, | 
|  | 259 | * otherwise, default to the PCI clock as the SPI base clock. | 
|  | 260 | */ | 
|  | 261 | if (si->rev >= 5) { | 
|  | 262 | uint32 clk_tick; | 
|  | 263 | /* Enable the External Clock Oscillator as PLL clock source. */ | 
|  | 264 | if (!sdspi_switch_clock(sd, TRUE)) { | 
|  | 265 | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | 266 | } | 
|  | 267 |  | 
|  | 268 | /* Check to make sure the external clock is running.  If not, then it | 
|  | 269 | * is not populated on the card, so we will default to the PCI clock. | 
|  | 270 | */ | 
|  | 271 | clk_tick = SPIPCI_RREG(osh, ®s->spih_clk_count); | 
|  | 272 | if (clk_tick == SPIPCI_RREG(osh, ®s->spih_clk_count)) { | 
|  | 273 |  | 
|  | 274 | /* Switch back to the PCI clock as the clock source. */ | 
|  | 275 | if (!sdspi_switch_clock(sd, FALSE)) { | 
|  | 276 | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | 277 | } | 
|  | 278 | } else { | 
|  | 279 | ext_clock = TRUE; | 
|  | 280 | } | 
|  | 281 | } | 
|  | 282 |  | 
|  | 283 | /* Hack to allow hot-swapping oscillators: | 
|  | 284 | * 1. Force PCI clock as clock source, using sd_divisor of 0. | 
|  | 285 | * 2. Swap oscillator | 
|  | 286 | * 3. Set desired sd_divisor (will switch to external oscillator as clock source. | 
|  | 287 | */ | 
|  | 288 | if (div == 0) { | 
|  | 289 | ext_clock = FALSE; | 
|  | 290 | div = 2; | 
|  | 291 |  | 
|  | 292 | /* Select PCI clock as the clock source. */ | 
|  | 293 | if (!sdspi_switch_clock(sd, FALSE)) { | 
|  | 294 | sd_err(("%s: error switching to external clock\n", __FUNCTION__)); | 
|  | 295 | } | 
|  | 296 |  | 
|  | 297 | sd_err(("%s: Ok to hot-swap oscillators.\n", __FUNCTION__)); | 
|  | 298 | } | 
|  | 299 |  | 
|  | 300 | /* If using the external oscillator, read the clock frequency from the controller | 
|  | 301 | * The value read is in units of 10000Hz, and it's not a nice round number because | 
|  | 302 | * it is calculated by the FPGA.  So to make up for that, we round it off. | 
|  | 303 | */ | 
|  | 304 | if (ext_clock == TRUE) { | 
|  | 305 | uint32 xtal_freq; | 
|  | 306 |  | 
|  | 307 | OSL_DELAY(1000); | 
|  | 308 | xtal_freq = SPIPCI_RREG(osh, ®s->spih_xtal_freq) * 10000; | 
|  | 309 |  | 
|  | 310 | sd_info(("%s: Oscillator is %dHz\n", __FUNCTION__, xtal_freq)); | 
|  | 311 |  | 
|  | 312 |  | 
|  | 313 | disp_xtal_freq = xtal_freq / 10000; | 
|  | 314 |  | 
|  | 315 | /* Round it off to a nice number. */ | 
|  | 316 | if ((disp_xtal_freq % 100) > 50) { | 
|  | 317 | disp_xtal_freq += 100; | 
|  | 318 | } | 
|  | 319 |  | 
|  | 320 | disp_xtal_freq = (disp_xtal_freq / 100) * 100; | 
|  | 321 | } else { | 
|  | 322 | sd_err(("%s: no external oscillator installed, using PCI clock.\n", __FUNCTION__)); | 
|  | 323 | disp_xtal_freq = 3333; | 
|  | 324 | } | 
|  | 325 |  | 
|  | 326 | /* Convert the SPI Clock frequency to BCD format. */ | 
|  | 327 | sprintf(disp_string, "%04d", disp_xtal_freq / div); | 
|  | 328 |  | 
|  | 329 | disp  = (disp_string[0] - '0') << 12; | 
|  | 330 | disp |= (disp_string[1] - '0') << 8; | 
|  | 331 | disp |= (disp_string[2] - '0') << 4; | 
|  | 332 | disp |= (disp_string[3] - '0'); | 
|  | 333 |  | 
|  | 334 | /* Select the correct ESPR register value based on the divisor. */ | 
|  | 335 | switch (div) { | 
|  | 336 | case 1:		espr = 0x0; break; | 
|  | 337 | case 2:		espr = 0x1; break; | 
|  | 338 | case 4:		espr = 0x2; break; | 
|  | 339 | case 8:		espr = 0x5; break; | 
|  | 340 | case 16:	espr = 0x3; break; | 
|  | 341 | case 32:	espr = 0x4; break; | 
|  | 342 | case 64:	espr = 0x6; break; | 
|  | 343 | case 128:	espr = 0x7; break; | 
|  | 344 | case 256:	espr = 0x8; break; | 
|  | 345 | case 512:	espr = 0x9; break; | 
|  | 346 | case 1024:	espr = 0xa; break; | 
|  | 347 | case 2048:	espr = 0xb; break; | 
|  | 348 | default:	espr = 0x0; ASSERT(0); break; | 
|  | 349 | } | 
|  | 350 |  | 
|  | 351 | t = SPIPCI_RREG(osh, ®s->spih_ctrl); | 
|  | 352 | t &= ~3; | 
|  | 353 | t |= espr & 3; | 
|  | 354 | SPIPCI_WREG(osh, ®s->spih_ctrl, t); | 
|  | 355 |  | 
|  | 356 | t = SPIPCI_RREG(osh, ®s->spih_ext); | 
|  | 357 | t &= ~3; | 
|  | 358 | t |= (espr >> 2) & 3; | 
|  | 359 | SPIPCI_WREG(osh, ®s->spih_ext, t); | 
|  | 360 |  | 
|  | 361 | SPIPCI_WREG(osh, ®s->spih_hex_disp, disp); | 
|  | 362 |  | 
|  | 363 | /* For Rev 8, writing to the PLL_CTRL register resets | 
|  | 364 | * the PLL, and it can re-acquire in 200uS.  For | 
|  | 365 | * Rev 7 and older, we use a software delay to allow | 
|  | 366 | * the PLL to re-acquire, which takes more than 2mS. | 
|  | 367 | */ | 
|  | 368 | if (si->rev < 8) { | 
|  | 369 | /* Wait for clock to settle. */ | 
|  | 370 | OSL_DELAY(5000); | 
|  | 371 | } | 
|  | 372 |  | 
|  | 373 | sd_info(("%s: SPI_CTRL=0x%08x SPI_EXT=0x%08x\n", | 
|  | 374 | __FUNCTION__, | 
|  | 375 | SPIPCI_RREG(osh, ®s->spih_ctrl), | 
|  | 376 | SPIPCI_RREG(osh, ®s->spih_ext))); | 
|  | 377 |  | 
|  | 378 | return TRUE; | 
|  | 379 | } | 
|  | 380 |  | 
|  | 381 | /* Configure PCI-SPI Host Controller High-Speed Clocking mode setting */ | 
|  | 382 | bool | 
|  | 383 | spi_controller_highspeed_mode(sdioh_info_t *sd, bool hsmode) | 
|  | 384 | { | 
|  | 385 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 386 | osl_t *osh = si->osh; | 
|  | 387 | spih_regs_t *regs = si->regs; | 
|  | 388 |  | 
|  | 389 | if (si->rev >= 10) { | 
|  | 390 | if (hsmode) { | 
|  | 391 | SPIPCI_ORREG(osh, ®s->spih_ext, 0x10); | 
|  | 392 | } else { | 
|  | 393 | SPIPCI_ANDREG(osh, ®s->spih_ext, ~0x10); | 
|  | 394 | } | 
|  | 395 | } | 
|  | 396 |  | 
|  | 397 | return TRUE; | 
|  | 398 | } | 
|  | 399 |  | 
|  | 400 | /* Disable device interrupt */ | 
|  | 401 | void | 
|  | 402 | spi_devintr_off(sdioh_info_t *sd) | 
|  | 403 | { | 
|  | 404 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 405 | osl_t *osh = si->osh; | 
|  | 406 | spih_regs_t *regs = si->regs; | 
|  | 407 |  | 
|  | 408 | sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); | 
|  | 409 | if (sd->use_client_ints) { | 
|  | 410 | sd->intmask &= ~SPIH_DEV_INTR; | 
|  | 411 | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask);	/* Clear Intmask */ | 
|  | 412 | } | 
|  | 413 | } | 
|  | 414 |  | 
|  | 415 | /* Enable device interrupt */ | 
|  | 416 | void | 
|  | 417 | spi_devintr_on(sdioh_info_t *sd) | 
|  | 418 | { | 
|  | 419 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 420 | osl_t *osh = si->osh; | 
|  | 421 | spih_regs_t *regs = si->regs; | 
|  | 422 |  | 
|  | 423 | ASSERT(sd->lockcount == 0); | 
|  | 424 | sd_trace(("%s: %d\n", __FUNCTION__, sd->use_client_ints)); | 
|  | 425 | if (sd->use_client_ints) { | 
|  | 426 | if (SPIPCI_RREG(osh, ®s->spih_ctrl) & 0x02) { | 
|  | 427 | /* Ack in case one was pending but is no longer... */ | 
|  | 428 | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); | 
|  | 429 | } | 
|  | 430 | sd->intmask |= SPIH_DEV_INTR; | 
|  | 431 | /* Set device intr in Intmask */ | 
|  | 432 | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  | 433 | } | 
|  | 434 | } | 
|  | 435 |  | 
|  | 436 | /* Check to see if an interrupt belongs to the PCI-SPI Host or a SPI Device */ | 
|  | 437 | bool | 
|  | 438 | spi_check_client_intr(sdioh_info_t *sd, int *is_dev_intr) | 
|  | 439 | { | 
|  | 440 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 441 | osl_t *osh = si->osh; | 
|  | 442 | spih_regs_t *regs = si->regs; | 
|  | 443 | bool ours = FALSE; | 
|  | 444 |  | 
|  | 445 | uint32 raw_int, cur_int; | 
|  | 446 | ASSERT(sd); | 
|  | 447 |  | 
|  | 448 | if (is_dev_intr) | 
|  | 449 | *is_dev_intr = FALSE; | 
|  | 450 | raw_int = SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | 451 | cur_int = raw_int & sd->intmask; | 
|  | 452 | if (cur_int & SPIH_DEV_INTR) { | 
|  | 453 | if (sd->client_intr_enabled && sd->use_client_ints) { | 
|  | 454 | sd->intrcount++; | 
|  | 455 | ASSERT(sd->intr_handler); | 
|  | 456 | ASSERT(sd->intr_handler_arg); | 
|  | 457 | (sd->intr_handler)(sd->intr_handler_arg); | 
|  | 458 | if (is_dev_intr) | 
|  | 459 | *is_dev_intr = TRUE; | 
|  | 460 | } else { | 
|  | 461 | sd_trace(("%s: Not ready for intr: enabled %d, handler 0x%p\n", | 
|  | 462 | __FUNCTION__, sd->client_intr_enabled, sd->intr_handler)); | 
|  | 463 | } | 
|  | 464 | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_DEV_INTR); | 
|  | 465 | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | 466 | ours = TRUE; | 
|  | 467 | } else if (cur_int & SPIH_CTLR_INTR) { | 
|  | 468 | /* Interrupt is from SPI FIFO... just clear and ack it... */ | 
|  | 469 | sd_trace(("%s: SPI CTLR interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | 470 | __FUNCTION__, raw_int, cur_int)); | 
|  | 471 |  | 
|  | 472 | /* Clear the interrupt in the SPI_STAT register */ | 
|  | 473 | SPIPCI_WREG(osh, ®s->spih_stat, 0x00000080); | 
|  | 474 |  | 
|  | 475 | /* Ack the interrupt in the interrupt controller */ | 
|  | 476 | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_CTLR_INTR); | 
|  | 477 | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | 478 |  | 
|  | 479 | ours = TRUE; | 
|  | 480 | } else if (cur_int & SPIH_WFIFO_INTR) { | 
|  | 481 | sd_trace(("%s: SPI WR FIFO Empty interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | 482 | __FUNCTION__, raw_int, cur_int)); | 
|  | 483 |  | 
|  | 484 | /* Disable the FIFO Empty Interrupt */ | 
|  | 485 | sd->intmask &= ~SPIH_WFIFO_INTR; | 
|  | 486 | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  | 487 |  | 
|  | 488 | sd->local_intrcount++; | 
|  | 489 | sd->got_hcint = TRUE; | 
|  | 490 | ours = TRUE; | 
|  | 491 | } else { | 
|  | 492 | /* Not an error: can share interrupts... */ | 
|  | 493 | sd_trace(("%s: Not my interrupt: raw_int 0x%08x cur_int 0x%08x\n", | 
|  | 494 | __FUNCTION__, raw_int, cur_int)); | 
|  | 495 | ours = FALSE; | 
|  | 496 | } | 
|  | 497 |  | 
|  | 498 | return ours; | 
|  | 499 | } | 
|  | 500 |  | 
|  | 501 | static void | 
|  | 502 | hexdump(char *pfx, unsigned char *msg, int msglen) | 
|  | 503 | { | 
|  | 504 | int i, col; | 
|  | 505 | char buf[80]; | 
|  | 506 |  | 
|  | 507 | ASSERT(strlen(pfx) + 49 <= sizeof(buf)); | 
|  | 508 |  | 
|  | 509 | col = 0; | 
|  | 510 |  | 
|  | 511 | for (i = 0; i < msglen; i++, col++) { | 
|  | 512 | if (col % 16 == 0) | 
|  | 513 | strcpy(buf, pfx); | 
|  | 514 | sprintf(buf + strlen(buf), "%02x", msg[i]); | 
|  | 515 | if ((col + 1) % 16 == 0) | 
|  | 516 | printf("%s\n", buf); | 
|  | 517 | else | 
|  | 518 | sprintf(buf + strlen(buf), " "); | 
|  | 519 | } | 
|  | 520 |  | 
|  | 521 | if (col % 16 != 0) | 
|  | 522 | printf("%s\n", buf); | 
|  | 523 | } | 
|  | 524 |  | 
|  | 525 | /* Send/Receive an SPI Packet */ | 
|  | 526 | void | 
|  | 527 | spi_sendrecv(sdioh_info_t *sd, uint8 *msg_out, uint8 *msg_in, int msglen) | 
|  | 528 | { | 
|  | 529 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 530 | osl_t *osh = si->osh; | 
|  | 531 | spih_regs_t *regs = si->regs; | 
|  | 532 | uint32 count; | 
|  | 533 | uint32 spi_data_out; | 
|  | 534 | uint32 spi_data_in; | 
|  | 535 | bool yield; | 
|  | 536 |  | 
|  | 537 | sd_trace(("%s: enter\n", __FUNCTION__)); | 
|  | 538 |  | 
|  | 539 | if (bcmpcispi_dump) { | 
|  | 540 | printf("SENDRECV(len=%d)\n", msglen); | 
|  | 541 | hexdump(" OUT: ", msg_out, msglen); | 
|  | 542 | } | 
|  | 543 |  | 
|  | 544 | #ifdef BCMSDYIELD | 
|  | 545 | /* Only yield the CPU and wait for interrupt on Rev 8 and newer FPGA images. */ | 
|  | 546 | yield = ((msglen > 500) && (si->rev >= 8)); | 
|  | 547 | #else | 
|  | 548 | yield = FALSE; | 
|  | 549 | #endif /* BCMSDYIELD */ | 
|  | 550 |  | 
|  | 551 | ASSERT(msglen % 4 == 0); | 
|  | 552 |  | 
|  | 553 |  | 
|  | 554 | SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~SPIH_CS);	/* Set GPIO CS# Low (asserted) */ | 
|  | 555 |  | 
|  | 556 | for (count = 0; count < (uint32)msglen/4; count++) { | 
|  | 557 | spi_data_out = ((uint32)((uint32 *)msg_out)[count]); | 
|  | 558 | SPIPCI_WREG(osh, ®s->spih_data, spi_data_out); | 
|  | 559 | } | 
|  | 560 |  | 
|  | 561 | #ifdef BCMSDYIELD | 
|  | 562 | if (yield) { | 
|  | 563 | /* Ack the interrupt in the interrupt controller */ | 
|  | 564 | SPIPCI_WREG(osh, ®s->spih_int_status, SPIH_WFIFO_INTR); | 
|  | 565 | SPIPCI_RREG(osh, ®s->spih_int_status); | 
|  | 566 |  | 
|  | 567 | /* Enable the FIFO Empty Interrupt */ | 
|  | 568 | sd->intmask |= SPIH_WFIFO_INTR; | 
|  | 569 | sd->got_hcint = FALSE; | 
|  | 570 | SPIPCI_WREG(osh, ®s->spih_int_mask, sd->intmask); | 
|  | 571 |  | 
|  | 572 | } | 
|  | 573 | #endif /* BCMSDYIELD */ | 
|  | 574 |  | 
|  | 575 | /* Wait for write fifo to empty... */ | 
|  | 576 | SPIPCI_ANDREG(osh, ®s->spih_gpio_data, ~0x00000020);	/* Set GPIO 5 Low */ | 
|  | 577 |  | 
|  | 578 | if (yield) { | 
|  | 579 | ASSERT((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0); | 
|  | 580 | } | 
|  | 581 |  | 
|  | 582 | spi_waitbits(sd, yield); | 
|  | 583 | SPIPCI_ORREG(osh, ®s->spih_gpio_data, 0x00000020);	/* Set GPIO 5 High (de-asserted) */ | 
|  | 584 |  | 
|  | 585 | for (count = 0; count < (uint32)msglen/4; count++) { | 
|  | 586 | spi_data_in = SPIPCI_RREG(osh, ®s->spih_data); | 
|  | 587 | ((uint32 *)msg_in)[count] = spi_data_in; | 
|  | 588 | } | 
|  | 589 |  | 
|  | 590 | /* Set GPIO CS# High (de-asserted) */ | 
|  | 591 | SPIPCI_ORREG(osh, ®s->spih_gpio_data, SPIH_CS); | 
|  | 592 |  | 
|  | 593 | if (bcmpcispi_dump) { | 
|  | 594 | hexdump(" IN : ", msg_in, msglen); | 
|  | 595 | } | 
|  | 596 | } | 
|  | 597 |  | 
|  | 598 | void | 
|  | 599 | spi_spinbits(sdioh_info_t *sd) | 
|  | 600 | { | 
|  | 601 | spih_info_t *si = (spih_info_t *)sd->controller; | 
|  | 602 | osl_t *osh = si->osh; | 
|  | 603 | spih_regs_t *regs = si->regs; | 
|  | 604 | uint spin_count; /* Spin loop bound check */ | 
|  | 605 |  | 
|  | 606 | spin_count = 0; | 
|  | 607 | while ((SPIPCI_RREG(sd->osh, ®s->spih_stat) & SPIH_WFEMPTY) == 0) { | 
|  | 608 | if (spin_count > SPI_SPIN_BOUND) { | 
|  | 609 | sd_err(("%s: SPIH_WFEMPTY spin bits out of bound %u times \n", | 
|  | 610 | __FUNCTION__, spin_count)); | 
|  | 611 | ASSERT(FALSE); | 
|  | 612 | } | 
|  | 613 | spin_count++; | 
|  | 614 | } | 
|  | 615 |  | 
|  | 616 | /* Wait for SPI Transfer state machine to return to IDLE state. | 
|  | 617 | * The state bits are only implemented in Rev >= 5 FPGA.  These | 
|  | 618 | * bits are hardwired to 00 for Rev < 5, so this check doesn't cause | 
|  | 619 | * any problems. | 
|  | 620 | */ | 
|  | 621 | spin_count = 0; | 
|  | 622 | while ((SPIPCI_RREG(osh, ®s->spih_stat) & SPIH_STATE_MASK) != 0) { | 
|  | 623 | if (spin_count > SPI_SPIN_BOUND) { | 
|  | 624 | sd_err(("%s: SPIH_STATE_MASK spin bits out of bound %u times \n", | 
|  | 625 | __FUNCTION__, spin_count)); | 
|  | 626 | ASSERT(FALSE); | 
|  | 627 | } | 
|  | 628 | spin_count++; | 
|  | 629 | } | 
|  | 630 | } |