diff options
-rw-r--r-- | Documentation/devicetree/bindings/serio/olpc,ap-sp.txt | 13 | ||||
-rw-r--r-- | drivers/input/serio/Kconfig | 10 | ||||
-rw-r--r-- | drivers/input/serio/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/serio/olpc_apsp.c | 287 |
4 files changed, 311 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt new file mode 100644 index 000000000000..0e72183f52bc --- /dev/null +++ b/Documentation/devicetree/bindings/serio/olpc,ap-sp.txt | |||
@@ -0,0 +1,13 @@ | |||
1 | OLPC AP-SP serio interface | ||
2 | |||
3 | Required properties: | ||
4 | - compatible : "olpc,ap-sp" | ||
5 | - reg : base address and length of SoC's WTM registers | ||
6 | - interrupts : SP-AP interrupt | ||
7 | |||
8 | Example: | ||
9 | ap-sp@d4290000 { | ||
10 | compatible = "olpc,ap-sp"; | ||
11 | reg = <0xd4290000 0x1000>; | ||
12 | interrupts = <40>; | ||
13 | } | ||
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index aebfe3ecb945..d401a7dccaa7 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig | |||
@@ -255,4 +255,14 @@ config SERIO_APBPS2 | |||
255 | To compile this driver as a module, choose M here: the module will | 255 | To compile this driver as a module, choose M here: the module will |
256 | be called apbps2. | 256 | be called apbps2. |
257 | 257 | ||
258 | config SERIO_OLPC_APSP | ||
259 | tristate "OLPC AP-SP input support" | ||
260 | depends on OF | ||
261 | help | ||
262 | Say Y here if you want support for the keyboard and touchpad included | ||
263 | in the OLPC XO-1.75 and XO-4 laptops. | ||
264 | |||
265 | To compile this driver as a module, choose M here: the module will | ||
266 | be called olpc_apsp. | ||
267 | |||
258 | endif | 268 | endif |
diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 8edb36c2cdb4..12298b1c0e71 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile | |||
@@ -27,3 +27,4 @@ obj-$(CONFIG_SERIO_XILINX_XPS_PS2) += xilinx_ps2.o | |||
27 | obj-$(CONFIG_SERIO_ALTERA_PS2) += altera_ps2.o | 27 | obj-$(CONFIG_SERIO_ALTERA_PS2) += altera_ps2.o |
28 | obj-$(CONFIG_SERIO_ARC_PS2) += arc_ps2.o | 28 | obj-$(CONFIG_SERIO_ARC_PS2) += arc_ps2.o |
29 | obj-$(CONFIG_SERIO_APBPS2) += apbps2.o | 29 | obj-$(CONFIG_SERIO_APBPS2) += apbps2.o |
30 | obj-$(CONFIG_SERIO_OLPC_APSP) += olpc_apsp.o | ||
diff --git a/drivers/input/serio/olpc_apsp.c b/drivers/input/serio/olpc_apsp.c new file mode 100644 index 000000000000..818aa466b5d2 --- /dev/null +++ b/drivers/input/serio/olpc_apsp.c | |||
@@ -0,0 +1,287 @@ | |||
1 | /* | ||
2 | * OLPC serio driver for multiplexed input from Marvell MMP security processor | ||
3 | * | ||
4 | * Copyright (C) 2011-2013 One Laptop Per Child | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | */ | ||
16 | |||
17 | #include <linux/module.h> | ||
18 | #include <linux/interrupt.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/serio.h> | ||
21 | #include <linux/err.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/io.h> | ||
24 | #include <linux/of.h> | ||
25 | #include <linux/slab.h> | ||
26 | #include <linux/delay.h> | ||
27 | |||
28 | /* | ||
29 | * The OLPC XO-1.75 and XO-4 laptops do not have a hardware PS/2 controller. | ||
30 | * Instead, the OLPC firmware runs a bit-banging PS/2 implementation on an | ||
31 | * otherwise-unused slow processor which is included in the Marvell MMP2/MMP3 | ||
32 | * SoC, known as the "Security Processor" (SP) or "Wireless Trusted Module" | ||
33 | * (WTM). This firmware then reports its results via the WTM registers, | ||
34 | * which we read from the Application Processor (AP, i.e. main CPU) in this | ||
35 | * driver. | ||
36 | * | ||
37 | * On the hardware side we have a PS/2 mouse and an AT keyboard, the data | ||
38 | * is multiplexed through this system. We create a serio port for each one, | ||
39 | * and demultiplex the data accordingly. | ||
40 | */ | ||
41 | |||
42 | /* WTM register offsets */ | ||
43 | #define SECURE_PROCESSOR_COMMAND 0x40 | ||
44 | #define COMMAND_RETURN_STATUS 0x80 | ||
45 | #define COMMAND_FIFO_STATUS 0xc4 | ||
46 | #define PJ_RST_INTERRUPT 0xc8 | ||
47 | #define PJ_INTERRUPT_MASK 0xcc | ||
48 | |||
49 | /* | ||
50 | * The upper byte of SECURE_PROCESSOR_COMMAND and COMMAND_RETURN_STATUS is | ||
51 | * used to identify which port (device) is being talked to. The lower byte | ||
52 | * is the data being sent/received. | ||
53 | */ | ||
54 | #define PORT_MASK 0xff00 | ||
55 | #define DATA_MASK 0x00ff | ||
56 | #define PORT_SHIFT 8 | ||
57 | #define KEYBOARD_PORT 0 | ||
58 | #define TOUCHPAD_PORT 1 | ||
59 | |||
60 | /* COMMAND_FIFO_STATUS */ | ||
61 | #define CMD_CNTR_MASK 0x7 /* Number of pending/unprocessed commands */ | ||
62 | #define MAX_PENDING_CMDS 4 /* from device specs */ | ||
63 | |||
64 | /* PJ_RST_INTERRUPT */ | ||
65 | #define SP_COMMAND_COMPLETE_RESET 0x1 | ||
66 | |||
67 | /* PJ_INTERRUPT_MASK */ | ||
68 | #define INT_0 (1 << 0) | ||
69 | |||
70 | /* COMMAND_FIFO_STATUS */ | ||
71 | #define CMD_STS_MASK 0x100 | ||
72 | |||
73 | struct olpc_apsp { | ||
74 | struct device *dev; | ||
75 | struct serio *kbio; | ||
76 | struct serio *padio; | ||
77 | void __iomem *base; | ||
78 | int open_count; | ||
79 | int irq; | ||
80 | }; | ||
81 | |||
82 | static int olpc_apsp_write(struct serio *port, unsigned char val) | ||
83 | { | ||
84 | struct olpc_apsp *priv = port->port_data; | ||
85 | unsigned int i; | ||
86 | u32 which = 0; | ||
87 | |||
88 | if (port == priv->padio) | ||
89 | which = TOUCHPAD_PORT << PORT_SHIFT; | ||
90 | else | ||
91 | which = KEYBOARD_PORT << PORT_SHIFT; | ||
92 | |||
93 | dev_dbg(priv->dev, "olpc_apsp_write which=%x val=%x\n", which, val); | ||
94 | for (i = 0; i < 50; i++) { | ||
95 | u32 sts = readl(priv->base + COMMAND_FIFO_STATUS); | ||
96 | if ((sts & CMD_CNTR_MASK) < MAX_PENDING_CMDS) { | ||
97 | writel(which | val, | ||
98 | priv->base + SECURE_PROCESSOR_COMMAND); | ||
99 | return 0; | ||
100 | } | ||
101 | /* SP busy. This has not been seen in practice. */ | ||
102 | mdelay(1); | ||
103 | } | ||
104 | |||
105 | dev_dbg(priv->dev, "olpc_apsp_write timeout, status=%x\n", | ||
106 | readl(priv->base + COMMAND_FIFO_STATUS)); | ||
107 | |||
108 | return -ETIMEDOUT; | ||
109 | } | ||
110 | |||
111 | static irqreturn_t olpc_apsp_rx(int irq, void *dev_id) | ||
112 | { | ||
113 | struct olpc_apsp *priv = dev_id; | ||
114 | unsigned int w, tmp; | ||
115 | struct serio *serio; | ||
116 | |||
117 | /* | ||
118 | * Write 1 to PJ_RST_INTERRUPT to acknowledge and clear the interrupt | ||
119 | * Write 0xff00 to SECURE_PROCESSOR_COMMAND. | ||
120 | */ | ||
121 | tmp = readl(priv->base + PJ_RST_INTERRUPT); | ||
122 | if (!(tmp & SP_COMMAND_COMPLETE_RESET)) { | ||
123 | dev_warn(priv->dev, "spurious interrupt?\n"); | ||
124 | return IRQ_NONE; | ||
125 | } | ||
126 | |||
127 | w = readl(priv->base + COMMAND_RETURN_STATUS); | ||
128 | dev_dbg(priv->dev, "olpc_apsp_rx %x\n", w); | ||
129 | |||
130 | if (w >> PORT_SHIFT == KEYBOARD_PORT) | ||
131 | serio = priv->kbio; | ||
132 | else | ||
133 | serio = priv->padio; | ||
134 | |||
135 | serio_interrupt(serio, w & DATA_MASK, 0); | ||
136 | |||
137 | /* Ack and clear interrupt */ | ||
138 | writel(tmp | SP_COMMAND_COMPLETE_RESET, priv->base + PJ_RST_INTERRUPT); | ||
139 | writel(PORT_MASK, priv->base + SECURE_PROCESSOR_COMMAND); | ||
140 | |||
141 | pm_wakeup_event(priv->dev, 1000); | ||
142 | return IRQ_HANDLED; | ||
143 | } | ||
144 | |||
145 | static int olpc_apsp_open(struct serio *port) | ||
146 | { | ||
147 | struct olpc_apsp *priv = port->port_data; | ||
148 | unsigned int tmp; | ||
149 | |||
150 | if (priv->open_count++ == 0) { | ||
151 | /* Enable interrupt 0 by clearing its bit */ | ||
152 | tmp = readl(priv->base + PJ_INTERRUPT_MASK); | ||
153 | writel(tmp & ~INT_0, priv->base + PJ_INTERRUPT_MASK); | ||
154 | } | ||
155 | |||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | static void olpc_apsp_close(struct serio *port) | ||
160 | { | ||
161 | struct olpc_apsp *priv = port->port_data; | ||
162 | unsigned int tmp; | ||
163 | |||
164 | if (--priv->open_count == 0) { | ||
165 | /* Disable interrupt 0 */ | ||
166 | tmp = readl(priv->base + PJ_INTERRUPT_MASK); | ||
167 | writel(tmp | INT_0, priv->base + PJ_INTERRUPT_MASK); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | static int olpc_apsp_probe(struct platform_device *pdev) | ||
172 | { | ||
173 | struct serio *kb_serio, *pad_serio; | ||
174 | struct olpc_apsp *priv; | ||
175 | struct resource *res; | ||
176 | struct device_node *np; | ||
177 | unsigned long l; | ||
178 | int error; | ||
179 | |||
180 | priv = devm_kzalloc(&pdev->dev, sizeof(struct olpc_apsp), GFP_KERNEL); | ||
181 | if (!priv) | ||
182 | return -ENOMEM; | ||
183 | |||
184 | np = pdev->dev.of_node; | ||
185 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
186 | if (!res) | ||
187 | return -ENOENT; | ||
188 | |||
189 | priv->base = devm_ioremap_resource(&pdev->dev, res); | ||
190 | if (IS_ERR(priv->base)) { | ||
191 | dev_err(&pdev->dev, "Failed to map WTM registers\n"); | ||
192 | return PTR_ERR(priv->base); | ||
193 | } | ||
194 | |||
195 | priv->irq = platform_get_irq(pdev, 0); | ||
196 | if (priv->irq < 0) | ||
197 | return priv->irq; | ||
198 | |||
199 | l = readl(priv->base + COMMAND_FIFO_STATUS); | ||
200 | if (!(l & CMD_STS_MASK)) { | ||
201 | dev_err(&pdev->dev, "SP cannot accept commands.\n"); | ||
202 | return -EIO; | ||
203 | } | ||
204 | |||
205 | /* KEYBOARD */ | ||
206 | kb_serio = kzalloc(sizeof(struct serio), GFP_KERNEL); | ||
207 | if (!kb_serio) | ||
208 | return -ENOMEM; | ||
209 | kb_serio->id.type = SERIO_8042_XL; | ||
210 | kb_serio->write = olpc_apsp_write; | ||
211 | kb_serio->open = olpc_apsp_open; | ||
212 | kb_serio->close = olpc_apsp_close; | ||
213 | kb_serio->port_data = priv; | ||
214 | kb_serio->dev.parent = &pdev->dev; | ||
215 | strlcpy(kb_serio->name, "sp keyboard", sizeof(kb_serio->name)); | ||
216 | strlcpy(kb_serio->phys, "sp/serio0", sizeof(kb_serio->phys)); | ||
217 | priv->kbio = kb_serio; | ||
218 | serio_register_port(kb_serio); | ||
219 | |||
220 | /* TOUCHPAD */ | ||
221 | pad_serio = kzalloc(sizeof(struct serio), GFP_KERNEL); | ||
222 | if (!pad_serio) { | ||
223 | error = -ENOMEM; | ||
224 | goto err_pad; | ||
225 | } | ||
226 | pad_serio->id.type = SERIO_8042; | ||
227 | pad_serio->write = olpc_apsp_write; | ||
228 | pad_serio->open = olpc_apsp_open; | ||
229 | pad_serio->close = olpc_apsp_close; | ||
230 | pad_serio->port_data = priv; | ||
231 | pad_serio->dev.parent = &pdev->dev; | ||
232 | strlcpy(pad_serio->name, "sp touchpad", sizeof(pad_serio->name)); | ||
233 | strlcpy(pad_serio->phys, "sp/serio1", sizeof(pad_serio->phys)); | ||
234 | priv->padio = pad_serio; | ||
235 | serio_register_port(pad_serio); | ||
236 | |||
237 | error = request_irq(priv->irq, olpc_apsp_rx, 0, "olpc-apsp", priv); | ||
238 | if (error) { | ||
239 | dev_err(&pdev->dev, "Failed to request IRQ\n"); | ||
240 | goto err_irq; | ||
241 | } | ||
242 | |||
243 | priv->dev = &pdev->dev; | ||
244 | device_init_wakeup(priv->dev, 1); | ||
245 | platform_set_drvdata(pdev, priv); | ||
246 | |||
247 | dev_dbg(&pdev->dev, "probed successfully.\n"); | ||
248 | return 0; | ||
249 | |||
250 | err_irq: | ||
251 | serio_unregister_port(pad_serio); | ||
252 | err_pad: | ||
253 | serio_unregister_port(kb_serio); | ||
254 | return error; | ||
255 | } | ||
256 | |||
257 | static int olpc_apsp_remove(struct platform_device *pdev) | ||
258 | { | ||
259 | struct olpc_apsp *priv = platform_get_drvdata(pdev); | ||
260 | |||
261 | free_irq(priv->irq, priv); | ||
262 | |||
263 | serio_unregister_port(priv->kbio); | ||
264 | serio_unregister_port(priv->padio); | ||
265 | |||
266 | return 0; | ||
267 | } | ||
268 | |||
269 | static struct of_device_id olpc_apsp_dt_ids[] = { | ||
270 | { .compatible = "olpc,ap-sp", }, | ||
271 | {} | ||
272 | }; | ||
273 | MODULE_DEVICE_TABLE(of, olpc_apsp_dt_ids); | ||
274 | |||
275 | static struct platform_driver olpc_apsp_driver = { | ||
276 | .probe = olpc_apsp_probe, | ||
277 | .remove = olpc_apsp_remove, | ||
278 | .driver = { | ||
279 | .name = "olpc-apsp", | ||
280 | .owner = THIS_MODULE, | ||
281 | .of_match_table = olpc_apsp_dt_ids, | ||
282 | }, | ||
283 | }; | ||
284 | |||
285 | MODULE_DESCRIPTION("OLPC AP-SP serio driver"); | ||
286 | MODULE_LICENSE("GPL"); | ||
287 | module_platform_driver(olpc_apsp_driver); | ||