linux/arch/arm/plat-samsung/s5p-irq-gpioint.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2010 Samsung Electronics Co., Ltd.
   3 * Author: Kyungmin Park <kyungmin.park@samsung.com>
   4 * Author: Joonyoung Shim <jy0922.shim@samsung.com>
   5 * Author: Marek Szyprowski <m.szyprowski@samsung.com>
   6 *
   7 *  This program is free software; you can redistribute  it and/or modify it
   8 *  under  the terms of  the GNU General  Public License as published by the
   9 *  Free Software Foundation;  either version 2 of the  License, or (at your
  10 *  option) any later version.
  11 *
  12 */
  13
  14#include <linux/kernel.h>
  15#include <linux/interrupt.h>
  16#include <linux/irq.h>
  17#include <linux/irqchip/chained_irq.h>
  18#include <linux/io.h>
  19#include <linux/gpio.h>
  20#include <linux/slab.h>
  21
  22#include <mach/map.h>
  23#include <plat/gpio-core.h>
  24#include <plat/gpio-cfg.h>
  25
  26#define GPIO_BASE(chip)         ((void __iomem *)((unsigned long)((chip)->base) & 0xFFFFF000u))
  27
  28#define CON_OFFSET              0x700
  29#define MASK_OFFSET             0x900
  30#define PEND_OFFSET             0xA00
  31#define REG_OFFSET(x)           ((x) << 2)
  32
  33struct s5p_gpioint_bank {
  34        struct list_head        list;
  35        int                     start;
  36        int                     nr_groups;
  37        int                     irq;
  38        struct samsung_gpio_chip        **chips;
  39        void                    (*handler)(unsigned int, struct irq_desc *);
  40};
  41
  42static LIST_HEAD(banks);
  43
  44static int s5p_gpioint_set_type(struct irq_data *d, unsigned int type)
  45{
  46        struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d);
  47        struct irq_chip_type *ct = gc->chip_types;
  48        unsigned int shift = (d->irq - gc->irq_base) << 2;
  49
  50        switch (type) {
  51        case IRQ_TYPE_EDGE_RISING:
  52                type = S5P_IRQ_TYPE_EDGE_RISING;
  53                break;
  54        case IRQ_TYPE_EDGE_FALLING:
  55                type = S5P_IRQ_TYPE_EDGE_FALLING;
  56                break;
  57        case IRQ_TYPE_EDGE_BOTH:
  58                type = S5P_IRQ_TYPE_EDGE_BOTH;
  59                break;
  60        case IRQ_TYPE_LEVEL_HIGH:
  61                type = S5P_IRQ_TYPE_LEVEL_HIGH;
  62                break;
  63        case IRQ_TYPE_LEVEL_LOW:
  64                type = S5P_IRQ_TYPE_LEVEL_LOW;
  65                break;
  66        case IRQ_TYPE_NONE:
  67        default:
  68                printk(KERN_WARNING "No irq type\n");
  69                return -EINVAL;
  70        }
  71
  72        gc->type_cache &= ~(0x7 << shift);
  73        gc->type_cache |= type << shift;
  74        writel(gc->type_cache, gc->reg_base + ct->regs.type);
  75        return 0;
  76}
  77
  78static void s5p_gpioint_handler(unsigned int irq, struct irq_desc *desc)
  79{
  80        struct s5p_gpioint_bank *bank = irq_get_handler_data(irq);
  81        int group, pend_offset, mask_offset;
  82        unsigned int pend, mask;
  83
  84        struct irq_chip *chip = irq_get_chip(irq);
  85        chained_irq_enter(chip, desc);
  86
  87        for (group = 0; group < bank->nr_groups; group++) {
  88                struct samsung_gpio_chip *chip = bank->chips[group];
  89                if (!chip)
  90                        continue;
  91
  92                pend_offset = REG_OFFSET(group);
  93                pend = __raw_readl(GPIO_BASE(chip) + PEND_OFFSET + pend_offset);
  94                if (!pend)
  95                        continue;
  96
  97                mask_offset = REG_OFFSET(group);
  98                mask = __raw_readl(GPIO_BASE(chip) + MASK_OFFSET + mask_offset);
  99                pend &= ~mask;
 100
 101                while (pend) {
 102                        int offset = fls(pend) - 1;
 103                        int real_irq = chip->irq_base + offset;
 104                        generic_handle_irq(real_irq);
 105                        pend &= ~BIT(offset);
 106                }
 107        }
 108        chained_irq_exit(chip, desc);
 109}
 110
 111static __init int s5p_gpioint_add(struct samsung_gpio_chip *chip)
 112{
 113        static int used_gpioint_groups = 0;
 114        int group = chip->group;
 115        struct s5p_gpioint_bank *b, *bank = NULL;
 116        struct irq_chip_generic *gc;
 117        struct irq_chip_type *ct;
 118
 119        if (used_gpioint_groups >= S5P_GPIOINT_GROUP_COUNT)
 120                return -ENOMEM;
 121
 122        list_for_each_entry(b, &banks, list) {
 123                if (group >= b->start && group < b->start + b->nr_groups) {
 124                        bank = b;
 125                        break;
 126                }
 127        }
 128        if (!bank)
 129                return -EINVAL;
 130
 131        if (!bank->handler) {
 132                bank->chips = kzalloc(sizeof(struct samsung_gpio_chip *) *
 133                                      bank->nr_groups, GFP_KERNEL);
 134                if (!bank->chips)
 135                        return -ENOMEM;
 136
 137                irq_set_chained_handler(bank->irq, s5p_gpioint_handler);
 138                irq_set_handler_data(bank->irq, bank);
 139                bank->handler = s5p_gpioint_handler;
 140                printk(KERN_INFO "Registered chained gpio int handler for interrupt %d.\n",
 141                       bank->irq);
 142        }
 143
 144        /*
 145         * chained GPIO irq has been successfully registered, allocate new gpio
 146         * int group and assign irq nubmers
 147         */
 148        chip->irq_base = S5P_GPIOINT_BASE +
 149                         used_gpioint_groups * S5P_GPIOINT_GROUP_SIZE;
 150        used_gpioint_groups++;
 151
 152        bank->chips[group - bank->start] = chip;
 153
 154        gc = irq_alloc_generic_chip("s5p_gpioint", 1, chip->irq_base,
 155                                    GPIO_BASE(chip),
 156                                    handle_level_irq);
 157        if (!gc)
 158                return -ENOMEM;
 159        ct = gc->chip_types;
 160        ct->chip.irq_ack = irq_gc_ack_set_bit;
 161        ct->chip.irq_mask = irq_gc_mask_set_bit;
 162        ct->chip.irq_unmask = irq_gc_mask_clr_bit;
 163        ct->chip.irq_set_type = s5p_gpioint_set_type,
 164        ct->regs.ack = PEND_OFFSET + REG_OFFSET(group - bank->start);
 165        ct->regs.mask = MASK_OFFSET + REG_OFFSET(group - bank->start);
 166        ct->regs.type = CON_OFFSET + REG_OFFSET(group - bank->start);
 167        irq_setup_generic_chip(gc, IRQ_MSK(chip->chip.ngpio),
 168                               IRQ_GC_INIT_MASK_CACHE,
 169                               IRQ_NOREQUEST | IRQ_NOPROBE, 0);
 170        return 0;
 171}
 172
 173int __init s5p_register_gpio_interrupt(int pin)
 174{
 175        struct samsung_gpio_chip *my_chip = samsung_gpiolib_getchip(pin);
 176        int offset, group;
 177        int ret;
 178
 179        if (!my_chip)
 180                return -EINVAL;
 181
 182        offset = pin - my_chip->chip.base;
 183        group = my_chip->group;
 184
 185        /* check if the group has been already registered */
 186        if (my_chip->irq_base)
 187                goto success;
 188
 189        /* register gpio group */
 190        ret = s5p_gpioint_add(my_chip);
 191        if (ret == 0) {
 192                my_chip->chip.to_irq = samsung_gpiolib_to_irq;
 193                printk(KERN_INFO "Registered interrupt support for gpio group %d.\n",
 194                       group);
 195                goto success;
 196        }
 197        return ret;
 198success:
 199        my_chip->bitmap_gpio_int |= BIT(offset);
 200
 201        return my_chip->irq_base + offset;
 202}
 203
 204int __init s5p_register_gpioint_bank(int chain_irq, int start, int nr_groups)
 205{
 206        struct s5p_gpioint_bank *bank;
 207
 208        bank = kzalloc(sizeof(*bank), GFP_KERNEL);
 209        if (!bank)
 210                return -ENOMEM;
 211
 212        bank->start = start;
 213        bank->nr_groups = nr_groups;
 214        bank->irq = chain_irq;
 215
 216        list_add_tail(&bank->list, &banks);
 217        return 0;
 218}
 219