diff options
Diffstat (limited to 'drivers/input/serio/ambakmi.c')
-rw-r--r-- | drivers/input/serio/ambakmi.c | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c new file mode 100644 index 000000000000..9b1ab5e7a98d --- /dev/null +++ b/drivers/input/serio/ambakmi.c | |||
@@ -0,0 +1,231 @@ | |||
1 | /* | ||
2 | * linux/drivers/input/serio/ambakmi.c | ||
3 | * | ||
4 | * Copyright (C) 2000-2003 Deep Blue Solutions Ltd. | ||
5 | * Copyright (C) 2002 Russell King. | ||
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 as published by | ||
9 | * the Free Software Foundation; either version 2 of the License, or | ||
10 | * (at your option) any later version. | ||
11 | */ | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/init.h> | ||
14 | #include <linux/serio.h> | ||
15 | #include <linux/errno.h> | ||
16 | #include <linux/interrupt.h> | ||
17 | #include <linux/ioport.h> | ||
18 | #include <linux/device.h> | ||
19 | #include <linux/delay.h> | ||
20 | #include <linux/slab.h> | ||
21 | #include <linux/err.h> | ||
22 | |||
23 | #include <asm/io.h> | ||
24 | #include <asm/irq.h> | ||
25 | #include <asm/hardware/amba.h> | ||
26 | #include <asm/hardware/amba_kmi.h> | ||
27 | #include <asm/hardware/clock.h> | ||
28 | |||
29 | #define KMI_BASE (kmi->base) | ||
30 | |||
31 | struct amba_kmi_port { | ||
32 | struct serio *io; | ||
33 | struct clk *clk; | ||
34 | void __iomem *base; | ||
35 | unsigned int irq; | ||
36 | unsigned int divisor; | ||
37 | unsigned int open; | ||
38 | }; | ||
39 | |||
40 | static irqreturn_t amba_kmi_int(int irq, void *dev_id, struct pt_regs *regs) | ||
41 | { | ||
42 | struct amba_kmi_port *kmi = dev_id; | ||
43 | unsigned int status = readb(KMIIR); | ||
44 | int handled = IRQ_NONE; | ||
45 | |||
46 | while (status & KMIIR_RXINTR) { | ||
47 | serio_interrupt(kmi->io, readb(KMIDATA), 0, regs); | ||
48 | status = readb(KMIIR); | ||
49 | handled = IRQ_HANDLED; | ||
50 | } | ||
51 | |||
52 | return handled; | ||
53 | } | ||
54 | |||
55 | static int amba_kmi_write(struct serio *io, unsigned char val) | ||
56 | { | ||
57 | struct amba_kmi_port *kmi = io->port_data; | ||
58 | unsigned int timeleft = 10000; /* timeout in 100ms */ | ||
59 | |||
60 | while ((readb(KMISTAT) & KMISTAT_TXEMPTY) == 0 && timeleft--) | ||
61 | udelay(10); | ||
62 | |||
63 | if (timeleft) | ||
64 | writeb(val, KMIDATA); | ||
65 | |||
66 | return timeleft ? 0 : SERIO_TIMEOUT; | ||
67 | } | ||
68 | |||
69 | static int amba_kmi_open(struct serio *io) | ||
70 | { | ||
71 | struct amba_kmi_port *kmi = io->port_data; | ||
72 | unsigned int divisor; | ||
73 | int ret; | ||
74 | |||
75 | ret = clk_use(kmi->clk); | ||
76 | if (ret) | ||
77 | goto out; | ||
78 | |||
79 | ret = clk_enable(kmi->clk); | ||
80 | if (ret) | ||
81 | goto clk_unuse; | ||
82 | |||
83 | divisor = clk_get_rate(kmi->clk) / 8000000 - 1; | ||
84 | writeb(divisor, KMICLKDIV); | ||
85 | writeb(KMICR_EN, KMICR); | ||
86 | |||
87 | ret = request_irq(kmi->irq, amba_kmi_int, 0, "kmi-pl050", kmi); | ||
88 | if (ret) { | ||
89 | printk(KERN_ERR "kmi: failed to claim IRQ%d\n", kmi->irq); | ||
90 | writeb(0, KMICR); | ||
91 | goto clk_disable; | ||
92 | } | ||
93 | |||
94 | writeb(KMICR_EN | KMICR_RXINTREN, KMICR); | ||
95 | |||
96 | return 0; | ||
97 | |||
98 | clk_disable: | ||
99 | clk_disable(kmi->clk); | ||
100 | clk_unuse: | ||
101 | clk_unuse(kmi->clk); | ||
102 | out: | ||
103 | return ret; | ||
104 | } | ||
105 | |||
106 | static void amba_kmi_close(struct serio *io) | ||
107 | { | ||
108 | struct amba_kmi_port *kmi = io->port_data; | ||
109 | |||
110 | writeb(0, KMICR); | ||
111 | |||
112 | free_irq(kmi->irq, kmi); | ||
113 | clk_disable(kmi->clk); | ||
114 | clk_unuse(kmi->clk); | ||
115 | } | ||
116 | |||
117 | static int amba_kmi_probe(struct amba_device *dev, void *id) | ||
118 | { | ||
119 | struct amba_kmi_port *kmi; | ||
120 | struct serio *io; | ||
121 | int ret; | ||
122 | |||
123 | ret = amba_request_regions(dev, NULL); | ||
124 | if (ret) | ||
125 | return ret; | ||
126 | |||
127 | kmi = kmalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); | ||
128 | io = kmalloc(sizeof(struct serio), GFP_KERNEL); | ||
129 | if (!kmi || !io) { | ||
130 | ret = -ENOMEM; | ||
131 | goto out; | ||
132 | } | ||
133 | |||
134 | memset(kmi, 0, sizeof(struct amba_kmi_port)); | ||
135 | memset(io, 0, sizeof(struct serio)); | ||
136 | |||
137 | io->id.type = SERIO_8042; | ||
138 | io->write = amba_kmi_write; | ||
139 | io->open = amba_kmi_open; | ||
140 | io->close = amba_kmi_close; | ||
141 | strlcpy(io->name, dev->dev.bus_id, sizeof(io->name)); | ||
142 | strlcpy(io->phys, dev->dev.bus_id, sizeof(io->phys)); | ||
143 | io->port_data = kmi; | ||
144 | io->dev.parent = &dev->dev; | ||
145 | |||
146 | kmi->io = io; | ||
147 | kmi->base = ioremap(dev->res.start, KMI_SIZE); | ||
148 | if (!kmi->base) { | ||
149 | ret = -ENOMEM; | ||
150 | goto out; | ||
151 | } | ||
152 | |||
153 | kmi->clk = clk_get(&dev->dev, "KMIREFCLK"); | ||
154 | if (IS_ERR(kmi->clk)) { | ||
155 | ret = PTR_ERR(kmi->clk); | ||
156 | goto unmap; | ||
157 | } | ||
158 | |||
159 | kmi->irq = dev->irq[0]; | ||
160 | amba_set_drvdata(dev, kmi); | ||
161 | |||
162 | serio_register_port(kmi->io); | ||
163 | return 0; | ||
164 | |||
165 | unmap: | ||
166 | iounmap(kmi->base); | ||
167 | out: | ||
168 | kfree(kmi); | ||
169 | kfree(io); | ||
170 | amba_release_regions(dev); | ||
171 | return ret; | ||
172 | } | ||
173 | |||
174 | static int amba_kmi_remove(struct amba_device *dev) | ||
175 | { | ||
176 | struct amba_kmi_port *kmi = amba_get_drvdata(dev); | ||
177 | |||
178 | amba_set_drvdata(dev, NULL); | ||
179 | |||
180 | serio_unregister_port(kmi->io); | ||
181 | clk_put(kmi->clk); | ||
182 | iounmap(kmi->base); | ||
183 | kfree(kmi); | ||
184 | amba_release_regions(dev); | ||
185 | return 0; | ||
186 | } | ||
187 | |||
188 | static int amba_kmi_resume(struct amba_device *dev) | ||
189 | { | ||
190 | struct amba_kmi_port *kmi = amba_get_drvdata(dev); | ||
191 | |||
192 | /* kick the serio layer to rescan this port */ | ||
193 | serio_reconnect(kmi->io); | ||
194 | |||
195 | return 0; | ||
196 | } | ||
197 | |||
198 | static struct amba_id amba_kmi_idtable[] = { | ||
199 | { | ||
200 | .id = 0x00041050, | ||
201 | .mask = 0x000fffff, | ||
202 | }, | ||
203 | { 0, 0 } | ||
204 | }; | ||
205 | |||
206 | static struct amba_driver ambakmi_driver = { | ||
207 | .drv = { | ||
208 | .name = "kmi-pl050", | ||
209 | }, | ||
210 | .id_table = amba_kmi_idtable, | ||
211 | .probe = amba_kmi_probe, | ||
212 | .remove = amba_kmi_remove, | ||
213 | .resume = amba_kmi_resume, | ||
214 | }; | ||
215 | |||
216 | static int __init amba_kmi_init(void) | ||
217 | { | ||
218 | return amba_driver_register(&ambakmi_driver); | ||
219 | } | ||
220 | |||
221 | static void __exit amba_kmi_exit(void) | ||
222 | { | ||
223 | amba_driver_unregister(&ambakmi_driver); | ||
224 | } | ||
225 | |||
226 | module_init(amba_kmi_init); | ||
227 | module_exit(amba_kmi_exit); | ||
228 | |||
229 | MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); | ||
230 | MODULE_DESCRIPTION("AMBA KMI controller driver"); | ||
231 | MODULE_LICENSE("GPL"); | ||