1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) IBM Corporation 2020 |
4 | */ |
5 | |
6 | #include <linux/i2c.h> |
7 | #include <linux/init.h> |
8 | #include <linux/input.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/limits.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/spinlock.h> |
14 | |
15 | #define DEVICE_NAME "ibm-panel" |
16 | #define PANEL_KEYCODES_COUNT 3 |
17 | |
18 | struct ibm_panel { |
19 | u8 idx; |
20 | u8 command[11]; |
21 | u32 keycodes[PANEL_KEYCODES_COUNT]; |
22 | spinlock_t lock; /* protects writes to idx and command */ |
23 | struct input_dev *input; |
24 | }; |
25 | |
26 | static u8 ibm_panel_calculate_checksum(struct ibm_panel *panel) |
27 | { |
28 | u8 chksum; |
29 | u16 sum = 0; |
30 | unsigned int i; |
31 | |
32 | for (i = 0; i < sizeof(panel->command) - 1; ++i) { |
33 | sum += panel->command[i]; |
34 | if (sum & 0xff00) { |
35 | sum &= 0xff; |
36 | sum++; |
37 | } |
38 | } |
39 | |
40 | chksum = sum & 0xff; |
41 | chksum = ~chksum; |
42 | chksum++; |
43 | |
44 | return chksum; |
45 | } |
46 | |
47 | static void ibm_panel_process_command(struct ibm_panel *panel) |
48 | { |
49 | u8 button; |
50 | u8 chksum; |
51 | |
52 | if (panel->command[0] != 0xff && panel->command[1] != 0xf0) { |
53 | dev_dbg(&panel->input->dev, "command invalid: %02x %02x\n" , |
54 | panel->command[0], panel->command[1]); |
55 | return; |
56 | } |
57 | |
58 | chksum = ibm_panel_calculate_checksum(panel); |
59 | if (chksum != panel->command[sizeof(panel->command) - 1]) { |
60 | dev_dbg(&panel->input->dev, |
61 | "command failed checksum: %u != %u\n" , chksum, |
62 | panel->command[sizeof(panel->command) - 1]); |
63 | return; |
64 | } |
65 | |
66 | button = panel->command[2] & 0xf; |
67 | if (button < PANEL_KEYCODES_COUNT) { |
68 | input_report_key(dev: panel->input, code: panel->keycodes[button], |
69 | value: !(panel->command[2] & 0x80)); |
70 | input_sync(dev: panel->input); |
71 | } else { |
72 | dev_dbg(&panel->input->dev, "unknown button %u\n" , |
73 | button); |
74 | } |
75 | } |
76 | |
77 | static int ibm_panel_i2c_slave_cb(struct i2c_client *client, |
78 | enum i2c_slave_event event, u8 *val) |
79 | { |
80 | unsigned long flags; |
81 | struct ibm_panel *panel = i2c_get_clientdata(client); |
82 | |
83 | dev_dbg(&panel->input->dev, "event: %u data: %02x\n" , event, *val); |
84 | |
85 | spin_lock_irqsave(&panel->lock, flags); |
86 | |
87 | switch (event) { |
88 | case I2C_SLAVE_STOP: |
89 | if (panel->idx == sizeof(panel->command)) |
90 | ibm_panel_process_command(panel); |
91 | else |
92 | dev_dbg(&panel->input->dev, |
93 | "command incorrect size %u\n" , panel->idx); |
94 | fallthrough; |
95 | case I2C_SLAVE_WRITE_REQUESTED: |
96 | panel->idx = 0; |
97 | break; |
98 | case I2C_SLAVE_WRITE_RECEIVED: |
99 | if (panel->idx < sizeof(panel->command)) |
100 | panel->command[panel->idx++] = *val; |
101 | else |
102 | /* |
103 | * The command is too long and therefore invalid, so set the index |
104 | * to it's largest possible value. When a STOP is finally received, |
105 | * the command will be rejected upon processing. |
106 | */ |
107 | panel->idx = U8_MAX; |
108 | break; |
109 | case I2C_SLAVE_READ_REQUESTED: |
110 | case I2C_SLAVE_READ_PROCESSED: |
111 | *val = 0xff; |
112 | break; |
113 | default: |
114 | break; |
115 | } |
116 | |
117 | spin_unlock_irqrestore(lock: &panel->lock, flags); |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static int ibm_panel_probe(struct i2c_client *client) |
123 | { |
124 | struct ibm_panel *panel; |
125 | int i; |
126 | int error; |
127 | |
128 | panel = devm_kzalloc(dev: &client->dev, size: sizeof(*panel), GFP_KERNEL); |
129 | if (!panel) |
130 | return -ENOMEM; |
131 | |
132 | spin_lock_init(&panel->lock); |
133 | |
134 | panel->input = devm_input_allocate_device(&client->dev); |
135 | if (!panel->input) |
136 | return -ENOMEM; |
137 | |
138 | panel->input->name = client->name; |
139 | panel->input->id.bustype = BUS_I2C; |
140 | |
141 | error = device_property_read_u32_array(dev: &client->dev, |
142 | propname: "linux,keycodes" , |
143 | val: panel->keycodes, |
144 | PANEL_KEYCODES_COUNT); |
145 | if (error) { |
146 | /* |
147 | * Use gamepad buttons as defaults for compatibility with |
148 | * existing applications. |
149 | */ |
150 | panel->keycodes[0] = BTN_NORTH; |
151 | panel->keycodes[1] = BTN_SOUTH; |
152 | panel->keycodes[2] = BTN_SELECT; |
153 | } |
154 | |
155 | for (i = 0; i < PANEL_KEYCODES_COUNT; ++i) |
156 | input_set_capability(dev: panel->input, EV_KEY, code: panel->keycodes[i]); |
157 | |
158 | error = input_register_device(panel->input); |
159 | if (error) { |
160 | dev_err(&client->dev, |
161 | "Failed to register input device: %d\n" , error); |
162 | return error; |
163 | } |
164 | |
165 | i2c_set_clientdata(client, data: panel); |
166 | error = i2c_slave_register(client, slave_cb: ibm_panel_i2c_slave_cb); |
167 | if (error) { |
168 | dev_err(&client->dev, |
169 | "Failed to register as i2c slave: %d\n" , error); |
170 | return error; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static void ibm_panel_remove(struct i2c_client *client) |
177 | { |
178 | i2c_slave_unregister(client); |
179 | } |
180 | |
181 | static const struct of_device_id ibm_panel_match[] = { |
182 | { .compatible = "ibm,op-panel" }, |
183 | { } |
184 | }; |
185 | MODULE_DEVICE_TABLE(of, ibm_panel_match); |
186 | |
187 | static struct i2c_driver ibm_panel_driver = { |
188 | .driver = { |
189 | .name = DEVICE_NAME, |
190 | .of_match_table = ibm_panel_match, |
191 | }, |
192 | .probe = ibm_panel_probe, |
193 | .remove = ibm_panel_remove, |
194 | }; |
195 | module_i2c_driver(ibm_panel_driver); |
196 | |
197 | MODULE_AUTHOR("Eddie James <eajames@linux.ibm.com>" ); |
198 | MODULE_DESCRIPTION("IBM Operation Panel Driver" ); |
199 | MODULE_LICENSE("GPL" ); |
200 | |