exynos-combiner.c 7.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 *
 * Combiner irqchip for EXYNOS
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include <linux/err.h>
#include <linux/export.h>
#include <linux/init.h>
#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 22 23 24 25 26 27
#include <linux/of_address.h>
#include <linux/of_irq.h>

#define COMBINER_ENABLE_SET	0x0
#define COMBINER_ENABLE_CLEAR	0x4
#define COMBINER_INT_STATUS	0xC

28 29
#define IRQ_IN_COMBINER		8

30 31 32
static DEFINE_SPINLOCK(irq_controller_lock);

struct combiner_chip_data {
33
	unsigned int hwirq_offset;
34 35
	unsigned int irq_mask;
	void __iomem *base;
36
	unsigned int parent_irq;
37 38 39
#ifdef CONFIG_PM
	u32 pm_save;
#endif
40 41
};

42
static struct combiner_chip_data *combiner_data;
43
static struct irq_domain *combiner_irq_domain;
44
static unsigned int max_nr = 20;
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

static inline void __iomem *combiner_base(struct irq_data *data)
{
	struct combiner_chip_data *combiner_data =
		irq_data_get_irq_chip_data(data);

	return combiner_data->base;
}

static void combiner_mask_irq(struct irq_data *data)
{
	u32 mask = 1 << (data->hwirq % 32);

	__raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_CLEAR);
}

static void combiner_unmask_irq(struct irq_data *data)
{
	u32 mask = 1 << (data->hwirq % 32);

	__raw_writel(mask, combiner_base(data) + COMBINER_ENABLE_SET);
}

68 69
static void combiner_handle_cascade_irq(unsigned int __irq,
					struct irq_desc *desc)
70
{
71 72
	struct combiner_chip_data *chip_data = irq_desc_get_handler_data(desc);
	struct irq_chip *chip = irq_desc_get_chip(desc);
73
	unsigned int irq = irq_desc_get_irq(desc);
74 75 76 77 78 79 80 81 82 83 84 85 86
	unsigned int cascade_irq, combiner_irq;
	unsigned long status;

	chained_irq_enter(chip, desc);

	spin_lock(&irq_controller_lock);
	status = __raw_readl(chip_data->base + COMBINER_INT_STATUS);
	spin_unlock(&irq_controller_lock);
	status &= chip_data->irq_mask;

	if (status == 0)
		goto out;

87 88
	combiner_irq = chip_data->hwirq_offset + __ffs(status);
	cascade_irq = irq_find_mapping(combiner_irq_domain, combiner_irq);
89

90
	if (unlikely(!cascade_irq))
91
		handle_bad_irq(irq, desc);
92 93 94 95 96 97 98
	else
		generic_handle_irq(cascade_irq);

 out:
	chained_irq_exit(chip, desc);
}

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
#ifdef CONFIG_SMP
static int combiner_set_affinity(struct irq_data *d,
				 const struct cpumask *mask_val, bool force)
{
	struct combiner_chip_data *chip_data = irq_data_get_irq_chip_data(d);
	struct irq_chip *chip = irq_get_chip(chip_data->parent_irq);
	struct irq_data *data = irq_get_irq_data(chip_data->parent_irq);

	if (chip && chip->irq_set_affinity)
		return chip->irq_set_affinity(data, mask_val, force);
	else
		return -EINVAL;
}
#endif

114
static struct irq_chip combiner_chip = {
115 116 117 118 119 120
	.name			= "COMBINER",
	.irq_mask		= combiner_mask_irq,
	.irq_unmask		= combiner_unmask_irq,
#ifdef CONFIG_SMP
	.irq_set_affinity	= combiner_set_affinity,
#endif
121 122
};

123
static void __init combiner_cascade_irq(struct combiner_chip_data *combiner_data,
124 125
					unsigned int irq)
{
126 127
	irq_set_chained_handler_and_data(irq, combiner_handle_cascade_irq,
					 combiner_data);
128 129
}

130 131
static void __init combiner_init_one(struct combiner_chip_data *combiner_data,
				     unsigned int combiner_nr,
132
				     void __iomem *base, unsigned int irq)
133
{
134
	combiner_data->base = base;
135
	combiner_data->hwirq_offset = (combiner_nr & ~3) * IRQ_IN_COMBINER;
136 137
	combiner_data->irq_mask = 0xff << ((combiner_nr % 4) << 3);
	combiner_data->parent_irq = irq;
138 139

	/* Disable all interrupts */
140
	__raw_writel(combiner_data->irq_mask, base + COMBINER_ENABLE_CLEAR);
141 142 143 144 145 146 147 148 149 150 151 152 153 154
}

