Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/drivers/input/serio/ams_delta_serio.c
15112 views
1
/*
2
* Amstrad E3 (Delta) keyboard port driver
3
*
4
* Copyright (c) 2006 Matt Callow
5
* Copyright (c) 2010 Janusz Krzysztofik
6
*
7
* This program is free software; you can redistribute it and/or modify it
8
* under the terms of the GNU General Public License version 2 as published by
9
* the Free Software Foundation.
10
*
11
* Thanks to Cliff Lawson for his help
12
*
13
* The Amstrad Delta keyboard (aka mailboard) uses normal PC-AT style serial
14
* transmission. The keyboard port is formed of two GPIO lines, for clock
15
* and data. Due to strict timing requirements of the interface,
16
* the serial data stream is read and processed by a FIQ handler.
17
* The resulting words are fetched by this driver from a circular buffer.
18
*
19
* Standard AT keyboard driver (atkbd) is used for handling the keyboard data.
20
* However, when used with the E3 mailboard that producecs non-standard
21
* scancodes, a custom key table must be prepared and loaded from userspace.
22
*/
23
#include <linux/gpio.h>
24
#include <linux/irq.h>
25
#include <linux/serio.h>
26
#include <linux/slab.h>
27
28
#include <asm/mach-types.h>
29
#include <plat/board-ams-delta.h>
30
31
#include <mach/ams-delta-fiq.h>
32
33
MODULE_AUTHOR("Matt Callow");
34
MODULE_DESCRIPTION("AMS Delta (E3) keyboard port driver");
35
MODULE_LICENSE("GPL");
36
37
static struct serio *ams_delta_serio;
38
39
static int check_data(int data)
40
{
41
int i, parity = 0;
42
43
/* check valid stop bit */
44
if (!(data & 0x400)) {
45
dev_warn(&ams_delta_serio->dev,
46
"invalid stop bit, data=0x%X\n",
47
data);
48
return SERIO_FRAME;
49
}
50
/* calculate the parity */
51
for (i = 1; i < 10; i++) {
52
if (data & (1 << i))
53
parity++;
54
}
55
/* it should be odd */
56
if (!(parity & 0x01)) {
57
dev_warn(&ams_delta_serio->dev,
58
"paritiy check failed, data=0x%X parity=0x%X\n",
59
data, parity);
60
return SERIO_PARITY;
61
}
62
return 0;
63
}
64
65
static irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id)
66
{
67
int *circ_buff = &fiq_buffer[FIQ_CIRC_BUFF];
68
int data, dfl;
69
u8 scancode;
70
71
fiq_buffer[FIQ_IRQ_PEND] = 0;
72
73
/*
74
* Read data from the circular buffer, check it
75
* and then pass it on the serio
76
*/
77
while (fiq_buffer[FIQ_KEYS_CNT] > 0) {
78
79
data = circ_buff[fiq_buffer[FIQ_HEAD_OFFSET]++];
80
fiq_buffer[FIQ_KEYS_CNT]--;
81
if (fiq_buffer[FIQ_HEAD_OFFSET] == fiq_buffer[FIQ_BUF_LEN])
82
fiq_buffer[FIQ_HEAD_OFFSET] = 0;
83
84
dfl = check_data(data);
85
scancode = (u8) (data >> 1) & 0xFF;
86
serio_interrupt(ams_delta_serio, scancode, dfl);
87
}
88
return IRQ_HANDLED;
89
}
90
91
static int ams_delta_serio_open(struct serio *serio)
92
{
93
/* enable keyboard */
94
ams_delta_latch2_write(AMD_DELTA_LATCH2_KEYBRD_PWR,
95
AMD_DELTA_LATCH2_KEYBRD_PWR);
96
97
return 0;
98
}
99
100
static void ams_delta_serio_close(struct serio *serio)
101
{
102
/* disable keyboard */
103
ams_delta_latch2_write(AMD_DELTA_LATCH2_KEYBRD_PWR, 0);
104
}
105
106
static int __init ams_delta_serio_init(void)
107
{
108
int err;
109
110
if (!machine_is_ams_delta())
111
return -ENODEV;
112
113
ams_delta_serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
114
if (!ams_delta_serio)
115
return -ENOMEM;
116
117
ams_delta_serio->id.type = SERIO_8042;
118
ams_delta_serio->open = ams_delta_serio_open;
119
ams_delta_serio->close = ams_delta_serio_close;
120
strlcpy(ams_delta_serio->name, "AMS DELTA keyboard adapter",
121
sizeof(ams_delta_serio->name));
122
strlcpy(ams_delta_serio->phys, "GPIO/serio0",
123
sizeof(ams_delta_serio->phys));
124
125
err = gpio_request(AMS_DELTA_GPIO_PIN_KEYBRD_DATA, "serio-data");
126
if (err) {
127
pr_err("ams_delta_serio: Couldn't request gpio pin for data\n");
128
goto serio;
129
}
130
gpio_direction_input(AMS_DELTA_GPIO_PIN_KEYBRD_DATA);
131
132
err = gpio_request(AMS_DELTA_GPIO_PIN_KEYBRD_CLK, "serio-clock");
133
if (err) {
134
pr_err("ams_delta_serio: couldn't request gpio pin for clock\n");
135
goto gpio_data;
136
}
137
gpio_direction_input(AMS_DELTA_GPIO_PIN_KEYBRD_CLK);
138
139
err = request_irq(gpio_to_irq(AMS_DELTA_GPIO_PIN_KEYBRD_CLK),
140
ams_delta_serio_interrupt, IRQ_TYPE_EDGE_RISING,
141
"ams-delta-serio", 0);
142
if (err < 0) {
143
pr_err("ams_delta_serio: couldn't request gpio interrupt %d\n",
144
gpio_to_irq(AMS_DELTA_GPIO_PIN_KEYBRD_CLK));
145
goto gpio_clk;
146
}
147
/*
148
* Since GPIO register handling for keyboard clock pin is performed
149
* at FIQ level, switch back from edge to simple interrupt handler
150
* to avoid bad interaction.
151
*/
152
irq_set_handler(gpio_to_irq(AMS_DELTA_GPIO_PIN_KEYBRD_CLK),
153
handle_simple_irq);
154
155
serio_register_port(ams_delta_serio);
156
dev_info(&ams_delta_serio->dev, "%s\n", ams_delta_serio->name);
157
158
return 0;
159
gpio_clk:
160
gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_CLK);
161
gpio_data:
162
gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_DATA);
163
serio:
164
kfree(ams_delta_serio);
165
return err;
166
}
167
module_init(ams_delta_serio_init);
168
169
static void __exit ams_delta_serio_exit(void)
170
{
171
serio_unregister_port(ams_delta_serio);
172
free_irq(OMAP_GPIO_IRQ(AMS_DELTA_GPIO_PIN_KEYBRD_CLK), 0);
173
gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_CLK);
174
gpio_free(AMS_DELTA_GPIO_PIN_KEYBRD_DATA);
175
}
176
module_exit(ams_delta_serio_exit);
177
178