linux/drivers/mmc/core/sdio_irq.c
<<
>>
Prefs
   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 * Copyright 2008 Pierre Ossman
   9 *
  10 * This program is free software; you can redistribute it and/or modify
  11 * it under the terms of the GNU General Public License as published by
  12 * the Free Software Foundation; either version 2 of the License, or (at
  13 * your option) any later version.
  14 */
  15
  16#include <linux/kernel.h>
  17#include <linux/sched.h>
  18#include <linux/kthread.h>
  19#include <linux/wait.h>
  20#include <linux/delay.h>
  21
  22#include <linux/mmc/core.h>
  23#include <linux/mmc/host.h>
  24#include <linux/mmc/card.h>
  25#include <linux/mmc/sdio.h>
  26#include <linux/mmc/sdio_func.h>
  27
  28#include "sdio_ops.h"
  29
  30static int process_sdio_pending_irqs(struct mmc_card *card)
  31{
  32        int i, ret, count;
  33        unsigned char pending;
  34
  35        ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, &pending);
  36        if (ret) {
  37                printk(KERN_DEBUG "%s: error %d reading SDIO_CCCR_INTx\n",
  38                       mmc_card_id(card), ret);
  39                return ret;
  40        }
  41
  42        count = 0;
  43        for (i = 1; i <= 7; i++) {
  44                if (pending & (1 << i)) {
  45                        struct sdio_func *func = card->sdio_func[i - 1];
  46                        if (!func) {
  47                                printk(KERN_WARNING "%s: pending IRQ for "
  48                                        "non-existant function\n",
  49                                        mmc_card_id(card));
  50                                ret = -EINVAL;
  51                        } else if (func->irq_handler) {
  52                                func->irq_handler(func);
  53                                count++;
  54                        } else {
  55                                printk(KERN_WARNING "%s: pending IRQ with no handler\n",
  56                                       sdio_func_id(func));
  57                                ret = -EINVAL;
  58                        }
  59                }
  60        }
  61
  62        if (count)
  63                return count;
  64
  65        return ret;
  66}
  67
  68static int sdio_irq_thread(void *_host)
  69{
  70        struct mmc_host *host = _host;
  71        struct sched_param param = { .sched_priority = 1 };
  72        unsigned long period, idle_period;
  73        int ret;
  74
  75        sched_setscheduler(current, SCHED_FIFO, &param);
  76
  77        /*
  78         * We want to allow for SDIO cards to work even on non SDIO
  79         * aware hosts.  One thing that non SDIO host cannot do is
  80         * asynchronous notification of pending SDIO card interrupts
  81         * hence we poll for them in that case.
  82         */
  83        idle_period = msecs_to_jiffies(10);
  84        period = (host->caps & MMC_CAP_SDIO_IRQ) ?
  85                MAX_SCHEDULE_TIMEOUT : idle_period;
  86
  87        pr_debug("%s: IRQ thread started (poll period = %lu jiffies)\n",
  88                 mmc_hostname(host), period);
  89
  90        do {
  91                /*
  92                 * We claim the host here on drivers behalf for a couple
  93                 * reasons:
  94                 *
  95                 * 1) it is already needed to retrieve the CCCR_INTx;
  96                 * 2) we want the driver(s) to clear the IRQ condition ASAP;
  97                 * 3) we need to control the abort condition locally.
  98                 *
  99                 * Just like traditional hard IRQ handlers, we expect SDIO
 100                 * IRQ handlers to be quick and to the point, so that the
 101                 * holding of the host lock does not cover too much work
 102                 * that doesn't require that lock to be held.
 103                 */
 104                ret = __mmc_claim_host(host, &host->sdio_irq_thread_abort);
 105                if (ret)
 106                        break;
 107                ret = process_sdio_pending_irqs(host->card);
 108                mmc_release_host(host);
 109
 110                /*
 111                 * Give other threads a chance to run in the presence of
 112                 * errors.
 113                 */
 114                if (ret < 0) {
 115                        set_current_state(TASK_INTERRUPTIBLE);
 116                        if (!kthread_should_stop())
 117                                schedule_timeout(HZ);
 118                        set_current_state(TASK_RUNNING);
 119                }
 120
 121                /*
 122                 * Adaptive polling frequency based on the assumption
 123                 * that an interrupt will be closely followed by more.
 124                 * This has a substantial benefit for network devices.
 125                 */
 126                if (!(host->caps & MMC_CAP_SDIO_IRQ)) {
 127                        if (ret > 0)
 128                                period /= 2;
 129                        else {
 130                                period++;
 131                                if (period > idle_period)
 132                                        period = idle_period;
 133                        }
 134                }
 135
 136                set_current_state(TASK_INTERRUPTIBLE);
 137                if (host->caps & MMC_CAP_SDIO_IRQ)
 138                        host->ops->enable_sdio_irq(host, 1);
 139                if (!kthread_should_stop())
 140                        schedule_timeout(period);
 141                set_current_state(TASK_RUNNING);
 142        } while (!kthread_should_stop());
 143
 144        if (host->caps & MMC_CAP_SDIO_IRQ)
 145                host->ops->enable_sdio_irq(host, 0);
 146
 147        pr_debug("%s: IRQ thread exiting with code %d\n",
 148                 mmc_hostname(host), ret);
 149
 150        return ret;
 151}
 152
 153static int sdio_card_irq_get(struct mmc_card *card)
 154{
 155        struct mmc_host *host = card->host;
 156
 157        WARN_ON(!host->claimed);
 158
 159        if (!host->sdio_irqs++) {
 160                atomic_set(&host->sdio_irq_thread_abort, 0);
 161                host->sdio_irq_thread =
 162                        kthread_run(sdio_irq_thread, host, "ksdioirqd/%s",
 163                                mmc_hostname(host));
 164                if (IS_ERR(host->sdio_irq_thread)) {
 165                        int err = PTR_ERR(host->sdio_irq_thread);
 166                        host->sdio_irqs--;
 167                        return err;
 168                }
 169        }
 170
 171        return 0;
 172}
 173
 174static int sdio_card_irq_put(struct mmc_card *card)
 175{
 176        struct mmc_host *host = card->host;
 177
 178        WARN_ON(!host->claimed);
 179        BUG_ON(host->sdio_irqs < 1);
 180
 181        if (!--host->sdio_irqs) {
 182                atomic_set(&host->sdio_irq_thread_abort, 1);
 183                kthread_stop(host->sdio_irq_thread);
 184        }
 185
 186        return 0;
 187}
 188
 189/**
 190 *      sdio_claim_irq - claim the IRQ for a SDIO function
 191 *      @func: SDIO function
 192 *      @handler: IRQ handler callback
 193 *
 194 *      Claim and activate the IRQ for the given SDIO function. The provided
 195 *      handler will be called when that IRQ is asserted.  The host is always
 196 *      claimed already when the handler is called so the handler must not
 197 *      call sdio_claim_host() nor sdio_release_host().
 198 */
 199int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
 200{
 201        int ret;
 202        unsigned char reg;
 203
 204        BUG_ON(!func);
 205        BUG_ON(!func->card);
 206
 207        pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));
 208
 209        if (func->irq_handler) {
 210                pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
 211                return -EBUSY;
 212        }
 213
 214        ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
 215        if (ret)
 216                return ret;
 217
 218        reg |= 1 << func->num;
 219
 220        reg |= 1; /* Master interrupt enable */
 221
 222        ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
 223        if (ret)
 224                return ret;
 225
 226        func->irq_handler = handler;
 227        ret = sdio_card_irq_get(func->card);
 228        if (ret)
 229                func->irq_handler = NULL;
 230
 231        return ret;
 232}
 233EXPORT_SYMBOL_GPL(sdio_claim_irq);
 234
 235/**
 236 *      sdio_release_irq - release the IRQ for a SDIO function
 237 *      @func: SDIO function
 238 *
 239 *      Disable and release the IRQ for the given SDIO function.
 240 */
 241int sdio_release_irq(struct sdio_func *func)
 242{
 243        int ret;
 244        unsigned char reg;
 245
 246        BUG_ON(!func);
 247        BUG_ON(!func->card);
 248
 249        pr_debug("SDIO: Disabling IRQ for %s...\n", sdio_func_id(func));
 250
 251        if (func->irq_handler) {
 252                func->irq_handler = NULL;
 253                sdio_card_irq_put(func->card);
 254        }
 255
 256        ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
 257        if (ret)
 258                return ret;
 259
 260        reg &= ~(1 << func->num);
 261
 262        /* Disable master interrupt with the last function interrupt */
 263        if (!(reg & 0xFE))
 264                reg = 0;
 265
 266        ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL);
 267        if (ret)
 268                return ret;
 269
 270        return 0;
 271}
 272EXPORT_SYMBOL_GPL(sdio_release_irq);
 273
 274