xref: /linux/drivers/mmc/core/sdio_irq.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2d1496c39SNicolas Pitre /*
3d1496c39SNicolas Pitre  * linux/drivers/mmc/core/sdio_irq.c
4d1496c39SNicolas Pitre  *
5d1496c39SNicolas Pitre  * Author:      Nicolas Pitre
6d1496c39SNicolas Pitre  * Created:     June 18, 2007
7d1496c39SNicolas Pitre  * Copyright:   MontaVista Software Inc.
8d1496c39SNicolas Pitre  *
9e633b7bcSPierre Ossman  * Copyright 2008 Pierre Ossman
10d1496c39SNicolas Pitre  */
11d1496c39SNicolas Pitre 
12d1496c39SNicolas Pitre #include <linux/kernel.h>
13d1496c39SNicolas Pitre #include <linux/sched.h>
14ae7e81c0SIngo Molnar #include <uapi/linux/sched/types.h>
15d1496c39SNicolas Pitre #include <linux/kthread.h>
163ef77af1SPaul Gortmaker #include <linux/export.h>
17d1496c39SNicolas Pitre #include <linux/wait.h>
18d1496c39SNicolas Pitre #include <linux/delay.h>
19d1496c39SNicolas Pitre 
20d1496c39SNicolas Pitre #include <linux/mmc/core.h>
21d1496c39SNicolas Pitre #include <linux/mmc/host.h>
22d1496c39SNicolas Pitre #include <linux/mmc/card.h>
23d1496c39SNicolas Pitre #include <linux/mmc/sdio.h>
24d1496c39SNicolas Pitre #include <linux/mmc/sdio_func.h>
25d1496c39SNicolas Pitre 
26d1496c39SNicolas Pitre #include "sdio_ops.h"
2755244c56SUlf Hansson #include "core.h"
284facdde1SUlf Hansson #include "card.h"
29d1496c39SNicolas Pitre 
sdio_get_pending_irqs(struct mmc_host * host,u8 * pending)30a29b5fccSMatthias Kaehlcke static int sdio_get_pending_irqs(struct mmc_host *host, u8 *pending)
31a29b5fccSMatthias Kaehlcke {
32a29b5fccSMatthias Kaehlcke 	struct mmc_card *card = host->card;
33a29b5fccSMatthias Kaehlcke 	int ret;
34a29b5fccSMatthias Kaehlcke 
35a29b5fccSMatthias Kaehlcke 	WARN_ON(!host->claimed);
36a29b5fccSMatthias Kaehlcke 
37a29b5fccSMatthias Kaehlcke 	ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, pending);
38a29b5fccSMatthias Kaehlcke 	if (ret) {
39a29b5fccSMatthias Kaehlcke 		pr_debug("%s: error %d reading SDIO_CCCR_INTx\n",
40a29b5fccSMatthias Kaehlcke 		       mmc_card_id(card), ret);
41a29b5fccSMatthias Kaehlcke 		return ret;
42a29b5fccSMatthias Kaehlcke 	}
43a29b5fccSMatthias Kaehlcke 
44a29b5fccSMatthias Kaehlcke 	if (*pending && mmc_card_broken_irq_polling(card) &&
45a29b5fccSMatthias Kaehlcke 	    !(host->caps & MMC_CAP_SDIO_IRQ)) {
46a29b5fccSMatthias Kaehlcke 		unsigned char dummy;
47a29b5fccSMatthias Kaehlcke 
48a29b5fccSMatthias Kaehlcke 		/* A fake interrupt could be created when we poll SDIO_CCCR_INTx
49a29b5fccSMatthias Kaehlcke 		 * register with a Marvell SD8797 card. A dummy CMD52 read to
50a29b5fccSMatthias Kaehlcke 		 * function 0 register 0xff can avoid this.
51a29b5fccSMatthias Kaehlcke 		 */
52a29b5fccSMatthias Kaehlcke 		mmc_io_rw_direct(card, 0, 0, 0xff, 0, &dummy);
53a29b5fccSMatthias Kaehlcke 	}
54a29b5fccSMatthias Kaehlcke 
55a29b5fccSMatthias Kaehlcke 	return 0;
56a29b5fccSMatthias Kaehlcke }
57a29b5fccSMatthias Kaehlcke 
process_sdio_pending_irqs(struct mmc_host * host)58bbbc4c4dSNicolas Pitre static int process_sdio_pending_irqs(struct mmc_host *host)
59d1496c39SNicolas Pitre {
60bbbc4c4dSNicolas Pitre 	struct mmc_card *card = host->card;
616f4285d1SPierre Ossman 	int i, ret, count;
6236d57efbSUlf Hansson 	bool sdio_irq_pending = host->sdio_irq_pending;
63d1496c39SNicolas Pitre 	unsigned char pending;
6406e8935fSStefan Nilsson XK 	struct sdio_func *func;
6506e8935fSStefan Nilsson XK 
6683293386SUlf Hansson 	/* Don't process SDIO IRQs if the card is suspended. */
6783293386SUlf Hansson 	if (mmc_card_suspended(card))
6883293386SUlf Hansson 		return 0;
6983293386SUlf Hansson 
7036d57efbSUlf Hansson 	/* Clear the flag to indicate that we have processed the IRQ. */
7136d57efbSUlf Hansson 	host->sdio_irq_pending = false;
7236d57efbSUlf Hansson 
7306e8935fSStefan Nilsson XK 	/*
7406e8935fSStefan Nilsson XK 	 * Optimization, if there is only 1 function interrupt registered
75bbbc4c4dSNicolas Pitre 	 * and we know an IRQ was signaled then call irq handler directly.
76bbbc4c4dSNicolas Pitre 	 * Otherwise do the full probe.
7706e8935fSStefan Nilsson XK 	 */
7806e8935fSStefan Nilsson XK 	func = card->sdio_single_irq;
7936d57efbSUlf Hansson 	if (func && sdio_irq_pending) {
8006e8935fSStefan Nilsson XK 		func->irq_handler(func);
8106e8935fSStefan Nilsson XK 		return 1;
8206e8935fSStefan Nilsson XK 	}
83d1496c39SNicolas Pitre 
84a29b5fccSMatthias Kaehlcke 	ret = sdio_get_pending_irqs(host, &pending);
85a29b5fccSMatthias Kaehlcke 	if (ret)
86d1496c39SNicolas Pitre 		return ret;
87e5624054SBing Zhao 
886f4285d1SPierre Ossman 	count = 0;
89d1496c39SNicolas Pitre 	for (i = 1; i <= 7; i++) {
90d1496c39SNicolas Pitre 		if (pending & (1 << i)) {
9106e8935fSStefan Nilsson XK 			func = card->sdio_func[i - 1];
92d1496c39SNicolas Pitre 			if (!func) {
936606110dSJoe Perches 				pr_warn("%s: pending IRQ for non-existent function\n",
943e01e4bcSNicolas Pitre 					mmc_card_id(card));
95599473cfSNicolas Pitre 				ret = -EINVAL;
96d1496c39SNicolas Pitre 			} else if (func->irq_handler) {
97d1496c39SNicolas Pitre 				func->irq_handler(func);
986f4285d1SPierre Ossman 				count++;
99599473cfSNicolas Pitre 			} else {
1006606110dSJoe Perches 				pr_warn("%s: pending IRQ with no handler\n",
101d1496c39SNicolas Pitre 					sdio_func_id(func));
102599473cfSNicolas Pitre 				ret = -EINVAL;
103599473cfSNicolas Pitre 			}
104d1496c39SNicolas Pitre 		}
105d1496c39SNicolas Pitre 	}
106d1496c39SNicolas Pitre 
107599473cfSNicolas Pitre 	if (count)
1086f4285d1SPierre Ossman 		return count;
109599473cfSNicolas Pitre 
110599473cfSNicolas Pitre 	return ret;
111d1496c39SNicolas Pitre }
112d1496c39SNicolas Pitre 
sdio_run_irqs(struct mmc_host * host)113cf4b20ecSUlf Hansson static void sdio_run_irqs(struct mmc_host *host)
114bf3b5ec6SRussell King {
115bf3b5ec6SRussell King 	mmc_claim_host(host);
116e3a84267SUlf Hansson 	if (host->sdio_irqs) {
117bf3b5ec6SRussell King 		process_sdio_pending_irqs(host);
11851133850SUlf Hansson 		if (!host->sdio_irq_pending)
11968269660SUlf Hansson 			host->ops->ack_sdio_irq(host);
120e3a84267SUlf Hansson 	}
121bf3b5ec6SRussell King 	mmc_release_host(host);
122bf3b5ec6SRussell King }
123bf3b5ec6SRussell King 
sdio_irq_work(struct work_struct * work)12468269660SUlf Hansson void sdio_irq_work(struct work_struct *work)
12568269660SUlf Hansson {
12668269660SUlf Hansson 	struct mmc_host *host =
127*1dd611a9SHeiner Kallweit 		container_of(work, struct mmc_host, sdio_irq_work);
12868269660SUlf Hansson 
12968269660SUlf Hansson 	sdio_run_irqs(host);
13068269660SUlf Hansson }
13168269660SUlf Hansson 
sdio_signal_irq(struct mmc_host * host)13268269660SUlf Hansson void sdio_signal_irq(struct mmc_host *host)
13368269660SUlf Hansson {
13436d57efbSUlf Hansson 	host->sdio_irq_pending = true;
135*1dd611a9SHeiner Kallweit 	schedule_work(&host->sdio_irq_work);
13668269660SUlf Hansson }
13768269660SUlf Hansson EXPORT_SYMBOL_GPL(sdio_signal_irq);
13868269660SUlf Hansson 
sdio_irq_thread(void * _host)139d1496c39SNicolas Pitre static int sdio_irq_thread(void *_host)
140d1496c39SNicolas Pitre {
141d1496c39SNicolas Pitre 	struct mmc_host *host = _host;
1426f4285d1SPierre Ossman 	unsigned long period, idle_period;
143d1496c39SNicolas Pitre 	int ret;
144d1496c39SNicolas Pitre 
145f8ec806bSPeter Zijlstra 	sched_set_fifo_low(current);
146d1496c39SNicolas Pitre 
147d1496c39SNicolas Pitre 	/*
148d1496c39SNicolas Pitre 	 * We want to allow for SDIO cards to work even on non SDIO
149d1496c39SNicolas Pitre 	 * aware hosts.  One thing that non SDIO host cannot do is
150d1496c39SNicolas Pitre 	 * asynchronous notification of pending SDIO card interrupts
151d1496c39SNicolas Pitre 	 * hence we poll for them in that case.
152d1496c39SNicolas Pitre 	 */
1536f4285d1SPierre Ossman 	idle_period = msecs_to_jiffies(10);
15417b759afSNicolas Pitre 	period = (host->caps & MMC_CAP_SDIO_IRQ) ?
1556f4285d1SPierre Ossman 		MAX_SCHEDULE_TIMEOUT : idle_period;
156d1496c39SNicolas Pitre 
157d1496c39SNicolas Pitre 	pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
158d1496c39SNicolas Pitre 		 mmc_hostname(host), period);
159d1496c39SNicolas Pitre 
160d1496c39SNicolas Pitre 	do {
161d1496c39SNicolas Pitre 		/*
162d1496c39SNicolas Pitre 		 * We claim the host here on drivers behalf for a couple
163d1496c39SNicolas Pitre 		 * reasons:
164d1496c39SNicolas Pitre 		 *
165d1496c39SNicolas Pitre 		 * 1) it is already needed to retrieve the CCCR_INTx;
166d1496c39SNicolas Pitre 		 * 2) we want the driver(s) to clear the IRQ condition ASAP;
167d1496c39SNicolas Pitre 		 * 3) we need to control the abort condition locally.
168d1496c39SNicolas Pitre 		 *
169d1496c39SNicolas Pitre 		 * Just like traditional hard IRQ handlers, we expect SDIO
170d1496c39SNicolas Pitre 		 * IRQ handlers to be quick and to the point, so that the
171d1496c39SNicolas Pitre 		 * holding of the host lock does not cover too much work
172d1496c39SNicolas Pitre 		 * that doesn't require that lock to be held.
173d1496c39SNicolas Pitre 		 */
1746c0cedd1SAdrian Hunter 		ret = __mmc_claim_host(host, NULL,
1756c0cedd1SAdrian Hunter 				       &host->sdio_irq_thread_abort);
176d1496c39SNicolas Pitre 		if (ret)
177d1496c39SNicolas Pitre 			break;
178bbbc4c4dSNicolas Pitre 		ret = process_sdio_pending_irqs(host);
179d1496c39SNicolas Pitre 		mmc_release_host(host);
180d1496c39SNicolas Pitre 
181d1496c39SNicolas Pitre 		/*
182d1496c39SNicolas Pitre 		 * Give other threads a chance to run in the presence of
183e633b7bcSPierre Ossman 		 * errors.
184d1496c39SNicolas Pitre 		 */
185e633b7bcSPierre Ossman 		if (ret < 0) {
186e633b7bcSPierre Ossman 			set_current_state(TASK_INTERRUPTIBLE);
187e633b7bcSPierre Ossman 			if (!kthread_should_stop())
188e633b7bcSPierre Ossman 				schedule_timeout(HZ);
189e633b7bcSPierre Ossman 			set_current_state(TASK_RUNNING);
190e633b7bcSPierre Ossman 		}
191d1496c39SNicolas Pitre 
1926f4285d1SPierre Ossman 		/*
1936f4285d1SPierre Ossman 		 * Adaptive polling frequency based on the assumption
1946f4285d1SPierre Ossman 		 * that an interrupt will be closely followed by more.
1956f4285d1SPierre Ossman 		 * This has a substantial benefit for network devices.
1966f4285d1SPierre Ossman 		 */
1976f4285d1SPierre Ossman 		if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
1986f4285d1SPierre Ossman 			if (ret > 0)
1996f4285d1SPierre Ossman 				period /= 2;
2006f4285d1SPierre Ossman 			else {
2016f4285d1SPierre Ossman 				period++;
2026f4285d1SPierre Ossman 				if (period > idle_period)
2036f4285d1SPierre Ossman 					period = idle_period;
2046f4285d1SPierre Ossman 			}
2056f4285d1SPierre Ossman 		}
2066f4285d1SPierre Ossman 
2076fee65cfSRobert P. J. Day 		set_current_state(TASK_INTERRUPTIBLE);
2089eadcc05SUlf Hansson 		if (host->caps & MMC_CAP_SDIO_IRQ)
20917b759afSNicolas Pitre 			host->ops->enable_sdio_irq(host, 1);
210d1496c39SNicolas Pitre 		if (!kthread_should_stop())
211d1496c39SNicolas Pitre 			schedule_timeout(period);
2126fee65cfSRobert P. J. Day 		set_current_state(TASK_RUNNING);
213d1496c39SNicolas Pitre 	} while (!kthread_should_stop());
214d1496c39SNicolas Pitre 
2159eadcc05SUlf Hansson 	if (host->caps & MMC_CAP_SDIO_IRQ)
21617b759afSNicolas Pitre 		host->ops->enable_sdio_irq(host, 0);
21717b759afSNicolas Pitre 
218d1496c39SNicolas Pitre 	pr_debug("%s: IRQ thread exiting with code %d\n",
219d1496c39SNicolas Pitre 		 mmc_hostname(host), ret);
220d1496c39SNicolas Pitre 
221d1496c39SNicolas Pitre 	return ret;
222d1496c39SNicolas Pitre }
223d1496c39SNicolas Pitre 
sdio_card_irq_get(struct mmc_card * card)224d1496c39SNicolas Pitre static int sdio_card_irq_get(struct mmc_card *card)
225d1496c39SNicolas Pitre {
226d1496c39SNicolas Pitre 	struct mmc_host *host = card->host;
227d1496c39SNicolas Pitre 
228d84075c8SPierre Ossman 	WARN_ON(!host->claimed);
229d1496c39SNicolas Pitre 
230d1496c39SNicolas Pitre 	if (!host->sdio_irqs++) {
231bf3b5ec6SRussell King 		if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
232d1496c39SNicolas Pitre 			atomic_set(&host->sdio_irq_thread_abort, 0);
233d1496c39SNicolas Pitre 			host->sdio_irq_thread =
234bf3b5ec6SRussell King 				kthread_run(sdio_irq_thread, host,
235bf3b5ec6SRussell King 					    "ksdioirqd/%s", mmc_hostname(host));
236d1496c39SNicolas Pitre 			if (IS_ERR(host->sdio_irq_thread)) {
237d1496c39SNicolas Pitre 				int err = PTR_ERR(host->sdio_irq_thread);
238d1496c39SNicolas Pitre 				host->sdio_irqs--;
239d1496c39SNicolas Pitre 				return err;
240d1496c39SNicolas Pitre 			}
241dea67c4eSFu Zhonghui 		} else if (host->caps & MMC_CAP_SDIO_IRQ) {
242bf3b5ec6SRussell King 			host->ops->enable_sdio_irq(host, 1);
243bf3b5ec6SRussell King 		}
244d1496c39SNicolas Pitre 	}
245d1496c39SNicolas Pitre 
246d1496c39SNicolas Pitre 	return 0;
247d1496c39SNicolas Pitre }
248d1496c39SNicolas Pitre 
sdio_card_irq_put(struct mmc_card * card)249d1496c39SNicolas Pitre static int sdio_card_irq_put(struct mmc_card *card)
250d1496c39SNicolas Pitre {
251d1496c39SNicolas Pitre 	struct mmc_host *host = card->host;
252d1496c39SNicolas Pitre 
253d84075c8SPierre Ossman 	WARN_ON(!host->claimed);
2545df0e823SShawn Lin 
2555df0e823SShawn Lin 	if (host->sdio_irqs < 1)
2565df0e823SShawn Lin 		return -EINVAL;
257d1496c39SNicolas Pitre 
258d1496c39SNicolas Pitre 	if (!--host->sdio_irqs) {
259bf3b5ec6SRussell King 		if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
260d1496c39SNicolas Pitre 			atomic_set(&host->sdio_irq_thread_abort, 1);
261d1496c39SNicolas Pitre 			kthread_stop(host->sdio_irq_thread);
262dea67c4eSFu Zhonghui 		} else if (host->caps & MMC_CAP_SDIO_IRQ) {
263bf3b5ec6SRussell King 			host->ops->enable_sdio_irq(host, 0);
264bf3b5ec6SRussell King 		}
265d1496c39SNicolas Pitre 	}
266d1496c39SNicolas Pitre 
267d1496c39SNicolas Pitre 	return 0;
268d1496c39SNicolas Pitre }
269d1496c39SNicolas Pitre 
27006e8935fSStefan Nilsson XK /* If there is only 1 function registered set sdio_single_irq */
sdio_single_irq_set(struct mmc_card * card)27106e8935fSStefan Nilsson XK static void sdio_single_irq_set(struct mmc_card *card)
27206e8935fSStefan Nilsson XK {
27306e8935fSStefan Nilsson XK 	struct sdio_func *func;
27406e8935fSStefan Nilsson XK 	int i;
27506e8935fSStefan Nilsson XK 
27606e8935fSStefan Nilsson XK 	card->sdio_single_irq = NULL;
27706e8935fSStefan Nilsson XK 	if ((card->host->caps & MMC_CAP_SDIO_IRQ) &&
2786660d0aeSJérôme Pouiller 	    card->host->sdio_irqs == 1) {
27906e8935fSStefan Nilsson XK 		for (i = 0; i < card->sdio_funcs; i++) {
28006e8935fSStefan Nilsson XK 			func = card->sdio_func[i];
28106e8935fSStefan Nilsson XK 			if (func && func->irq_handler) {
28206e8935fSStefan Nilsson XK 				card->sdio_single_irq = func;
28306e8935fSStefan Nilsson XK 				break;
28406e8935fSStefan Nilsson XK 			}
28506e8935fSStefan Nilsson XK 		}
28606e8935fSStefan Nilsson XK 	}
2876660d0aeSJérôme Pouiller }
28806e8935fSStefan Nilsson XK 
289d1496c39SNicolas Pitre /**
290d1496c39SNicolas Pitre  *	sdio_claim_irq - claim the IRQ for a SDIO function
291d1496c39SNicolas Pitre  *	@func: SDIO function
292d1496c39SNicolas Pitre  *	@handler: IRQ handler callback
293d1496c39SNicolas Pitre  *
294d1496c39SNicolas Pitre  *	Claim and activate the IRQ for the given SDIO function. The provided
295d1496c39SNicolas Pitre  *	handler will be called when that IRQ is asserted.  The host is always
2968647d26eSJoel Cunningham  *	claimed already when the handler is called so the handler should not
2978647d26eSJoel Cunningham  *	call sdio_claim_host() or sdio_release_host().
298d1496c39SNicolas Pitre  */
sdio_claim_irq(struct sdio_func * func,sdio_irq_handler_t * handler)299d1496c39SNicolas Pitre int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
300d1496c39SNicolas Pitre {
301d1496c39SNicolas Pitre 	int ret;
302d1496c39SNicolas Pitre 	unsigned char reg;
303d1496c39SNicolas Pitre 
3045df0e823SShawn Lin 	if (!func)
3055df0e823SShawn Lin 		return -EINVAL;
306d1496c39SNicolas Pitre 
307d1496c39SNicolas Pitre 	pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
308d1496c39SNicolas Pitre 
309d1496c39SNicolas Pitre 	if (func->irq_handler) {
310d1496c39SNicolas Pitre 		pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
311d1496c39SNicolas Pitre 		return -EBUSY;
312d1496c39SNicolas Pitre 	}
313d1496c39SNicolas Pitre 
314d1496c39SNicolas Pitre 	ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
315d1496c39SNicolas Pitre 	if (ret)
316d1496c39SNicolas Pitre 		return ret;
317d1496c39SNicolas Pitre 
318d1496c39SNicolas Pitre 	reg |= 1 << func->num;
319d1496c39SNicolas Pitre 
320d1496c39SNicolas Pitre 	reg |= 1; /* Master interrupt enable */
321d1496c39SNicolas Pitre 
322d1496c39SNicolas Pitre 	ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
323d1496c39SNicolas Pitre 	if (ret)
324d1496c39SNicolas Pitre 		return ret;
325d1496c39SNicolas Pitre 
326d1496c39SNicolas Pitre 	func->irq_handler = handler;
327d1496c39SNicolas Pitre 	ret = sdio_card_irq_get(func->card);
328d1496c39SNicolas Pitre 	if (ret)
329d1496c39SNicolas Pitre 		func->irq_handler = NULL;
33006e8935fSStefan Nilsson XK 	sdio_single_irq_set(func->card);
331d1496c39SNicolas Pitre 
332d1496c39SNicolas Pitre 	return ret;
333d1496c39SNicolas Pitre }
334d1496c39SNicolas Pitre EXPORT_SYMBOL_GPL(sdio_claim_irq);
335d1496c39SNicolas Pitre 
336d1496c39SNicolas Pitre /**
337d1496c39SNicolas Pitre  *	sdio_release_irq - release the IRQ for a SDIO function
338d1496c39SNicolas Pitre  *	@func: SDIO function
339d1496c39SNicolas Pitre  *
340d1496c39SNicolas Pitre  *	Disable and release the IRQ for the given SDIO function.
341d1496c39SNicolas Pitre  */
sdio_release_irq(struct sdio_func * func)342d1496c39SNicolas Pitre int sdio_release_irq(struct sdio_func *func)
343d1496c39SNicolas Pitre {
344d1496c39SNicolas Pitre 	int ret;
345d1496c39SNicolas Pitre 	unsigned char reg;
346d1496c39SNicolas Pitre 
3475df0e823SShawn Lin 	if (!func)
3485df0e823SShawn Lin 		return -EINVAL;
349d1496c39SNicolas Pitre 
350d1496c39SNicolas Pitre 	pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
351d1496c39SNicolas Pitre 
352d1496c39SNicolas Pitre 	if (func->irq_handler) {
353d1496c39SNicolas Pitre 		func->irq_handler = NULL;
354d1496c39SNicolas Pitre 		sdio_card_irq_put(func->card);
35506e8935fSStefan Nilsson XK 		sdio_single_irq_set(func->card);
356d1496c39SNicolas Pitre 	}
357d1496c39SNicolas Pitre 
358d1496c39SNicolas Pitre 	ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
359d1496c39SNicolas Pitre 	if (ret)
360d1496c39SNicolas Pitre 		return ret;
361d1496c39SNicolas Pitre 
362d1496c39SNicolas Pitre 	reg &= ~(1 << func->num);
363d1496c39SNicolas Pitre 
364d1496c39SNicolas Pitre 	/* Disable master interrupt with the last function interrupt */
365d1496c39SNicolas Pitre 	if (!(reg & 0xFE))
366d1496c39SNicolas Pitre 		reg = 0;
367d1496c39SNicolas Pitre 
368d1496c39SNicolas Pitre 	ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
369d1496c39SNicolas Pitre 	if (ret)
370d1496c39SNicolas Pitre 		return ret;
371d1496c39SNicolas Pitre 
372d1496c39SNicolas Pitre 	return 0;
373d1496c39SNicolas Pitre }
374d1496c39SNicolas Pitre EXPORT_SYMBOL_GPL(sdio_release_irq);
375d1496c39SNicolas Pitre 
376