]>
Commit | Line | Data |
---|---|---|
30058677 RH |
1 | /* |
2 | * Copyright 2012 Calxeda, Inc. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms and conditions of the GNU General Public License, | |
6 | * version 2, as published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope it will be useful, but WITHOUT | |
9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
11 | * more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License along with | |
14 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
15 | */ | |
16 | #include <linux/types.h> | |
17 | #include <linux/err.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/export.h> | |
20 | #include <linux/io.h> | |
21 | #include <linux/interrupt.h> | |
22 | #include <linux/completion.h> | |
23 | #include <linux/mutex.h> | |
24 | #include <linux/notifier.h> | |
25 | #include <linux/spinlock.h> | |
26 | #include <linux/device.h> | |
27 | #include <linux/amba/bus.h> | |
28 | ||
29 | #include <linux/mailbox.h> | |
30 | ||
31 | #define IPCMxSOURCE(m) ((m) * 0x40) | |
32 | #define IPCMxDSET(m) (((m) * 0x40) + 0x004) | |
33 | #define IPCMxDCLEAR(m) (((m) * 0x40) + 0x008) | |
34 | #define IPCMxDSTATUS(m) (((m) * 0x40) + 0x00C) | |
35 | #define IPCMxMODE(m) (((m) * 0x40) + 0x010) | |
36 | #define IPCMxMSET(m) (((m) * 0x40) + 0x014) | |
37 | #define IPCMxMCLEAR(m) (((m) * 0x40) + 0x018) | |
38 | #define IPCMxMSTATUS(m) (((m) * 0x40) + 0x01C) | |
39 | #define IPCMxSEND(m) (((m) * 0x40) + 0x020) | |
40 | #define IPCMxDR(m, dr) (((m) * 0x40) + ((dr) * 4) + 0x024) | |
41 | ||
42 | #define IPCMMIS(irq) (((irq) * 8) + 0x800) | |
43 | #define IPCMRIS(irq) (((irq) * 8) + 0x804) | |
44 | ||
45 | #define MBOX_MASK(n) (1 << (n)) | |
46 | #define IPC_TX_MBOX 1 | |
47 | #define IPC_RX_MBOX 2 | |
48 | ||
49 | #define CHAN_MASK(n) (1 << (n)) | |
50 | #define A9_SOURCE 1 | |
51 | #define M3_SOURCE 0 | |
52 | ||
53 | static void __iomem *ipc_base; | |
54 | static int ipc_irq; | |
55 | static DEFINE_MUTEX(ipc_m1_lock); | |
56 | static DECLARE_COMPLETION(ipc_completion); | |
57 | static ATOMIC_NOTIFIER_HEAD(ipc_notifier); | |
58 | ||
59 | static inline void set_destination(int source, int mbox) | |
60 | { | |
61 | __raw_writel(CHAN_MASK(source), ipc_base + IPCMxDSET(mbox)); | |
62 | __raw_writel(CHAN_MASK(source), ipc_base + IPCMxMSET(mbox)); | |
63 | } | |
64 | ||
65 | static inline void clear_destination(int source, int mbox) | |
66 | { | |
67 | __raw_writel(CHAN_MASK(source), ipc_base + IPCMxDCLEAR(mbox)); | |
68 | __raw_writel(CHAN_MASK(source), ipc_base + IPCMxMCLEAR(mbox)); | |
69 | } | |
70 | ||
71 | static void __ipc_send(int mbox, u32 *data) | |
72 | { | |
73 | int i; | |
74 | for (i = 0; i < 7; i++) | |
75 | __raw_writel(data[i], ipc_base + IPCMxDR(mbox, i)); | |
76 | __raw_writel(0x1, ipc_base + IPCMxSEND(mbox)); | |
77 | } | |
78 | ||
79 | static u32 __ipc_rcv(int mbox, u32 *data) | |
80 | { | |
81 | int i; | |
82 | for (i = 0; i < 7; i++) | |
83 | data[i] = __raw_readl(ipc_base + IPCMxDR(mbox, i)); | |
84 | return data[1]; | |
85 | } | |
86 | ||
87 | /* blocking implmentation from the A9 side, not usuable in interrupts! */ | |
88 | int pl320_ipc_transmit(u32 *data) | |
89 | { | |
90 | int ret; | |
91 | ||
92 | mutex_lock(&ipc_m1_lock); | |
93 | ||
94 | init_completion(&ipc_completion); | |
95 | __ipc_send(IPC_TX_MBOX, data); | |
96 | ret = wait_for_completion_timeout(&ipc_completion, | |
97 | msecs_to_jiffies(1000)); | |
98 | if (ret == 0) { | |
99 | ret = -ETIMEDOUT; | |
100 | goto out; | |
101 | } | |
102 | ||
103 | ret = __ipc_rcv(IPC_TX_MBOX, data); | |
104 | out: | |
105 | mutex_unlock(&ipc_m1_lock); | |
106 | return ret; | |
107 | } | |
108 | EXPORT_SYMBOL_GPL(pl320_ipc_transmit); | |
109 | ||
110 | static irqreturn_t ipc_handler(int irq, void *dev) | |
111 | { | |
112 | u32 irq_stat; | |
113 | u32 data[7]; | |
114 | ||
115 | irq_stat = __raw_readl(ipc_base + IPCMMIS(1)); | |
116 | if (irq_stat & MBOX_MASK(IPC_TX_MBOX)) { | |
117 | __raw_writel(0, ipc_base + IPCMxSEND(IPC_TX_MBOX)); | |
118 | complete(&ipc_completion); | |
119 | } | |
120 | if (irq_stat & MBOX_MASK(IPC_RX_MBOX)) { | |
121 | __ipc_rcv(IPC_RX_MBOX, data); | |
122 | atomic_notifier_call_chain(&ipc_notifier, data[0], data + 1); | |
123 | __raw_writel(2, ipc_base + IPCMxSEND(IPC_RX_MBOX)); | |
124 | } | |
125 | ||
126 | return IRQ_HANDLED; | |
127 | } | |
128 | ||
129 | int pl320_ipc_register_notifier(struct notifier_block *nb) | |
130 | { | |
131 | return atomic_notifier_chain_register(&ipc_notifier, nb); | |
132 | } | |
133 | EXPORT_SYMBOL_GPL(pl320_ipc_register_notifier); | |
134 | ||
135 | int pl320_ipc_unregister_notifier(struct notifier_block *nb) | |
136 | { | |
137 | return atomic_notifier_chain_unregister(&ipc_notifier, nb); | |
138 | } | |
139 | EXPORT_SYMBOL_GPL(pl320_ipc_unregister_notifier); | |
140 | ||
141 | static int __init pl320_probe(struct amba_device *adev, | |
142 | const struct amba_id *id) | |
143 | { | |
144 | int ret; | |
145 | ||
146 | ipc_base = ioremap(adev->res.start, resource_size(&adev->res)); | |
147 | if (ipc_base == NULL) | |
148 | return -ENOMEM; | |
149 | ||
150 | __raw_writel(0, ipc_base + IPCMxSEND(IPC_TX_MBOX)); | |
151 | ||
152 | ipc_irq = adev->irq[0]; | |
153 | ret = request_irq(ipc_irq, ipc_handler, 0, dev_name(&adev->dev), NULL); | |
154 | if (ret < 0) | |
155 | goto err; | |
156 | ||
157 | /* Init slow mailbox */ | |
158 | __raw_writel(CHAN_MASK(A9_SOURCE), | |
159 | ipc_base + IPCMxSOURCE(IPC_TX_MBOX)); | |
160 | __raw_writel(CHAN_MASK(M3_SOURCE), | |
161 | ipc_base + IPCMxDSET(IPC_TX_MBOX)); | |
162 | __raw_writel(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE), | |
163 | ipc_base + IPCMxMSET(IPC_TX_MBOX)); | |
164 | ||
165 | /* Init receive mailbox */ | |
166 | __raw_writel(CHAN_MASK(M3_SOURCE), | |
167 | ipc_base + IPCMxSOURCE(IPC_RX_MBOX)); | |
168 | __raw_writel(CHAN_MASK(A9_SOURCE), | |
169 | ipc_base + IPCMxDSET(IPC_RX_MBOX)); | |
170 | __raw_writel(CHAN_MASK(M3_SOURCE) | CHAN_MASK(A9_SOURCE), | |
171 | ipc_base + IPCMxMSET(IPC_RX_MBOX)); | |
172 | ||
173 | return 0; | |
174 | err: | |
175 | iounmap(ipc_base); | |
176 | return ret; | |
177 | } | |
178 | ||
179 | static struct amba_id pl320_ids[] = { | |
180 | { | |
181 | .id = 0x00041320, | |
182 | .mask = 0x000fffff, | |
183 | }, | |
184 | { 0, 0 }, | |
185 | }; | |
186 | ||
187 | static struct amba_driver pl320_driver = { | |
188 | .drv = { | |
189 | .name = "pl320", | |
190 | }, | |
191 | .id_table = pl320_ids, | |
192 | .probe = pl320_probe, | |
193 | }; | |
194 | ||
195 | static int __init ipc_init(void) | |
196 | { | |
197 | return amba_driver_register(&pl320_driver); | |
198 | } | |
199 | module_init(ipc_init); |