1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * LoCoMo keyboard driver for Linux-based ARM PDAs:
4*4882a593Smuzhiyun * - SHARP Zaurus Collie (SL-5500)
5*4882a593Smuzhiyun * - SHARP Zaurus Poodle (SL-5600)
6*4882a593Smuzhiyun *
7*4882a593Smuzhiyun * Copyright (c) 2005 John Lenz
8*4882a593Smuzhiyun * Based on from xtkbd.c
9*4882a593Smuzhiyun */
10*4882a593Smuzhiyun
11*4882a593Smuzhiyun #include <linux/slab.h>
12*4882a593Smuzhiyun #include <linux/module.h>
13*4882a593Smuzhiyun #include <linux/init.h>
14*4882a593Smuzhiyun #include <linux/input.h>
15*4882a593Smuzhiyun #include <linux/delay.h>
16*4882a593Smuzhiyun #include <linux/device.h>
17*4882a593Smuzhiyun #include <linux/interrupt.h>
18*4882a593Smuzhiyun #include <linux/ioport.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun #include <asm/hardware/locomo.h>
21*4882a593Smuzhiyun #include <asm/irq.h>
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>");
24*4882a593Smuzhiyun MODULE_DESCRIPTION("LoCoMo keyboard driver");
25*4882a593Smuzhiyun MODULE_LICENSE("GPL");
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun #define LOCOMOKBD_NUMKEYS 128
28*4882a593Smuzhiyun
29*4882a593Smuzhiyun #define KEY_ACTIVITY KEY_F16
30*4882a593Smuzhiyun #define KEY_CONTACT KEY_F18
31*4882a593Smuzhiyun #define KEY_CENTER KEY_F15
32*4882a593Smuzhiyun
33*4882a593Smuzhiyun static const unsigned char
34*4882a593Smuzhiyun locomokbd_keycode[LOCOMOKBD_NUMKEYS] = {
35*4882a593Smuzhiyun 0, KEY_ESC, KEY_ACTIVITY, 0, 0, 0, 0, 0, 0, 0, /* 0 - 9 */
36*4882a593Smuzhiyun 0, 0, 0, 0, 0, 0, 0, KEY_MENU, KEY_HOME, KEY_CONTACT, /* 10 - 19 */
37*4882a593Smuzhiyun 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 29 */
38*4882a593Smuzhiyun 0, 0, 0, KEY_CENTER, 0, KEY_MAIL, 0, 0, 0, 0, /* 30 - 39 */
39*4882a593Smuzhiyun 0, 0, 0, 0, 0, 0, 0, 0, 0, KEY_RIGHT, /* 40 - 49 */
40*4882a593Smuzhiyun KEY_UP, KEY_LEFT, 0, 0, KEY_P, 0, KEY_O, KEY_I, KEY_Y, KEY_T, /* 50 - 59 */
41*4882a593Smuzhiyun KEY_E, KEY_W, 0, 0, 0, 0, KEY_DOWN, KEY_ENTER, 0, 0, /* 60 - 69 */
42*4882a593Smuzhiyun KEY_BACKSPACE, 0, KEY_L, KEY_U, KEY_H, KEY_R, KEY_D, KEY_Q, 0, 0, /* 70 - 79 */
43*4882a593Smuzhiyun 0, 0, 0, 0, 0, 0, KEY_ENTER, KEY_RIGHTSHIFT, KEY_K, KEY_J, /* 80 - 89 */
44*4882a593Smuzhiyun KEY_G, KEY_F, KEY_X, KEY_S, 0, 0, 0, 0, 0, 0, /* 90 - 99 */
45*4882a593Smuzhiyun 0, 0, KEY_DOT, 0, KEY_COMMA, KEY_N, KEY_B, KEY_C, KEY_Z, KEY_A, /* 100 - 109 */
46*4882a593Smuzhiyun KEY_LEFTSHIFT, KEY_TAB, KEY_LEFTCTRL, 0, 0, 0, 0, 0, 0, 0, /* 110 - 119 */
47*4882a593Smuzhiyun KEY_M, KEY_SPACE, KEY_V, KEY_APOSTROPHE, KEY_SLASH, 0, 0, 0 /* 120 - 128 */
48*4882a593Smuzhiyun };
49*4882a593Smuzhiyun
50*4882a593Smuzhiyun #define KB_ROWS 16
51*4882a593Smuzhiyun #define KB_COLS 8
52*4882a593Smuzhiyun #define KB_ROWMASK(r) (1 << (r))
53*4882a593Smuzhiyun #define SCANCODE(c,r) ( ((c)<<4) + (r) + 1 )
54*4882a593Smuzhiyun
55*4882a593Smuzhiyun #define KB_DELAY 8
56*4882a593Smuzhiyun #define SCAN_INTERVAL (HZ/10)
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun struct locomokbd {
59*4882a593Smuzhiyun unsigned char keycode[LOCOMOKBD_NUMKEYS];
60*4882a593Smuzhiyun struct input_dev *input;
61*4882a593Smuzhiyun char phys[32];
62*4882a593Smuzhiyun
63*4882a593Smuzhiyun unsigned long base;
64*4882a593Smuzhiyun spinlock_t lock;
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun struct timer_list timer;
67*4882a593Smuzhiyun unsigned long suspend_jiffies;
68*4882a593Smuzhiyun unsigned int count_cancel;
69*4882a593Smuzhiyun };
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun /* helper functions for reading the keyboard matrix */
locomokbd_charge_all(unsigned long membase)72*4882a593Smuzhiyun static inline void locomokbd_charge_all(unsigned long membase)
73*4882a593Smuzhiyun {
74*4882a593Smuzhiyun locomo_writel(0x00FF, membase + LOCOMO_KSC);
75*4882a593Smuzhiyun }
76*4882a593Smuzhiyun
locomokbd_activate_all(unsigned long membase)77*4882a593Smuzhiyun static inline void locomokbd_activate_all(unsigned long membase)
78*4882a593Smuzhiyun {
79*4882a593Smuzhiyun unsigned long r;
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun locomo_writel(0, membase + LOCOMO_KSC);
82*4882a593Smuzhiyun r = locomo_readl(membase + LOCOMO_KIC);
83*4882a593Smuzhiyun r &= 0xFEFF;
84*4882a593Smuzhiyun locomo_writel(r, membase + LOCOMO_KIC);
85*4882a593Smuzhiyun }
86*4882a593Smuzhiyun
locomokbd_activate_col(unsigned long membase,int col)87*4882a593Smuzhiyun static inline void locomokbd_activate_col(unsigned long membase, int col)
88*4882a593Smuzhiyun {
89*4882a593Smuzhiyun unsigned short nset;
90*4882a593Smuzhiyun unsigned short nbset;
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun nset = 0xFF & ~(1 << col);
93*4882a593Smuzhiyun nbset = (nset << 8) + nset;
94*4882a593Smuzhiyun locomo_writel(nbset, membase + LOCOMO_KSC);
95*4882a593Smuzhiyun }
96*4882a593Smuzhiyun
locomokbd_reset_col(unsigned long membase,int col)97*4882a593Smuzhiyun static inline void locomokbd_reset_col(unsigned long membase, int col)
98*4882a593Smuzhiyun {
99*4882a593Smuzhiyun unsigned short nbset;
100*4882a593Smuzhiyun
101*4882a593Smuzhiyun nbset = ((0xFF & ~(1 << col)) << 8) + 0xFF;
102*4882a593Smuzhiyun locomo_writel(nbset, membase + LOCOMO_KSC);
103*4882a593Smuzhiyun }
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun /*
106*4882a593Smuzhiyun * The LoCoMo keyboard only generates interrupts when a key is pressed.
107*4882a593Smuzhiyun * So when a key is pressed, we enable a timer. This timer scans the
108*4882a593Smuzhiyun * keyboard, and this is how we detect when the key is released.
109*4882a593Smuzhiyun */
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun /* Scan the hardware keyboard and push any changes up through the input layer */
locomokbd_scankeyboard(struct locomokbd * locomokbd)112*4882a593Smuzhiyun static void locomokbd_scankeyboard(struct locomokbd *locomokbd)
113*4882a593Smuzhiyun {
114*4882a593Smuzhiyun unsigned int row, col, rowd;
115*4882a593Smuzhiyun unsigned long flags;
116*4882a593Smuzhiyun unsigned int num_pressed;
117*4882a593Smuzhiyun unsigned long membase = locomokbd->base;
118*4882a593Smuzhiyun
119*4882a593Smuzhiyun spin_lock_irqsave(&locomokbd->lock, flags);
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun locomokbd_charge_all(membase);
122*4882a593Smuzhiyun
123*4882a593Smuzhiyun num_pressed = 0;
124*4882a593Smuzhiyun for (col = 0; col < KB_COLS; col++) {
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun locomokbd_activate_col(membase, col);
127*4882a593Smuzhiyun udelay(KB_DELAY);
128*4882a593Smuzhiyun
129*4882a593Smuzhiyun rowd = ~locomo_readl(membase + LOCOMO_KIB);
130*4882a593Smuzhiyun for (row = 0; row < KB_ROWS; row++) {
131*4882a593Smuzhiyun unsigned int scancode, pressed, key;
132*4882a593Smuzhiyun
133*4882a593Smuzhiyun scancode = SCANCODE(col, row);
134*4882a593Smuzhiyun pressed = rowd & KB_ROWMASK(row);
135*4882a593Smuzhiyun key = locomokbd->keycode[scancode];
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun input_report_key(locomokbd->input, key, pressed);
138*4882a593Smuzhiyun if (likely(!pressed))
139*4882a593Smuzhiyun continue;
140*4882a593Smuzhiyun
141*4882a593Smuzhiyun num_pressed++;
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun /* The "Cancel/ESC" key is labeled "On/Off" on
144*4882a593Smuzhiyun * Collie and Poodle and should suspend the device
145*4882a593Smuzhiyun * if it was pressed for more than a second. */
146*4882a593Smuzhiyun if (unlikely(key == KEY_ESC)) {
147*4882a593Smuzhiyun if (!time_after(jiffies,
148*4882a593Smuzhiyun locomokbd->suspend_jiffies + HZ))
149*4882a593Smuzhiyun continue;
150*4882a593Smuzhiyun if (locomokbd->count_cancel++
151*4882a593Smuzhiyun != (HZ/SCAN_INTERVAL + 1))
152*4882a593Smuzhiyun continue;
153*4882a593Smuzhiyun input_event(locomokbd->input, EV_PWR,
154*4882a593Smuzhiyun KEY_SUSPEND, 1);
155*4882a593Smuzhiyun locomokbd->suspend_jiffies = jiffies;
156*4882a593Smuzhiyun } else
157*4882a593Smuzhiyun locomokbd->count_cancel = 0;
158*4882a593Smuzhiyun }
159*4882a593Smuzhiyun locomokbd_reset_col(membase, col);
160*4882a593Smuzhiyun }
161*4882a593Smuzhiyun locomokbd_activate_all(membase);
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun input_sync(locomokbd->input);
164*4882a593Smuzhiyun
165*4882a593Smuzhiyun /* if any keys are pressed, enable the timer */
166*4882a593Smuzhiyun if (num_pressed)
167*4882a593Smuzhiyun mod_timer(&locomokbd->timer, jiffies + SCAN_INTERVAL);
168*4882a593Smuzhiyun else
169*4882a593Smuzhiyun locomokbd->count_cancel = 0;
170*4882a593Smuzhiyun
171*4882a593Smuzhiyun spin_unlock_irqrestore(&locomokbd->lock, flags);
172*4882a593Smuzhiyun }
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun /*
175*4882a593Smuzhiyun * LoCoMo keyboard interrupt handler.
176*4882a593Smuzhiyun */
locomokbd_interrupt(int irq,void * dev_id)177*4882a593Smuzhiyun static irqreturn_t locomokbd_interrupt(int irq, void *dev_id)
178*4882a593Smuzhiyun {
179*4882a593Smuzhiyun struct locomokbd *locomokbd = dev_id;
180*4882a593Smuzhiyun u16 r;
181*4882a593Smuzhiyun
182*4882a593Smuzhiyun r = locomo_readl(locomokbd->base + LOCOMO_KIC);
183*4882a593Smuzhiyun if ((r & 0x0001) == 0)
184*4882a593Smuzhiyun return IRQ_HANDLED;
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun locomo_writel(r & ~0x0100, locomokbd->base + LOCOMO_KIC); /* Ack */
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun /** wait chattering delay **/
189*4882a593Smuzhiyun udelay(100);
190*4882a593Smuzhiyun
191*4882a593Smuzhiyun locomokbd_scankeyboard(locomokbd);
192*4882a593Smuzhiyun return IRQ_HANDLED;
193*4882a593Smuzhiyun }
194*4882a593Smuzhiyun
195*4882a593Smuzhiyun /*
196*4882a593Smuzhiyun * LoCoMo timer checking for released keys
197*4882a593Smuzhiyun */
locomokbd_timer_callback(struct timer_list * t)198*4882a593Smuzhiyun static void locomokbd_timer_callback(struct timer_list *t)
199*4882a593Smuzhiyun {
200*4882a593Smuzhiyun struct locomokbd *locomokbd = from_timer(locomokbd, t, timer);
201*4882a593Smuzhiyun
202*4882a593Smuzhiyun locomokbd_scankeyboard(locomokbd);
203*4882a593Smuzhiyun }
204*4882a593Smuzhiyun
locomokbd_open(struct input_dev * dev)205*4882a593Smuzhiyun static int locomokbd_open(struct input_dev *dev)
206*4882a593Smuzhiyun {
207*4882a593Smuzhiyun struct locomokbd *locomokbd = input_get_drvdata(dev);
208*4882a593Smuzhiyun u16 r;
209*4882a593Smuzhiyun
210*4882a593Smuzhiyun r = locomo_readl(locomokbd->base + LOCOMO_KIC) | 0x0010;
211*4882a593Smuzhiyun locomo_writel(r, locomokbd->base + LOCOMO_KIC);
212*4882a593Smuzhiyun return 0;
213*4882a593Smuzhiyun }
214*4882a593Smuzhiyun
locomokbd_close(struct input_dev * dev)215*4882a593Smuzhiyun static void locomokbd_close(struct input_dev *dev)
216*4882a593Smuzhiyun {
217*4882a593Smuzhiyun struct locomokbd *locomokbd = input_get_drvdata(dev);
218*4882a593Smuzhiyun u16 r;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun r = locomo_readl(locomokbd->base + LOCOMO_KIC) & ~0x0010;
221*4882a593Smuzhiyun locomo_writel(r, locomokbd->base + LOCOMO_KIC);
222*4882a593Smuzhiyun }
223*4882a593Smuzhiyun
locomokbd_probe(struct locomo_dev * dev)224*4882a593Smuzhiyun static int locomokbd_probe(struct locomo_dev *dev)
225*4882a593Smuzhiyun {
226*4882a593Smuzhiyun struct locomokbd *locomokbd;
227*4882a593Smuzhiyun struct input_dev *input_dev;
228*4882a593Smuzhiyun int i, err;
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun locomokbd = kzalloc(sizeof(struct locomokbd), GFP_KERNEL);
231*4882a593Smuzhiyun input_dev = input_allocate_device();
232*4882a593Smuzhiyun if (!locomokbd || !input_dev) {
233*4882a593Smuzhiyun err = -ENOMEM;
234*4882a593Smuzhiyun goto err_free_mem;
235*4882a593Smuzhiyun }
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun /* try and claim memory region */
238*4882a593Smuzhiyun if (!request_mem_region((unsigned long) dev->mapbase,
239*4882a593Smuzhiyun dev->length,
240*4882a593Smuzhiyun LOCOMO_DRIVER_NAME(dev))) {
241*4882a593Smuzhiyun err = -EBUSY;
242*4882a593Smuzhiyun printk(KERN_ERR "locomokbd: Can't acquire access to io memory for keyboard\n");
243*4882a593Smuzhiyun goto err_free_mem;
244*4882a593Smuzhiyun }
245*4882a593Smuzhiyun
246*4882a593Smuzhiyun locomo_set_drvdata(dev, locomokbd);
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun locomokbd->base = (unsigned long) dev->mapbase;
249*4882a593Smuzhiyun
250*4882a593Smuzhiyun spin_lock_init(&locomokbd->lock);
251*4882a593Smuzhiyun
252*4882a593Smuzhiyun timer_setup(&locomokbd->timer, locomokbd_timer_callback, 0);
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun locomokbd->suspend_jiffies = jiffies;
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun locomokbd->input = input_dev;
257*4882a593Smuzhiyun strcpy(locomokbd->phys, "locomokbd/input0");
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun input_dev->name = "LoCoMo keyboard";
260*4882a593Smuzhiyun input_dev->phys = locomokbd->phys;
261*4882a593Smuzhiyun input_dev->id.bustype = BUS_HOST;
262*4882a593Smuzhiyun input_dev->id.vendor = 0x0001;
263*4882a593Smuzhiyun input_dev->id.product = 0x0001;
264*4882a593Smuzhiyun input_dev->id.version = 0x0100;
265*4882a593Smuzhiyun input_dev->open = locomokbd_open;
266*4882a593Smuzhiyun input_dev->close = locomokbd_close;
267*4882a593Smuzhiyun input_dev->dev.parent = &dev->dev;
268*4882a593Smuzhiyun
269*4882a593Smuzhiyun input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) |
270*4882a593Smuzhiyun BIT_MASK(EV_PWR);
271*4882a593Smuzhiyun input_dev->keycode = locomokbd->keycode;
272*4882a593Smuzhiyun input_dev->keycodesize = sizeof(locomokbd_keycode[0]);
273*4882a593Smuzhiyun input_dev->keycodemax = ARRAY_SIZE(locomokbd_keycode);
274*4882a593Smuzhiyun
275*4882a593Smuzhiyun input_set_drvdata(input_dev, locomokbd);
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun memcpy(locomokbd->keycode, locomokbd_keycode, sizeof(locomokbd->keycode));
278*4882a593Smuzhiyun for (i = 0; i < LOCOMOKBD_NUMKEYS; i++)
279*4882a593Smuzhiyun set_bit(locomokbd->keycode[i], input_dev->keybit);
280*4882a593Smuzhiyun clear_bit(0, input_dev->keybit);
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun /* attempt to get the interrupt */
283*4882a593Smuzhiyun err = request_irq(dev->irq[0], locomokbd_interrupt, 0, "locomokbd", locomokbd);
284*4882a593Smuzhiyun if (err) {
285*4882a593Smuzhiyun printk(KERN_ERR "locomokbd: Can't get irq for keyboard\n");
286*4882a593Smuzhiyun goto err_release_region;
287*4882a593Smuzhiyun }
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun err = input_register_device(locomokbd->input);
290*4882a593Smuzhiyun if (err)
291*4882a593Smuzhiyun goto err_free_irq;
292*4882a593Smuzhiyun
293*4882a593Smuzhiyun return 0;
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun err_free_irq:
296*4882a593Smuzhiyun free_irq(dev->irq[0], locomokbd);
297*4882a593Smuzhiyun err_release_region:
298*4882a593Smuzhiyun release_mem_region((unsigned long) dev->mapbase, dev->length);
299*4882a593Smuzhiyun locomo_set_drvdata(dev, NULL);
300*4882a593Smuzhiyun err_free_mem:
301*4882a593Smuzhiyun input_free_device(input_dev);
302*4882a593Smuzhiyun kfree(locomokbd);
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun return err;
305*4882a593Smuzhiyun }
306*4882a593Smuzhiyun
locomokbd_remove(struct locomo_dev * dev)307*4882a593Smuzhiyun static int locomokbd_remove(struct locomo_dev *dev)
308*4882a593Smuzhiyun {
309*4882a593Smuzhiyun struct locomokbd *locomokbd = locomo_get_drvdata(dev);
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun free_irq(dev->irq[0], locomokbd);
312*4882a593Smuzhiyun
313*4882a593Smuzhiyun del_timer_sync(&locomokbd->timer);
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun input_unregister_device(locomokbd->input);
316*4882a593Smuzhiyun locomo_set_drvdata(dev, NULL);
317*4882a593Smuzhiyun
318*4882a593Smuzhiyun release_mem_region((unsigned long) dev->mapbase, dev->length);
319*4882a593Smuzhiyun
320*4882a593Smuzhiyun kfree(locomokbd);
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun return 0;
323*4882a593Smuzhiyun }
324*4882a593Smuzhiyun
325*4882a593Smuzhiyun static struct locomo_driver keyboard_driver = {
326*4882a593Smuzhiyun .drv = {
327*4882a593Smuzhiyun .name = "locomokbd"
328*4882a593Smuzhiyun },
329*4882a593Smuzhiyun .devid = LOCOMO_DEVID_KEYBOARD,
330*4882a593Smuzhiyun .probe = locomokbd_probe,
331*4882a593Smuzhiyun .remove = locomokbd_remove,
332*4882a593Smuzhiyun };
333*4882a593Smuzhiyun
locomokbd_init(void)334*4882a593Smuzhiyun static int __init locomokbd_init(void)
335*4882a593Smuzhiyun {
336*4882a593Smuzhiyun return locomo_driver_register(&keyboard_driver);
337*4882a593Smuzhiyun }
338*4882a593Smuzhiyun
locomokbd_exit(void)339*4882a593Smuzhiyun static void __exit locomokbd_exit(void)
340*4882a593Smuzhiyun {
341*4882a593Smuzhiyun locomo_driver_unregister(&keyboard_driver);
342*4882a593Smuzhiyun }
343*4882a593Smuzhiyun
344*4882a593Smuzhiyun module_init(locomokbd_init);
345*4882a593Smuzhiyun module_exit(locomokbd_exit);
346