summaryrefslogtreecommitdiff
path: root/arch/arm/mach-ux500/modem_irq.c
blob: 3187f88711691b546cf7fc65877e8bac13770592 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * Copyright (C) ST-Ericsson SA 2010
 * Author: Stefan Nilsson <stefan.xk.nilsson@stericsson.com> for ST-Ericsson.
 * Author: Martin Persson <martin.persson@stericsson.com> for ST-Ericsson.
 * License terms: GNU General Public License (GPL), version 2.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/slab.h>

#define MODEM_INTCON_BASE_ADDR 0xBFFD3000
#define MODEM_INTCON_SIZE 0xFFF

#define DEST_IRQ41_OFFSET 0x2A4
#define DEST_IRQ43_OFFSET 0x2AC
#define DEST_IRQ45_OFFSET 0x2B4

#define PRIO_IRQ41_OFFSET 0x6A4
#define PRIO_IRQ43_OFFSET 0x6AC
#define PRIO_IRQ45_OFFSET 0x6B4

#define ALLOW_IRQ_OFFSET 0x104

#define MODEM_INTCON_CPU_NBR 0x1
#define MODEM_INTCON_PRIO_HIGH 0x0

#define MODEM_INTCON_ALLOW_IRQ41 0x0200
#define MODEM_INTCON_ALLOW_IRQ43 0x0800
#define MODEM_INTCON_ALLOW_IRQ45 0x2000

#define MODEM_IRQ_REG_OFFSET 0x4

struct modem_irq {
	void __iomem *modem_intcon_base;
};


static void setup_modem_intcon(void __iomem *modem_intcon_base)
{
	/* IC_DESTINATION_BASE_ARRAY - Which CPU to receive the IRQ */
	writel(MODEM_INTCON_CPU_NBR, modem_intcon_base + DEST_IRQ41_OFFSET);
	writel(MODEM_INTCON_CPU_NBR, modem_intcon_base + DEST_IRQ43_OFFSET);
	writel(MODEM_INTCON_CPU_NBR, modem_intcon_base + DEST_IRQ45_OFFSET);

	/* IC_PRIORITY_BASE_ARRAY - IRQ priority in modem IRQ controller */
	writel(MODEM_INTCON_PRIO_HIGH, modem_intcon_base + PRIO_IRQ41_OFFSET);
	writel(MODEM_INTCON_PRIO_HIGH, modem_intcon_base + PRIO_IRQ43_OFFSET);
	writel(MODEM_INTCON_PRIO_HIGH, modem_intcon_base + PRIO_IRQ45_OFFSET);

	/* IC_ALLOW_ARRAY - IRQ enable */
	writel(MODEM_INTCON_ALLOW_IRQ41 |
		   MODEM_INTCON_ALLOW_IRQ43 |
		   MODEM_INTCON_ALLOW_IRQ45,
		   modem_intcon_base + ALLOW_IRQ_OFFSET);
}

static irqreturn_t modem_cpu_irq_handler(int irq, void *data)
{
	int real_irq;
	int virt_irq;
	struct modem_irq *mi = (struct modem_irq *)data;

	/* Read modem side IRQ number from modem IRQ controller */
	real_irq = readl(mi->modem_intcon_base + MODEM_IRQ_REG_OFFSET) & 0xFF;
	virt_irq = IRQ_MODEM_EVENTS_BASE + real_irq;

	pr_debug("modem_irq: Worker read addr 0x%X and got value 0x%X "
		 "which will be 0x%X (%d) which translates to "
		 "virtual IRQ 0x%X (%d)!\n",
		   (u32)mi->modem_intcon_base + MODEM_IRQ_REG_OFFSET,
		   real_irq,
		   real_irq & 0xFF,
		   real_irq & 0xFF,
		   virt_irq,
		   virt_irq);

	if (virt_irq != 0)
		generic_handle_irq(virt_irq);

	pr_debug("modem_irq: Done handling virtual IRQ %d!\n", virt_irq);

	return IRQ_HANDLED;
}

static void create_virtual_irq(int irq, struct irq_chip *modem_irq_chip)
{
	set_irq_chip(irq, modem_irq_chip);
	set_irq_handler(irq, handle_simple_irq);
	set_irq_flags(irq, IRQF_VALID);

	pr_debug("modem_irq: Created virtual IRQ %d\n", irq);
}

static int modem_irq_init(void)
{
	int err;
	static struct irq_chip  modem_irq_chip;
	struct modem_irq *mi;

	pr_info("modem_irq: Set up IRQ handler for incoming modem IRQ %d\n",
		   IRQ_DB5500_MODEM);

	mi = kmalloc(sizeof(struct modem_irq), GFP_KERNEL);
	if (!mi) {
		pr_err("modem_irq: Could not allocate device\n");
		return -ENOMEM;
	}

	mi->modem_intcon_base =
		ioremap(MODEM_INTCON_BASE_ADDR, MODEM_INTCON_SIZE);
	pr_debug("modem_irq: ioremapped modem_intcon_base from "
		 "phy 0x%x to virt 0x%x\n", MODEM_INTCON_BASE_ADDR,
		 (u32)mi->modem_intcon_base);

	setup_modem_intcon(mi->modem_intcon_base);

	modem_irq_chip = dummy_irq_chip;
	modem_irq_chip.name = "modem_irq";

	/* Create the virtual IRQ:s needed */
	create_virtual_irq(MBOX_PAIR0_VIRT_IRQ, &modem_irq_chip);
	create_virtual_irq(MBOX_PAIR1_VIRT_IRQ, &modem_irq_chip);
	create_virtual_irq(MBOX_PAIR2_VIRT_IRQ, &modem_irq_chip);

	err = request_threaded_irq(IRQ_DB5500_MODEM, NULL,
				   modem_cpu_irq_handler, IRQF_ONESHOT,
				   "modem_irq", mi);
	if (err)
		pr_err("modem_irq: Could not register IRQ %d\n",
		       IRQ_DB5500_MODEM);

	return 0;
}

arch_initcall(modem_irq_init);