| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * linux/drivers/mmc/core/sdio_irq.c | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Author:      Nicolas Pitre | 
					
						
							|  |  |  |  * Created:     June 18, 2007 | 
					
						
							|  |  |  |  * Copyright:   MontaVista Software Inc. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2008-08-31 13:38:54 +02:00
										 |  |  |  * Copyright 2008 Pierre Ossman | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation; either version 2 of the License, or (at | 
					
						
							|  |  |  |  * your option) any later version. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/kthread.h>
 | 
					
						
							| 
									
										
										
										
											2011-07-10 12:42:00 -04:00
										 |  |  | #include <linux/export.h>
 | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | #include <linux/wait.h>
 | 
					
						
							|  |  |  | #include <linux/delay.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/mmc/core.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/host.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/card.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/sdio.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/sdio_func.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "sdio_ops.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-04-16 19:16:54 -04:00
										 |  |  | static int process_sdio_pending_irqs(struct mmc_host *host) | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-04-16 19:16:54 -04:00
										 |  |  | 	struct mmc_card *card = host->card; | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 	int i, ret, count; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	unsigned char pending; | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 	struct sdio_func *func; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Optimization, if there is only 1 function interrupt registered | 
					
						
							| 
									
										
										
										
											2012-04-16 19:16:54 -04:00
										 |  |  | 	 * and we know an IRQ was signaled then call irq handler directly. | 
					
						
							|  |  |  | 	 * Otherwise do the full probe. | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 	 */ | 
					
						
							|  |  |  | 	func = card->sdio_single_irq; | 
					
						
							| 
									
										
										
										
											2012-04-16 19:16:54 -04:00
										 |  |  | 	if (func && host->sdio_irq_pending) { | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 		func->irq_handler(func); | 
					
						
							|  |  |  | 		return 1; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2011-10-11 11:44:09 +05:30
										 |  |  | 		pr_debug("%s: error %d reading SDIO_CCCR_INTx\n", | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		       mmc_card_id(card), ret); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 	count = 0; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	for (i = 1; i <= 7; i++) { | 
					
						
							|  |  |  | 		if (pending & (1 << i)) { | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 			func = card->sdio_func[i - 1]; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 			if (!func) { | 
					
						
							| 
									
										
										
										
											2011-10-11 11:44:09 +05:30
										 |  |  | 				pr_warning("%s: pending IRQ for " | 
					
						
							| 
									
										
										
										
											2011-03-30 22:57:33 -03:00
										 |  |  | 					"non-existent function\n", | 
					
						
							| 
									
										
										
										
											2007-10-03 15:32:10 -04:00
										 |  |  | 					mmc_card_id(card)); | 
					
						
							| 
									
										
										
										
											2007-10-03 15:32:57 -04:00
										 |  |  | 				ret = -EINVAL; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 			} else if (func->irq_handler) { | 
					
						
							|  |  |  | 				func->irq_handler(func); | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 				count++; | 
					
						
							| 
									
										
										
										
											2007-10-03 15:32:57 -04:00
										 |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2011-10-11 11:44:09 +05:30
										 |  |  | 				pr_warning("%s: pending IRQ with no handler\n", | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 				       sdio_func_id(func)); | 
					
						
							| 
									
										
										
										
											2007-10-03 15:32:57 -04:00
										 |  |  | 				ret = -EINVAL; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-03 15:32:57 -04:00
										 |  |  | 	if (count) | 
					
						
							|  |  |  | 		return count; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int sdio_irq_thread(void *_host) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mmc_host *host = _host; | 
					
						
							|  |  |  | 	struct sched_param param = { .sched_priority = 1 }; | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 	unsigned long period, idle_period; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sched_setscheduler(current, SCHED_FIFO, ¶m); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We want to allow for SDIO cards to work even on non SDIO | 
					
						
							|  |  |  | 	 * aware hosts.  One thing that non SDIO host cannot do is | 
					
						
							|  |  |  | 	 * asynchronous notification of pending SDIO card interrupts | 
					
						
							|  |  |  | 	 * hence we poll for them in that case. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 	idle_period = msecs_to_jiffies(10); | 
					
						
							| 
									
										
										
										
											2007-07-24 02:09:39 -04:00
										 |  |  | 	period = (host->caps & MMC_CAP_SDIO_IRQ) ? | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 		MAX_SCHEDULE_TIMEOUT : idle_period; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n", | 
					
						
							|  |  |  | 		 mmc_hostname(host), period); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	do { | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * We claim the host here on drivers behalf for a couple | 
					
						
							|  |  |  | 		 * reasons: | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * 1) it is already needed to retrieve the CCCR_INTx; | 
					
						
							|  |  |  | 		 * 2) we want the driver(s) to clear the IRQ condition ASAP; | 
					
						
							|  |  |  | 		 * 3) we need to control the abort condition locally. | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * Just like traditional hard IRQ handlers, we expect SDIO | 
					
						
							|  |  |  | 		 * IRQ handlers to be quick and to the point, so that the | 
					
						
							|  |  |  | 		 * holding of the host lock does not cover too much work | 
					
						
							|  |  |  | 		 * that doesn't require that lock to be held. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2012-04-16 19:16:54 -04:00
										 |  |  | 		ret = process_sdio_pending_irqs(host); | 
					
						
							|  |  |  | 		host->sdio_irq_pending = false; | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		mmc_release_host(host); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Give other threads a chance to run in the presence of | 
					
						
							| 
									
										
										
										
											2008-08-31 13:38:54 +02:00
										 |  |  | 		 * errors. | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2008-08-31 13:38:54 +02:00
										 |  |  | 		if (ret < 0) { | 
					
						
							|  |  |  | 			set_current_state(TASK_INTERRUPTIBLE); | 
					
						
							|  |  |  | 			if (!kthread_should_stop()) | 
					
						
							|  |  |  | 				schedule_timeout(HZ); | 
					
						
							|  |  |  | 			set_current_state(TASK_RUNNING); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-09-27 10:48:29 +02:00
										 |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * Adaptive polling frequency based on the assumption | 
					
						
							|  |  |  | 		 * that an interrupt will be closely followed by more. | 
					
						
							|  |  |  | 		 * This has a substantial benefit for network devices. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		if (!(host->caps & MMC_CAP_SDIO_IRQ)) { | 
					
						
							|  |  |  | 			if (ret > 0) | 
					
						
							|  |  |  | 				period /= 2; | 
					
						
							|  |  |  | 			else { | 
					
						
							|  |  |  | 				period++; | 
					
						
							|  |  |  | 				if (period > idle_period) | 
					
						
							|  |  |  | 					period = idle_period; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-03-28 14:34:47 -07:00
										 |  |  | 		set_current_state(TASK_INTERRUPTIBLE); | 
					
						
							| 
									
										
										
										
											2012-02-04 16:14:50 -05:00
										 |  |  | 		if (host->caps & MMC_CAP_SDIO_IRQ) { | 
					
						
							|  |  |  | 			mmc_host_clk_hold(host); | 
					
						
							| 
									
										
										
										
											2007-07-24 02:09:39 -04:00
										 |  |  | 			host->ops->enable_sdio_irq(host, 1); | 
					
						
							| 
									
										
										
										
											2012-02-04 16:14:50 -05:00
										 |  |  | 			mmc_host_clk_release(host); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		if (!kthread_should_stop()) | 
					
						
							|  |  |  | 			schedule_timeout(period); | 
					
						
							| 
									
										
										
										
											2008-03-28 14:34:47 -07:00
										 |  |  | 		set_current_state(TASK_RUNNING); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	} while (!kthread_should_stop()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-04 16:14:50 -05:00
										 |  |  | 	if (host->caps & MMC_CAP_SDIO_IRQ) { | 
					
						
							|  |  |  | 		mmc_host_clk_hold(host); | 
					
						
							| 
									
										
										
										
											2007-07-24 02:09:39 -04:00
										 |  |  | 		host->ops->enable_sdio_irq(host, 0); | 
					
						
							| 
									
										
										
										
											2012-02-04 16:14:50 -05:00
										 |  |  | 		mmc_host_clk_release(host); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-07-24 02:09:39 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	pr_debug("%s: IRQ thread exiting with code %d\n", | 
					
						
							|  |  |  | 		 mmc_hostname(host), ret); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int sdio_card_irq_get(struct mmc_card *card) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mmc_host *host = card->host; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-08-09 13:23:56 +02:00
										 |  |  | 	WARN_ON(!host->claimed); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!host->sdio_irqs++) { | 
					
						
							|  |  |  | 		atomic_set(&host->sdio_irq_thread_abort, 0); | 
					
						
							|  |  |  | 		host->sdio_irq_thread = | 
					
						
							| 
									
										
										
										
											2008-08-31 13:42:00 +02:00
										 |  |  | 			kthread_run(sdio_irq_thread, host, "ksdioirqd/%s", | 
					
						
							|  |  |  | 				mmc_hostname(host)); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 		if (IS_ERR(host->sdio_irq_thread)) { | 
					
						
							|  |  |  | 			int err = PTR_ERR(host->sdio_irq_thread); | 
					
						
							|  |  |  | 			host->sdio_irqs--; | 
					
						
							|  |  |  | 			return err; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int sdio_card_irq_put(struct mmc_card *card) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mmc_host *host = card->host; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-08-09 13:23:56 +02:00
										 |  |  | 	WARN_ON(!host->claimed); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	BUG_ON(host->sdio_irqs < 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!--host->sdio_irqs) { | 
					
						
							|  |  |  | 		atomic_set(&host->sdio_irq_thread_abort, 1); | 
					
						
							|  |  |  | 		kthread_stop(host->sdio_irq_thread); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | /* If there is only 1 function registered set sdio_single_irq */ | 
					
						
							|  |  |  | static void sdio_single_irq_set(struct mmc_card *card) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct sdio_func *func; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	card->sdio_single_irq = NULL; | 
					
						
							|  |  |  | 	if ((card->host->caps & MMC_CAP_SDIO_IRQ) && | 
					
						
							|  |  |  | 	    card->host->sdio_irqs == 1) | 
					
						
							|  |  |  | 		for (i = 0; i < card->sdio_funcs; i++) { | 
					
						
							|  |  |  | 		       func = card->sdio_func[i]; | 
					
						
							|  |  |  | 		       if (func && func->irq_handler) { | 
					
						
							|  |  |  | 			       card->sdio_single_irq = func; | 
					
						
							|  |  |  | 			       break; | 
					
						
							|  |  |  | 		       } | 
					
						
							|  |  |  | 	       } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | /**
 | 
					
						
							|  |  |  |  *	sdio_claim_irq - claim the IRQ for a SDIO function | 
					
						
							|  |  |  |  *	@func: SDIO function | 
					
						
							|  |  |  |  *	@handler: IRQ handler callback | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Claim and activate the IRQ for the given SDIO function. The provided | 
					
						
							|  |  |  |  *	handler will be called when that IRQ is asserted.  The host is always | 
					
						
							|  |  |  |  *	claimed already when the handler is called so the handler must not | 
					
						
							|  |  |  |  *	call sdio_claim_host() nor sdio_release_host(). | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	unsigned char reg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	BUG_ON(!func); | 
					
						
							|  |  |  | 	BUG_ON(!func->card); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (func->irq_handler) { | 
					
						
							|  |  |  | 		pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func)); | 
					
						
							|  |  |  | 		return -EBUSY; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reg |= 1 << func->num; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reg |= 1; /* Master interrupt enable */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	func->irq_handler = handler; | 
					
						
							|  |  |  | 	ret = sdio_card_irq_get(func->card); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		func->irq_handler = NULL; | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 	sdio_single_irq_set(func->card); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(sdio_claim_irq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  *	sdio_release_irq - release the IRQ for a SDIO function | 
					
						
							|  |  |  |  *	@func: SDIO function | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *	Disable and release the IRQ for the given SDIO function. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | int sdio_release_irq(struct sdio_func *func) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	unsigned char reg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	BUG_ON(!func); | 
					
						
							|  |  |  | 	BUG_ON(!func->card); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (func->irq_handler) { | 
					
						
							|  |  |  | 		func->irq_handler = NULL; | 
					
						
							|  |  |  | 		sdio_card_irq_put(func->card); | 
					
						
							| 
									
										
										
										
											2011-05-11 17:48:05 +02:00
										 |  |  | 		sdio_single_irq_set(func->card); | 
					
						
							| 
									
										
										
										
											2007-06-30 16:29:41 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, ®); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reg &= ~(1 << func->num); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Disable master interrupt with the last function interrupt */ | 
					
						
							|  |  |  | 	if (!(reg & 0xFE)) | 
					
						
							|  |  |  | 		reg = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(sdio_release_irq); | 
					
						
							|  |  |  | 
 |