linux/drivers/irqchip/exynos-combiner.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
   3 *              http://www.samsung.com
   4 *
   5 * Combiner irqchip for EXYNOS
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10 */
  11#include <linux/err.h>
  12#include <linux/export.h>
  13#include <linux/init.h>
  14#include <linux/io.h>
  15#include <linux/slab.h>
  16#include <linux/syscore_ops.h>
  17#include <linux/irqdomain.h>
  18#include <linux/irqchip.h>
  19#include <linux/irqchip/chained_irq.h>
  20#include <linux/interrupt.h>
  21#include <linux/of_address.h>
  22#include <linux/of_irq.h>
  23
  24#define COMBINER_ENABLE_SET     0x0
  25#define COMBINER_ENABLE_CLEAR   0x4
  26#define COMBINER_INT_STATUS     0xC
  27
  28#define IRQ_IN_COMBINER         8
  29
  30static DEFINE_SPINLOCK(irq_controller_lock);
  31
  32struct combiner_chip_data {
  33        unsigned int hwirq_offset;
  34        unsigned int irq_mask;
  35        void __iomem *base;
  36        unsigned int parent_irq;
  37#ifdef CONFIG_PM
  38        u32 pm_save;
  39#endif
  40};
  41
  42static struct combiner_chip_data *combiner_data;
  43static struct irq_domain *combiner_irq_domain;
  44static unsigned int max_nr = 20;
  45
  46static inline void __iomem *combiner_base(struct irq_data *data)
  47{
  48        struct combiner_chip_data *combiner_data =
  49                irq_data_get_irq_chip_data(data);
  50
  51        return combiner_data->base;
  52}
  53
  54static void combiner_mask_irq(struct irq_data *data)
  55{
  56        u32 mask = 1 << (data->hwirq % 32);
  57
  58        __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR);
  59}
  60
  61static void combiner_unmask_irq(struct irq_data *data)
  62{
  63        u32 mask = 1 << (data->hwirq % 32);
  64
  65        __raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_SET);
  66}
  67
  68static void combiner_handle_cascade_irq(struct irq_desc *desc)
  69{
  70        struct combiner_chip_data *chip_data = irq_desc_get_handler_data(desc);
  71        struct irq_chip *chip = irq_desc_get_chip(desc);
  72        unsigned int cascade_irq, combiner_irq;
  73        unsigned long status;
  74
  75        chained_irq_enter(chip, desc);
  76
  77        spin_lock(&irq_controller_lock);
  78        status = __raw_readl(chip_data->base + COMBINER_INT_STATUS);
  79        spin_unlock(&irq_controller_lock);
  80        status &= chip_data->irq_mask;
  81
  82        if (status == 0)
  83                goto out;
  84
  85        combiner_irq = chip_data->hwirq_offset + __ffs(status);
  86        cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq);
  87
  88        if (unlikely(!cascade_irq))
  89                handle_bad_irq(desc);
  90        else
  91                generic_handle_irq(cascade_irq);
  92
  93 out:
  94        chained_irq_exit(chip, desc);
  95}
  96
  97#ifdef CONFIG_SMP
  98static int combiner_set_affinity(struct irq_data *d,
  99                                 const struct cpumask *mask_val, bool force)
 100{
 101        struct combiner_chip_data *chip_data = irq_data_get_irq_chip_data(d);
 102        struct irq_chip *chip = irq_get_chip(chip_data->parent_irq);
 103        struct irq_data *data = irq_get_irq_data(chip_data->parent_irq);
 104
 105        if (chip && chip->irq_set_affinity)
 106                return chip->irq_set_affinity(data, mask_val, force);
 107        else
 108                return -EINVAL;
 109}
 110#endif
 111
 112static struct irq_chip combiner_chip = {
 113        .name                   = "COMBINER",
 114        .irq_mask               = combiner_mask_irq,
 115        .irq_unmask             = combiner_unmask_irq,
 116#ifdef CONFIG_SMP
 117        .irq_set_affinity       = combiner_set_affinity,
 118#endif
 119};
 120
 121static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data,
 122                                        unsigned int irq)
 123{
 124        irq_set_chained_handler_and_data(irq, combiner_handle_cascade_irq,
 125                                         combiner_data);
 126}
 127
 128static void __init combiner_init_one(struct combiner_chip_data *combiner_data,
 129                                     unsigned int combiner_nr,
 130                                     void __iomem *base, unsigned int irq)
 131{
 132        combiner_data->base = base;
 133        combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER;
 134        combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3);
 135        combiner_data->parent_irq = irq;
 136
 137        /* Disable all interrupts */
 138        __raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR);
 139}
 140
 141static int combiner_irq_domain_xlate(struct irq_domain *d,
 142                                     struct device_node *controller,
 143                                     const u32 *intspec, unsigned int intsize,
 144                                     unsigned long *out_hwirq,
 145                                     unsigned int *out_type)
 146{
 147        if (irq_domain_get_of_node(d) != controller)
 148                return -EINVAL;
 149
 150        if (intsize < 2)
 151                return -EINVAL;
 152
 153        *out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1];
 154        *out_type = 0;
 155
 156        return 0;
 157}
 158
 159static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq,
 160                                   irq_hw_number_t hw)
 161{
 162        struct combiner_chip_data *combiner_data = d->host_data;
 163
 164        irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq);
 165        irq_set_chip_data(irq, &combiner_data[hw >> 3]);
 166        irq_set_probe(irq);
 167
 168        return 0;
 169}
 170
 171static const struct irq_domain_ops combiner_irq_domain_ops = {
 172        .xlate  = combiner_irq_domain_xlate,
 173        .map    = combiner_irq_domain_map,
 174};
 175
 176static void __init combiner_init(void __iomem *combiner_base,
 177                                 struct device_node *np)
 178{
 179        int i, irq;
 180        unsigned int nr_irq;
 181
 182        nr_irq = max_nr * IRQ_IN_COMBINER;
 183
 184        combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL);
 185        if (!combiner_data) {
 186                pr_warn("%s: could not allocate combiner data\n", __func__);
 187                return;
 188        }
 189
 190        combiner_irq_domain = irq_domain_add_linear(np, nr_irq,
 191                                &combiner_irq_domain_ops, combiner_data);
 192        if (WARN_ON(!combiner_irq_domain)) {
 193                pr_warn("%s: irq domain init failed\n", __func__);
 194                return;
 195        }
 196
 197        for (i = 0; i < max_nr; i++) {
 198                irq = irq_of_parse_and_map(np, i);
 199
 200                combiner_init_one(&combiner_data[i], i,
 201                                  combiner_base + (i >> 2) * 0x10, irq);
 202                combiner_cascade_irq(&combiner_data[i], irq);
 203        }
 204}
 205
 206#ifdef CONFIG_PM
 207
 208/**
 209 * combiner_suspend - save interrupt combiner state before suspend
 210 *
 211 * Save the interrupt enable set register for all combiner groups since
 212 * the state is lost when the system enters into a sleep state.
 213 *
 214 */
 215static int combiner_suspend(void)
 216{
 217        int i;
 218
 219        for (i = 0; i < max_nr; i++)
 220                combiner_data[i].pm_save =
 221                        __raw_readl(combiner_data[i].base + COMBINER_ENABLE_SET);
 222
 223        return 0;
 224}
 225
 226/**
 227 * combiner_resume - restore interrupt combiner state after resume
 228 *
 229 * Restore the interrupt enable set register for all combiner groups since
 230 * the state is lost when the system enters into a sleep state on suspend.
 231 *
 232 */
 233static void combiner_resume(void)
 234{
 235        int i;
 236
 237        for (i = 0; i < max_nr; i++) {
 238                __raw_writel(combiner_data[i].irq_mask,
 239                             combiner_data[i].base + COMBINER_ENABLE_CLEAR);
 240                __raw_writel(combiner_data[i].pm_save,
 241                             combiner_data[i].base + COMBINER_ENABLE_SET);
 242        }
 243}
 244
 245#else
 246#define combiner_suspend        NULL
 247#define combiner_resume         NULL
 248#endif
 249
 250static struct syscore_ops combiner_syscore_ops = {
 251        .suspend        = combiner_suspend,
 252        .resume         = combiner_resume,
 253};
 254
 255static int __init combiner_of_init(struct device_node *np,
 256                                   struct device_node *parent)
 257{
 258        void __iomem *combiner_base;
 259
 260        combiner_base = of_iomap(np, 0);
 261        if (!combiner_base) {
 262                pr_err("%s: failed to map combiner registers\n", __func__);
 263                return -ENXIO;
 264        }
 265
 266        if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) {
 267                pr_info("%s: number of combiners not specified, "
 268                        "setting default as %d.\n",
 269                        __func__, max_nr);
 270        }
 271
 272        combiner_init(combiner_base, np);
 273
 274        register_syscore_ops(&combiner_syscore_ops);
 275
 276        return 0;
 277}
 278IRQCHIP_DECLARE(exynos4210_combiner, "samsung,exynos4210-combiner",
 279                combiner_of_init);
 280