1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | |
3 | /* |
4 | * Support for EC-connected GPIOs for identify |
5 | * LED/button on Barco P50 board |
6 | * |
7 | * Copyright (C) 2021 Barco NV |
8 | * Author: Santosh Kumar Yadav <santoshkumar.yadav@barco.com> |
9 | */ |
10 | |
11 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
12 | |
13 | #include <linux/delay.h> |
14 | #include <linux/dmi.h> |
15 | #include <linux/err.h> |
16 | #include <linux/io.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/leds.h> |
19 | #include <linux/module.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/gpio_keys.h> |
22 | #include <linux/gpio/driver.h> |
23 | #include <linux/gpio/machine.h> |
24 | #include <linux/input.h> |
25 | |
26 | |
27 | #define DRIVER_NAME "barco-p50-gpio" |
28 | |
29 | /* GPIO lines */ |
30 | #define P50_GPIO_LINE_LED 0 |
31 | #define P50_GPIO_LINE_BTN 1 |
32 | |
33 | /* GPIO IO Ports */ |
34 | #define P50_GPIO_IO_PORT_BASE 0x299 |
35 | |
36 | #define P50_PORT_DATA 0x00 |
37 | #define P50_PORT_CMD 0x01 |
38 | |
39 | #define P50_STATUS_OBF 0x01 /* EC output buffer full */ |
40 | #define P50_STATUS_IBF 0x02 /* EC input buffer full */ |
41 | |
42 | #define P50_CMD_READ 0xa0 |
43 | #define P50_CMD_WRITE 0x50 |
44 | |
45 | /* EC mailbox registers */ |
46 | #define P50_MBOX_REG_CMD 0x00 |
47 | #define P50_MBOX_REG_STATUS 0x01 |
48 | #define P50_MBOX_REG_PARAM 0x02 |
49 | #define P50_MBOX_REG_DATA 0x03 |
50 | |
51 | #define P50_MBOX_CMD_READ_GPIO 0x11 |
52 | #define P50_MBOX_CMD_WRITE_GPIO 0x12 |
53 | #define P50_MBOX_CMD_CLEAR 0xff |
54 | |
55 | #define P50_MBOX_STATUS_SUCCESS 0x01 |
56 | |
57 | #define P50_MBOX_PARAM_LED 0x12 |
58 | #define P50_MBOX_PARAM_BTN 0x13 |
59 | |
60 | |
61 | struct p50_gpio { |
62 | struct gpio_chip gc; |
63 | struct mutex lock; |
64 | unsigned long base; |
65 | struct platform_device *leds_pdev; |
66 | struct platform_device *keys_pdev; |
67 | }; |
68 | |
69 | static struct platform_device *gpio_pdev; |
70 | |
71 | static int gpio_params[] = { |
72 | [P50_GPIO_LINE_LED] = P50_MBOX_PARAM_LED, |
73 | [P50_GPIO_LINE_BTN] = P50_MBOX_PARAM_BTN, |
74 | }; |
75 | |
76 | static const char * const gpio_names[] = { |
77 | [P50_GPIO_LINE_LED] = "identify-led" , |
78 | [P50_GPIO_LINE_BTN] = "identify-button" , |
79 | }; |
80 | |
81 | |
82 | static struct gpiod_lookup_table p50_gpio_led_table = { |
83 | .dev_id = "leds-gpio" , |
84 | .table = { |
85 | GPIO_LOOKUP_IDX(DRIVER_NAME, P50_GPIO_LINE_LED, NULL, 0, GPIO_ACTIVE_HIGH), |
86 | {} |
87 | } |
88 | }; |
89 | |
90 | /* GPIO LEDs */ |
91 | static struct gpio_led leds[] = { |
92 | { .name = "identify" } |
93 | }; |
94 | |
95 | static struct gpio_led_platform_data leds_pdata = { |
96 | .num_leds = ARRAY_SIZE(leds), |
97 | .leds = leds, |
98 | }; |
99 | |
100 | /* GPIO keyboard */ |
101 | static struct gpio_keys_button buttons[] = { |
102 | { |
103 | .code = KEY_VENDOR, |
104 | .gpio = P50_GPIO_LINE_BTN, |
105 | .active_low = 1, |
106 | .type = EV_KEY, |
107 | .value = 1, |
108 | }, |
109 | }; |
110 | |
111 | static struct gpio_keys_platform_data keys_pdata = { |
112 | .buttons = buttons, |
113 | .nbuttons = ARRAY_SIZE(buttons), |
114 | .poll_interval = 100, |
115 | .rep = 0, |
116 | .name = "identify" , |
117 | }; |
118 | |
119 | |
120 | /* low level access routines */ |
121 | |
122 | static int p50_wait_ec(struct p50_gpio *p50, int mask, int expected) |
123 | { |
124 | int i, val; |
125 | |
126 | for (i = 0; i < 100; i++) { |
127 | val = inb(port: p50->base + P50_PORT_CMD) & mask; |
128 | if (val == expected) |
129 | return 0; |
130 | usleep_range(min: 500, max: 2000); |
131 | } |
132 | |
133 | dev_err(p50->gc.parent, "Timed out waiting for EC (0x%x)\n" , val); |
134 | return -ETIMEDOUT; |
135 | } |
136 | |
137 | |
138 | static int p50_read_mbox_reg(struct p50_gpio *p50, int reg) |
139 | { |
140 | int ret; |
141 | |
142 | ret = p50_wait_ec(p50, P50_STATUS_IBF, expected: 0); |
143 | if (ret) |
144 | return ret; |
145 | |
146 | /* clear output buffer flag, prevent unfinished commands */ |
147 | inb(port: p50->base + P50_PORT_DATA); |
148 | |
149 | /* cmd/address */ |
150 | outb(P50_CMD_READ | reg, port: p50->base + P50_PORT_CMD); |
151 | |
152 | ret = p50_wait_ec(p50, P50_STATUS_OBF, P50_STATUS_OBF); |
153 | if (ret) |
154 | return ret; |
155 | |
156 | return inb(port: p50->base + P50_PORT_DATA); |
157 | } |
158 | |
159 | static int p50_write_mbox_reg(struct p50_gpio *p50, int reg, int val) |
160 | { |
161 | int ret; |
162 | |
163 | ret = p50_wait_ec(p50, P50_STATUS_IBF, expected: 0); |
164 | if (ret) |
165 | return ret; |
166 | |
167 | /* cmd/address */ |
168 | outb(P50_CMD_WRITE | reg, port: p50->base + P50_PORT_CMD); |
169 | |
170 | ret = p50_wait_ec(p50, P50_STATUS_IBF, expected: 0); |
171 | if (ret) |
172 | return ret; |
173 | |
174 | /* data */ |
175 | outb(value: val, port: p50->base + P50_PORT_DATA); |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | |
181 | /* mbox routines */ |
182 | |
183 | static int p50_wait_mbox_idle(struct p50_gpio *p50) |
184 | { |
185 | int i, val; |
186 | |
187 | for (i = 0; i < 1000; i++) { |
188 | val = p50_read_mbox_reg(p50, P50_MBOX_REG_CMD); |
189 | /* cmd is 0 when idle */ |
190 | if (val <= 0) |
191 | return val; |
192 | |
193 | usleep_range(min: 500, max: 2000); |
194 | } |
195 | |
196 | dev_err(p50->gc.parent, "Timed out waiting for EC mbox idle (CMD: 0x%x)\n" , val); |
197 | |
198 | return -ETIMEDOUT; |
199 | } |
200 | |
201 | static int p50_send_mbox_cmd(struct p50_gpio *p50, int cmd, int param, int data) |
202 | { |
203 | int ret; |
204 | |
205 | ret = p50_wait_mbox_idle(p50); |
206 | if (ret) |
207 | return ret; |
208 | |
209 | ret = p50_write_mbox_reg(p50, P50_MBOX_REG_DATA, val: data); |
210 | if (ret) |
211 | return ret; |
212 | |
213 | ret = p50_write_mbox_reg(p50, P50_MBOX_REG_PARAM, val: param); |
214 | if (ret) |
215 | return ret; |
216 | |
217 | ret = p50_write_mbox_reg(p50, P50_MBOX_REG_CMD, val: cmd); |
218 | if (ret) |
219 | return ret; |
220 | |
221 | ret = p50_wait_mbox_idle(p50); |
222 | if (ret) |
223 | return ret; |
224 | |
225 | ret = p50_read_mbox_reg(p50, P50_MBOX_REG_STATUS); |
226 | if (ret < 0) |
227 | return ret; |
228 | |
229 | if (ret == P50_MBOX_STATUS_SUCCESS) |
230 | return 0; |
231 | |
232 | dev_err(p50->gc.parent, "Mbox command failed (CMD=0x%x STAT=0x%x PARAM=0x%x DATA=0x%x)\n" , |
233 | cmd, ret, param, data); |
234 | |
235 | return -EIO; |
236 | } |
237 | |
238 | |
239 | /* gpio routines */ |
240 | |
241 | static int p50_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) |
242 | { |
243 | switch (offset) { |
244 | case P50_GPIO_LINE_BTN: |
245 | return GPIO_LINE_DIRECTION_IN; |
246 | |
247 | case P50_GPIO_LINE_LED: |
248 | return GPIO_LINE_DIRECTION_OUT; |
249 | |
250 | default: |
251 | return -EINVAL; |
252 | } |
253 | } |
254 | |
255 | static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset) |
256 | { |
257 | struct p50_gpio *p50 = gpiochip_get_data(gc); |
258 | int ret; |
259 | |
260 | mutex_lock(&p50->lock); |
261 | |
262 | ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_READ_GPIO, param: gpio_params[offset], data: 0); |
263 | if (ret == 0) |
264 | ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA); |
265 | |
266 | mutex_unlock(lock: &p50->lock); |
267 | |
268 | return ret; |
269 | } |
270 | |
271 | static void p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) |
272 | { |
273 | struct p50_gpio *p50 = gpiochip_get_data(gc); |
274 | |
275 | mutex_lock(&p50->lock); |
276 | |
277 | p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, param: gpio_params[offset], data: value); |
278 | |
279 | mutex_unlock(lock: &p50->lock); |
280 | } |
281 | |
282 | static int p50_gpio_probe(struct platform_device *pdev) |
283 | { |
284 | struct p50_gpio *p50; |
285 | struct resource *res; |
286 | int ret; |
287 | |
288 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); |
289 | if (!res) { |
290 | dev_err(&pdev->dev, "Cannot get I/O ports\n" ); |
291 | return -ENODEV; |
292 | } |
293 | |
294 | if (!devm_request_region(&pdev->dev, res->start, resource_size(res), pdev->name)) { |
295 | dev_err(&pdev->dev, "Unable to reserve I/O region\n" ); |
296 | return -EBUSY; |
297 | } |
298 | |
299 | p50 = devm_kzalloc(dev: &pdev->dev, size: sizeof(*p50), GFP_KERNEL); |
300 | if (!p50) |
301 | return -ENOMEM; |
302 | |
303 | platform_set_drvdata(pdev, data: p50); |
304 | mutex_init(&p50->lock); |
305 | p50->base = res->start; |
306 | p50->gc.owner = THIS_MODULE; |
307 | p50->gc.parent = &pdev->dev; |
308 | p50->gc.label = dev_name(dev: &pdev->dev); |
309 | p50->gc.ngpio = ARRAY_SIZE(gpio_names); |
310 | p50->gc.names = gpio_names; |
311 | p50->gc.can_sleep = true; |
312 | p50->gc.base = -1; |
313 | p50->gc.get_direction = p50_gpio_get_direction; |
314 | p50->gc.get = p50_gpio_get; |
315 | p50->gc.set = p50_gpio_set; |
316 | |
317 | |
318 | /* reset mbox */ |
319 | ret = p50_wait_mbox_idle(p50); |
320 | if (ret) |
321 | return ret; |
322 | |
323 | ret = p50_write_mbox_reg(p50, P50_MBOX_REG_CMD, P50_MBOX_CMD_CLEAR); |
324 | if (ret) |
325 | return ret; |
326 | |
327 | ret = p50_wait_mbox_idle(p50); |
328 | if (ret) |
329 | return ret; |
330 | |
331 | |
332 | ret = devm_gpiochip_add_data(&pdev->dev, &p50->gc, p50); |
333 | if (ret < 0) { |
334 | dev_err(&pdev->dev, "Could not register gpiochip: %d\n" , ret); |
335 | return ret; |
336 | } |
337 | |
338 | gpiod_add_lookup_table(table: &p50_gpio_led_table); |
339 | |
340 | p50->leds_pdev = platform_device_register_data(parent: &pdev->dev, |
341 | name: "leds-gpio" , PLATFORM_DEVID_NONE, data: &leds_pdata, size: sizeof(leds_pdata)); |
342 | |
343 | if (IS_ERR(ptr: p50->leds_pdev)) { |
344 | ret = PTR_ERR(ptr: p50->leds_pdev); |
345 | dev_err(&pdev->dev, "Could not register leds-gpio: %d\n" , ret); |
346 | goto err_leds; |
347 | } |
348 | |
349 | /* gpio-keys-polled uses old-style gpio interface, pass the right identifier */ |
350 | buttons[0].gpio += p50->gc.base; |
351 | |
352 | p50->keys_pdev = |
353 | platform_device_register_data(parent: &pdev->dev, name: "gpio-keys-polled" , |
354 | PLATFORM_DEVID_NONE, |
355 | data: &keys_pdata, size: sizeof(keys_pdata)); |
356 | |
357 | if (IS_ERR(ptr: p50->keys_pdev)) { |
358 | ret = PTR_ERR(ptr: p50->keys_pdev); |
359 | dev_err(&pdev->dev, "Could not register gpio-keys-polled: %d\n" , ret); |
360 | goto err_keys; |
361 | } |
362 | |
363 | return 0; |
364 | |
365 | err_keys: |
366 | platform_device_unregister(p50->leds_pdev); |
367 | err_leds: |
368 | gpiod_remove_lookup_table(table: &p50_gpio_led_table); |
369 | |
370 | return ret; |
371 | } |
372 | |
373 | static void p50_gpio_remove(struct platform_device *pdev) |
374 | { |
375 | struct p50_gpio *p50 = platform_get_drvdata(pdev); |
376 | |
377 | platform_device_unregister(p50->keys_pdev); |
378 | platform_device_unregister(p50->leds_pdev); |
379 | |
380 | gpiod_remove_lookup_table(table: &p50_gpio_led_table); |
381 | } |
382 | |
383 | static struct platform_driver p50_gpio_driver = { |
384 | .driver = { |
385 | .name = DRIVER_NAME, |
386 | }, |
387 | .probe = p50_gpio_probe, |
388 | .remove_new = p50_gpio_remove, |
389 | }; |
390 | |
391 | /* Board setup */ |
392 | static const struct dmi_system_id dmi_ids[] __initconst = { |
393 | { |
394 | .matches = { |
395 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Barco" ), |
396 | DMI_EXACT_MATCH(DMI_PRODUCT_FAMILY, "P50" ) |
397 | }, |
398 | }, |
399 | {} |
400 | }; |
401 | MODULE_DEVICE_TABLE(dmi, dmi_ids); |
402 | |
403 | static int __init p50_module_init(void) |
404 | { |
405 | struct resource res = DEFINE_RES_IO(P50_GPIO_IO_PORT_BASE, P50_PORT_CMD + 1); |
406 | int ret; |
407 | |
408 | if (!dmi_first_match(list: dmi_ids)) |
409 | return -ENODEV; |
410 | |
411 | ret = platform_driver_register(&p50_gpio_driver); |
412 | if (ret) |
413 | return ret; |
414 | |
415 | gpio_pdev = platform_device_register_simple(DRIVER_NAME, PLATFORM_DEVID_NONE, res: &res, num: 1); |
416 | if (IS_ERR(ptr: gpio_pdev)) { |
417 | pr_err("failed registering %s: %ld\n" , DRIVER_NAME, PTR_ERR(gpio_pdev)); |
418 | platform_driver_unregister(&p50_gpio_driver); |
419 | return PTR_ERR(ptr: gpio_pdev); |
420 | } |
421 | |
422 | return 0; |
423 | } |
424 | |
425 | static void __exit p50_module_exit(void) |
426 | { |
427 | platform_device_unregister(gpio_pdev); |
428 | platform_driver_unregister(&p50_gpio_driver); |
429 | } |
430 | |
431 | module_init(p50_module_init); |
432 | module_exit(p50_module_exit); |
433 | |
434 | MODULE_AUTHOR("Santosh Kumar Yadav, Barco NV <santoshkumar.yadav@barco.com>" ); |
435 | MODULE_DESCRIPTION("Barco P50 identify GPIOs driver" ); |
436 | MODULE_LICENSE("GPL" ); |
437 | |