Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

sdio: core support for SDIO function interrupt

Signed-off-by: Nicolas Pitre <npitre@mvista.com>
Signed-off-by: Pierre Ossman <drzeus@drzeus.cx>

authored by

Nicolas Pitre and committed by
Pierre Ossman
d1496c39 2342f332

+257 -1
+1 -1
drivers/mmc/core/Makefile
··· 10 10 mmc_core-y := core.o sysfs.o bus.o host.o \ 11 11 mmc.o mmc_ops.o sd.o sd_ops.o \ 12 12 sdio.o sdio_ops.o sdio_bus.o \ 13 - sdio_cis.o sdio_io.o 13 + sdio_cis.o sdio_io.o sdio_irq.o 14 14
+8
drivers/mmc/core/sdio_bus.c
··· 143 143 144 144 drv->remove(func); 145 145 146 + if (func->irq_handler) { 147 + printk(KERN_WARNING "WARNING: driver %s did not remove " 148 + "its interrupt handler!\n", drv->name); 149 + sdio_claim_host(func); 150 + sdio_release_irq(func); 151 + sdio_release_host(func); 152 + } 153 + 146 154 return 0; 147 155 } 148 156
+237
drivers/mmc/core/sdio_irq.c
··· 1 + /* 2 + * linux/drivers/mmc/core/sdio_irq.c 3 + * 4 + * Author: Nicolas Pitre 5 + * Created: June 18, 2007 6 + * Copyright: MontaVista Software Inc. 7 + * 8 + * This program is free software; you can redistribute it and/or modify 9 + * it under the terms of the GNU General Public License as published by 10 + * the Free Software Foundation; either version 2 of the License, or (at 11 + * your option) any later version. 12 + */ 13 + 14 + #include <linux/kernel.h> 15 + #include <linux/sched.h> 16 + #include <linux/kthread.h> 17 + #include <linux/wait.h> 18 + #include <linux/delay.h> 19 + 20 + #include <linux/mmc/core.h> 21 + #include <linux/mmc/host.h> 22 + #include <linux/mmc/card.h> 23 + #include <linux/mmc/sdio.h> 24 + #include <linux/mmc/sdio_func.h> 25 + 26 + #include "sdio_ops.h" 27 + 28 + static int process_sdio_pending_irqs(struct mmc_card *card) 29 + { 30 + int i, ret; 31 + unsigned char pending; 32 + 33 + ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); 34 + if (ret) { 35 + printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n", 36 + mmc_card_id(card), ret); 37 + return ret; 38 + } 39 + 40 + for (i = 1; i <= 7; i++) { 41 + if (pending & (1 << i)) { 42 + struct sdio_func *func = card->sdio_func[i - 1]; 43 + if (!func) { 44 + printk(KERN_WARNING "%s: pending IRQ for " 45 + "non-existant function\n", 46 + sdio_func_id(func)); 47 + } else if (func->irq_handler) { 48 + func->irq_handler(func); 49 + } else 50 + printk(KERN_WARNING "%s: pending IRQ with no handler\n", 51 + sdio_func_id(func)); 52 + } 53 + } 54 + 55 + return 0; 56 + } 57 + 58 + static int sdio_irq_thread(void *_host) 59 + { 60 + struct mmc_host *host = _host; 61 + struct sched_param param = { .sched_priority = 1 }; 62 + unsigned long period; 63 + int ret; 64 + 65 + sched_setscheduler(current, SCHED_FIFO, &param); 66 + 67 + /* 68 + * We want to allow for SDIO cards to work even on non SDIO 69 + * aware hosts. One thing that non SDIO host cannot do is 70 + * asynchronous notification of pending SDIO card interrupts 71 + * hence we poll for them in that case. 72 + */ 73 + period = msecs_to_jiffies(10); 74 + 75 + pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", 76 + mmc_hostname(host), period); 77 + 78 + do { 79 + /* 80 + * We claim the host here on drivers behalf for a couple 81 + * reasons: 82 + * 83 + * 1) it is already needed to retrieve the CCCR_INTx; 84 + * 2) we want the driver(s) to clear the IRQ condition ASAP; 85 + * 3) we need to control the abort condition locally. 86 + * 87 + * Just like traditional hard IRQ handlers, we expect SDIO 88 + * IRQ handlers to be quick and to the point, so that the 89 + * holding of the host lock does not cover too much work 90 + * that doesn't require that lock to be held. 91 + */ 92 + ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); 93 + if (ret) 94 + break; 95 + ret = process_sdio_pending_irqs(host->card); 96 + mmc_release_host(host); 97 + 98 + /* 99 + * Give other threads a chance to run in the presence of 100 + * errors. FIXME: determine if due to card removal and 101 + * possibly exit this thread if so. 102 + */ 103 + if (ret) 104 + ssleep(1); 105 + 106 + set_task_state(current, TASK_INTERRUPTIBLE); 107 + if (!kthread_should_stop()) 108 + schedule_timeout(period); 109 + set_task_state(current, TASK_RUNNING); 110 + } while (!kthread_should_stop()); 111 + 112 + pr_debug("%s: IRQ thread exiting with code %d\n", 113 + mmc_hostname(host), ret); 114 + 115 + return ret; 116 + } 117 + 118 + static int sdio_card_irq_get(struct mmc_card *card) 119 + { 120 + struct mmc_host *host = card->host; 121 + 122 + BUG_ON(!host->claimed); 123 + 124 + if (!host->sdio_irqs++) { 125 + atomic_set(&host->sdio_irq_thread_abort, 0); 126 + host->sdio_irq_thread = 127 + kthread_run(sdio_irq_thread, host, "ksdiorqd"); 128 + if (IS_ERR(host->sdio_irq_thread)) { 129 + int err = PTR_ERR(host->sdio_irq_thread); 130 + host->sdio_irqs--; 131 + return err; 132 + } 133 + } 134 + 135 + return 0; 136 + } 137 + 138 + static int sdio_card_irq_put(struct mmc_card *card) 139 + { 140 + struct mmc_host *host = card->host; 141 + 142 + BUG_ON(!host->claimed); 143 + BUG_ON(host->sdio_irqs < 1); 144 + 145 + if (!--host->sdio_irqs) { 146 + atomic_set(&host->sdio_irq_thread_abort, 1); 147 + kthread_stop(host->sdio_irq_thread); 148 + } 149 + 150 + return 0; 151 + } 152 + 153 + /** 154 + * sdio_claim_irq - claim the IRQ for a SDIO function 155 + * @func: SDIO function 156 + * @handler: IRQ handler callback 157 + * 158 + * Claim and activate the IRQ for the given SDIO function. The provided 159 + * handler will be called when that IRQ is asserted. The host is always 160 + * claimed already when the handler is called so the handler must not 161 + * call sdio_claim_host() nor sdio_release_host(). 162 + */ 163 + int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler) 164 + { 165 + int ret; 166 + unsigned char reg; 167 + 168 + BUG_ON(!func); 169 + BUG_ON(!func->card); 170 + 171 + pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func)); 172 + 173 + if (func->irq_handler) { 174 + pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func)); 175 + return -EBUSY; 176 + } 177 + 178 + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg); 179 + if (ret) 180 + return ret; 181 + 182 + reg |= 1 << func->num; 183 + 184 + reg |= 1; /* Master interrupt enable */ 185 + 186 + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); 187 + if (ret) 188 + return ret; 189 + 190 + func->irq_handler = handler; 191 + ret = sdio_card_irq_get(func->card); 192 + if (ret) 193 + func->irq_handler = NULL; 194 + 195 + return ret; 196 + } 197 + EXPORT_SYMBOL_GPL(sdio_claim_irq); 198 + 199 + /** 200 + * sdio_release_irq - release the IRQ for a SDIO function 201 + * @func: SDIO function 202 + * 203 + * Disable and release the IRQ for the given SDIO function. 204 + */ 205 + int sdio_release_irq(struct sdio_func *func) 206 + { 207 + int ret; 208 + unsigned char reg; 209 + 210 + BUG_ON(!func); 211 + BUG_ON(!func->card); 212 + 213 + pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func)); 214 + 215 + if (func->irq_handler) { 216 + func->irq_handler = NULL; 217 + sdio_card_irq_put(func->card); 218 + } 219 + 220 + ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg); 221 + if (ret) 222 + return ret; 223 + 224 + reg &= ~(1 << func->num); 225 + 226 + /* Disable master interrupt with the last function interrupt */ 227 + if (!(reg & 0xFE)) 228 + reg = 0; 229 + 230 + ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); 231 + if (ret) 232 + return ret; 233 + 234 + return 0; 235 + } 236 + EXPORT_SYMBOL_GPL(sdio_release_irq); 237 +
+4
include/linux/mmc/host.h
··· 123 123 unsigned int bus_refs; /* reference counter */ 124 124 unsigned int bus_dead:1; /* bus has been released */ 125 125 126 + unsigned int sdio_irqs; 127 + struct task_struct *sdio_irq_thread; 128 + atomic_t sdio_irq_thread_abort; 129 + 126 130 unsigned long private[0] ____cacheline_aligned; 127 131 }; 128 132
+7
include/linux/mmc/sdio_func.h
··· 16 16 #include <linux/mod_devicetable.h> 17 17 18 18 struct mmc_card; 19 + struct sdio_func; 20 + 21 + typedef void (sdio_irq_handler_t)(struct sdio_func *); 19 22 20 23 /* 21 24 * SDIO function CIS tuple (unknown to the core) ··· 36 33 struct sdio_func { 37 34 struct mmc_card *card; /* the card this device belongs to */ 38 35 struct device dev; /* the device */ 36 + sdio_irq_handler_t *irq_handler; /* IRQ callback */ 39 37 unsigned int num; /* function number */ 40 38 41 39 unsigned char class; /* standard interface class */ ··· 108 104 109 105 extern int sdio_enable_func(struct sdio_func *func); 110 106 extern int sdio_disable_func(struct sdio_func *func); 107 + 108 + extern int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler); 109 + extern int sdio_release_irq(struct sdio_func *func); 111 110 112 111 extern unsigned char sdio_readb(struct sdio_func *func, 113 112 unsigned int addr, int *err_ret);