Home | History | Annotate | Download | only in sys
      1 /*
      2  * Broadcom SPI Host Controller Driver - Linux Per-port
      3  *
      4  * Copyright (C) 1999-2009, 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