1 /* 2 * Broadcom SPI Host Controller Driver - Linux Per-port 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: bcmsdspi_linux.c,v 1.7.2.1.4.3 2008/06/30 21:09:36 Exp $ 25 */ 26 27 #include <typedefs.h> 28 #include <pcicfg.h> 29 #include <bcmutils.h> 30 31 #include <sdio.h> /* SDIO Specs */ 32 #include <bcmsdbus.h> /* bcmsdh to/from specific controller APIs */ 33 #include <sdiovar.h> /* to get msglevel bit values */ 34 35 #include <linux/sched.h> /* request_irq(), free_irq() */ 36 37 #include <bcmsdspi.h> 38 #include <bcmspi.h> 39 40 extern uint sd_crc; 41 module_param(sd_crc, uint, 0); 42 43 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) 44 #define KERNEL26 45 #endif 46 47 struct sdos_info { 48 sdioh_info_t *sd; 49 spinlock_t lock; 50 wait_queue_head_t intr_wait_queue; 51 }; 52 53 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) 54 #define BLOCKABLE() (!in_atomic()) 55 #else 56 #define BLOCKABLE() (!in_interrupt()) 57 #endif 58 59 /* Interrupt handler */ 60 static irqreturn_t 61 sdspi_isr(int irq, void *dev_id 62 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 20) 63 , struct pt_regs *ptregs 64 #endif 65 ) 66 { 67 sdioh_info_t *sd; 68 struct sdos_info *sdos; 69 bool ours; 70 71 sd = (sdioh_info_t *)dev_id; 72 sd->local_intrcount++; 73 74 if (!sd->card_init_done) { 75 sd_err(("%s: Hey Bogus intr...not even initted: irq %d\n", __FUNCTION__, irq)); 76 return IRQ_RETVAL(FALSE); 77 } else { 78 ours = spi_check_client_intr(sd, NULL); 79 80 /* For local interrupts, wake the waiting process */ 81 if (ours && sd->got_hcint) { 82 sdos = (struct sdos_info *)sd->sdos_info; 83 wake_up_interruptible(&sdos->intr_wait_queue); 84 } 85 86 return IRQ_RETVAL(ours); 87 } 88 } 89 90 /* Register with Linux for interrupts */ 91 int 92 spi_register_irq(sdioh_info_t *sd, uint irq) 93 { 94 sd_trace(("Entering %s: irq == %d\n", __FUNCTION__, irq)); 95 if (request_irq(irq, sdspi_isr, IRQF_SHARED, "bcmsdspi", sd) < 0) { 96 sd_err(("%s: request_irq() failed\n", __FUNCTION__)); 97 return ERROR; 98 } 99 return SUCCESS; 100 } 101 102 /* Free Linux irq */ 103 void 104 spi_free_irq(uint irq, sdioh_info_t *sd) 105 { 106 free_irq(irq, sd); 107 } 108 109 /* Map Host controller registers */ 110 111 uint32 * 112 spi_reg_map(osl_t *osh, uintptr addr, int size) 113 { 114 return (uint32 *)REG_MAP(addr, size); 115 } 116 117 void 118 spi_reg_unmap(osl_t *osh, uintptr addr, int size) 119 { 120 REG_UNMAP((void*)(uintptr)addr); 121 } 122 123 int 124 spi_osinit(sdioh_info_t *sd) 125 { 126 struct sdos_info *sdos; 127 128 sdos = (struct sdos_info*)MALLOC(sd->osh, sizeof(struct sdos_info)); 129 sd->sdos_info = (void*)sdos; 130 if (sdos == NULL) 131 return BCME_NOMEM; 132 133 sdos->sd = sd; 134 spin_lock_init(&sdos->lock); 135 init_waitqueue_head(&sdos->intr_wait_queue); 136 return BCME_OK; 137 } 138 139 void 140 spi_osfree(sdioh_info_t *sd) 141 { 142 struct sdos_info *sdos; 143 ASSERT(sd && sd->sdos_info); 144 145 sdos = (struct sdos_info *)sd->sdos_info; 146 MFREE(sd->osh, sdos, sizeof(struct sdos_info)); 147 } 148 149 /* Interrupt enable/disable */ 150 SDIOH_API_RC 151 sdioh_interrupt_set(sdioh_info_t *sd, bool enable) 152 { 153 ulong flags; 154 struct sdos_info *sdos; 155 156 sd_trace(("%s: %s\n", __FUNCTION__, enable ? "Enabling" : "Disabling")); 157 158 sdos = (struct sdos_info *)sd->sdos_info; 159 ASSERT(sdos); 160 161 if (!(sd->host_init_done && sd->card_init_done)) { 162 sd_err(("%s: Card & Host are not initted - bailing\n", __FUNCTION__)); 163 return SDIOH_API_RC_FAIL; 164 } 165 166 if (enable && !(sd->intr_handler && sd->intr_handler_arg)) { 167 sd_err(("%s: no handler registered, will not enable\n", __FUNCTION__)); 168 return SDIOH_API_RC_FAIL; 169 } 170 171 /* Ensure atomicity for enable/disable calls */ 172 spin_lock_irqsave(&sdos->lock, flags); 173 174 sd->client_intr_enabled = enable; 175 if (enable && !sd->lockcount) 176 spi_devintr_on(sd); 177 else 178 spi_devintr_off(sd); 179 180 spin_unlock_irqrestore(&sdos->lock, flags); 181 182 return SDIOH_API_RC_SUCCESS; 183 } 184 185 /* Protect against reentrancy (disable device interrupts while executing) */ 186 void 187 spi_lock(sdioh_info_t *sd) 188 { 189 ulong flags; 190 struct sdos_info *sdos; 191 192 sdos = (struct sdos_info *)sd->sdos_info; 193 ASSERT(sdos); 194 195 sd_trace(("%s: %d\n", __FUNCTION__, sd->lockcount)); 196 197 spin_lock_irqsave(&sdos->lock, flags); 198 if (sd->lockcount) { 199 sd_err(("%s: Already locked!\n", __FUNCTION__)); 200 ASSERT(sd->lockcount == 0); 201 } 202 spi_devintr_off(sd); 203 sd->lockcount++; 204 spin_unlock_irqrestore(&sdos->lock, flags); 205 } 206 207 /* Enable client interrupt */ 208 void 209 spi_unlock(sdioh_info_t *sd) 210 { 211 ulong flags; 212 struct sdos_info *sdos; 213 214 sd_trace(("%s: %d, %d\n", __FUNCTION__, sd->lockcount, sd->client_intr_enabled)); 215 ASSERT(sd->lockcount > 0); 216 217 sdos = (struct sdos_info *)sd->sdos_info; 218 ASSERT(sdos); 219 220 spin_lock_irqsave(&sdos->lock, flags); 221 if (--sd->lockcount == 0 && sd->client_intr_enabled) { 222 spi_devintr_on(sd); 223 } 224 spin_unlock_irqrestore(&sdos->lock, flags); 225 } 226 227 void spi_waitbits(sdioh_info_t *sd, bool yield) 228 { 229 struct sdos_info *sdos; 230 231 sdos = (struct sdos_info *)sd->sdos_info; 232 233 #ifndef BCMSDYIELD 234 ASSERT(!yield); 235 #endif 236 sd_trace(("%s: yield %d canblock %d\n", 237 __FUNCTION__, yield, BLOCKABLE())); 238 239 /* Clear the "interrupt happened" flag and last intrstatus */ 240 sd->got_hcint = FALSE; 241 242 #ifdef BCMSDYIELD 243 if (yield && BLOCKABLE()) { 244 /* Wait for the indication, the interrupt will be masked when the ISR fires. */ 245 wait_event_interruptible(sdos->intr_wait_queue, (sd->got_hcint)); 246 } else 247 #endif /* BCMSDYIELD */ 248 { 249 spi_spinbits(sd); 250 } 251 252 } 253