static int combiner_irq_domain_xlate(struct irq_domain *d,
				     struct device_node *controller,
				     const u32 *intspec, unsigned int intsize,
				     unsigned long *out_hwirq,
				     unsigned int *out_type)
{
	if (d->of_node != controller)
		return -EINVAL;

	if (intsize < 2)
		return -EINVAL;

155
	*out_hwirq = intspec[0] * IRQ_IN_COMBINER + intspec[1];
156 157 158 159 160 161 162 163
	*out_type = 0;

	return 0;
}

static int combiner_irq_domain_map(struct irq_domain *d, unsigned int irq,
				   irq_hw_number_t hw)
{
164 165
	struct combiner_chip_data *combiner_data = d->host_data;

166 167 168 169 170 171 172
	irq_set_chip_and_handler(irq, &combiner_chip, handle_level_irq);
	irq_set_chip_data(irq, &combiner_data[hw >> 3]);
	set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);

	return 0;
}

173
static const struct irq_domain_ops combiner_irq_domain_ops = {
174 175 176 177
	.xlate	= combiner_irq_domain_xlate,
	.map	= combiner_irq_domain_map,
};

178
static void __init combiner_init(void __iomem *combiner_base,
179
				 struct device_node *np)
180
{
181
	int i, irq;
182
	unsigned int nr_irq;
183

184
	nr_irq = max_nr * IRQ_IN_COMBINER;
185

186 187
	combiner_data = kcalloc(max_nr, sizeof (*combiner_data), GFP_KERNEL);
	if (!combiner_data) {
188
		pr_warn("%s: could not allocate combiner data\n", __func__);
189
		return;
190 191
	}

192
	combiner_irq_domain = irq_domain_add_linear(np, nr_irq,
193
				&combiner_irq_domain_ops, combiner_data);
194
	if (WARN_ON(!combiner_irq_domain)) {
195
		pr_warn("%s: irq domain init failed\n", __func__);
196 197 198 199
		return;
	}

	for (i = 0; i < max_nr; i++) {
200
		irq = irq_of_parse_and_map(np, i);
201

202 203 204
		combiner_init_one(&combiner_data[i], i,
				  combiner_base + (i >> 2) * 0x10, irq);
		combiner_cascade_irq(&combiner_data[i], irq);
205 206 207
	}
}

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
#ifdef CONFIG_PM

/**
 * combiner_suspend - save interrupt combiner state before suspend
 *
 * Save the interrupt enable set register for all combiner groups since
 * the state is lost when the system enters into a sleep state.
 *
 */
static int combiner_suspend(void)
{
	int i;

	for (i = 0; i < max_nr; i++)
		combiner_data[i].pm_save =
			__raw_readl(combiner_data[i].base + COMBINER_ENABLE_SET);

	return 0;
}

/**
 * combiner_resume - restore interrupt combiner state after resume
 *
 * Restore the interrupt enable set register for all combiner groups since
 * the state is lost when the system enters into a sleep state on suspend.
 *
 */
static void combiner_resume(void)
{
	int i;

	for (i = 0; i < max_nr; i++) {
		__raw_writel(combiner_data[i].irq_mask,
			     combiner_data[i].base + COMBINER_ENABLE_CLEAR);
		__raw_writel(combiner_data[i].pm_save,
			     combiner_data[i].base + COMBINER_ENABLE_SET);
	}
}

#else
#define combiner_suspend	NULL
#define combiner_resume		NULL
#endif

static struct syscore_ops combiner_syscore_ops = {
	.suspend	= combiner_suspend,
	.resume		= combiner_resume,
};

257 258 259 260 261 262 263 264 265 266 267
static int __init combiner_of_init(struct device_node *np,
				   struct device_node *parent)
{
	void __iomem *combiner_base;

	combiner_base = of_iomap(np, 0);
	if (!combiner_base) {
		pr_err("%s: failed to map combiner registers\n", __func__);
		return -ENXIO;
	}

268 269 270 271 272 273
	if (of_property_read_u32(np, "samsung,combiner-nr", &max_nr)) {
		pr_info("%s: number of combiners not specified, "
			"setting default as %d.\n",
			__func__, max_nr);
	}

274 275 276
	combiner_init(combiner_base, np);

	register_syscore_ops(&combiner_syscore_ops);
277 278 279 280 281

	return 0;
}
IRQCHIP_DECLARE(exynos4210_combiner, "samsung,exynos4210-combiner",
		combiner_of_init);