18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Driver for simulating a mouse on GPIO lines. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2007 Atmel Corporation 68c2ecf20Sopenharmony_ci * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 118c2ecf20Sopenharmony_ci#include <linux/input.h> 128c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 138c2ecf20Sopenharmony_ci#include <linux/property.h> 148c2ecf20Sopenharmony_ci#include <linux/of.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci/** 178c2ecf20Sopenharmony_ci * struct gpio_mouse 188c2ecf20Sopenharmony_ci * @scan_ms: the scan interval in milliseconds. 198c2ecf20Sopenharmony_ci * @up: GPIO line for up value. 208c2ecf20Sopenharmony_ci * @down: GPIO line for down value. 218c2ecf20Sopenharmony_ci * @left: GPIO line for left value. 228c2ecf20Sopenharmony_ci * @right: GPIO line for right value. 238c2ecf20Sopenharmony_ci * @bleft: GPIO line for left button. 248c2ecf20Sopenharmony_ci * @bmiddle: GPIO line for middle button. 258c2ecf20Sopenharmony_ci * @bright: GPIO line for right button. 268c2ecf20Sopenharmony_ci * 278c2ecf20Sopenharmony_ci * This struct must be added to the platform_device in the board code. 288c2ecf20Sopenharmony_ci * It is used by the gpio_mouse driver to setup GPIO lines and to 298c2ecf20Sopenharmony_ci * calculate mouse movement. 308c2ecf20Sopenharmony_ci */ 318c2ecf20Sopenharmony_cistruct gpio_mouse { 328c2ecf20Sopenharmony_ci u32 scan_ms; 338c2ecf20Sopenharmony_ci struct gpio_desc *up; 348c2ecf20Sopenharmony_ci struct gpio_desc *down; 358c2ecf20Sopenharmony_ci struct gpio_desc *left; 368c2ecf20Sopenharmony_ci struct gpio_desc *right; 378c2ecf20Sopenharmony_ci struct gpio_desc *bleft; 388c2ecf20Sopenharmony_ci struct gpio_desc *bmiddle; 398c2ecf20Sopenharmony_ci struct gpio_desc *bright; 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci/* 438c2ecf20Sopenharmony_ci * Timer function which is run every scan_ms ms when the device is opened. 448c2ecf20Sopenharmony_ci * The dev input variable is set to the the input_dev pointer. 458c2ecf20Sopenharmony_ci */ 468c2ecf20Sopenharmony_cistatic void gpio_mouse_scan(struct input_dev *input) 478c2ecf20Sopenharmony_ci{ 488c2ecf20Sopenharmony_ci struct gpio_mouse *gpio = input_get_drvdata(input); 498c2ecf20Sopenharmony_ci int x, y; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (gpio->bleft) 528c2ecf20Sopenharmony_ci input_report_key(input, BTN_LEFT, 538c2ecf20Sopenharmony_ci gpiod_get_value(gpio->bleft)); 548c2ecf20Sopenharmony_ci if (gpio->bmiddle) 558c2ecf20Sopenharmony_ci input_report_key(input, BTN_MIDDLE, 568c2ecf20Sopenharmony_ci gpiod_get_value(gpio->bmiddle)); 578c2ecf20Sopenharmony_ci if (gpio->bright) 588c2ecf20Sopenharmony_ci input_report_key(input, BTN_RIGHT, 598c2ecf20Sopenharmony_ci gpiod_get_value(gpio->bright)); 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci x = gpiod_get_value(gpio->right) - gpiod_get_value(gpio->left); 628c2ecf20Sopenharmony_ci y = gpiod_get_value(gpio->down) - gpiod_get_value(gpio->up); 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci input_report_rel(input, REL_X, x); 658c2ecf20Sopenharmony_ci input_report_rel(input, REL_Y, y); 668c2ecf20Sopenharmony_ci input_sync(input); 678c2ecf20Sopenharmony_ci} 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_cistatic int gpio_mouse_probe(struct platform_device *pdev) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 728c2ecf20Sopenharmony_ci struct gpio_mouse *gmouse; 738c2ecf20Sopenharmony_ci struct input_dev *input; 748c2ecf20Sopenharmony_ci int error; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci gmouse = devm_kzalloc(dev, sizeof(*gmouse), GFP_KERNEL); 778c2ecf20Sopenharmony_ci if (!gmouse) 788c2ecf20Sopenharmony_ci return -ENOMEM; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* Assign some default scanning time */ 818c2ecf20Sopenharmony_ci error = device_property_read_u32(dev, "scan-interval-ms", 828c2ecf20Sopenharmony_ci &gmouse->scan_ms); 838c2ecf20Sopenharmony_ci if (error || gmouse->scan_ms == 0) { 848c2ecf20Sopenharmony_ci dev_warn(dev, "invalid scan time, set to 50 ms\n"); 858c2ecf20Sopenharmony_ci gmouse->scan_ms = 50; 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci gmouse->up = devm_gpiod_get(dev, "up", GPIOD_IN); 898c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->up)) 908c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->up); 918c2ecf20Sopenharmony_ci gmouse->down = devm_gpiod_get(dev, "down", GPIOD_IN); 928c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->down)) 938c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->down); 948c2ecf20Sopenharmony_ci gmouse->left = devm_gpiod_get(dev, "left", GPIOD_IN); 958c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->left)) 968c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->left); 978c2ecf20Sopenharmony_ci gmouse->right = devm_gpiod_get(dev, "right", GPIOD_IN); 988c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->right)) 998c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->right); 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci gmouse->bleft = devm_gpiod_get_optional(dev, "button-left", GPIOD_IN); 1028c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->bleft)) 1038c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->bleft); 1048c2ecf20Sopenharmony_ci gmouse->bmiddle = devm_gpiod_get_optional(dev, "button-middle", 1058c2ecf20Sopenharmony_ci GPIOD_IN); 1068c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->bmiddle)) 1078c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->bmiddle); 1088c2ecf20Sopenharmony_ci gmouse->bright = devm_gpiod_get_optional(dev, "button-right", 1098c2ecf20Sopenharmony_ci GPIOD_IN); 1108c2ecf20Sopenharmony_ci if (IS_ERR(gmouse->bright)) 1118c2ecf20Sopenharmony_ci return PTR_ERR(gmouse->bright); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci input = devm_input_allocate_device(dev); 1148c2ecf20Sopenharmony_ci if (!input) 1158c2ecf20Sopenharmony_ci return -ENOMEM; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci input->name = pdev->name; 1188c2ecf20Sopenharmony_ci input->id.bustype = BUS_HOST; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci input_set_drvdata(input, gmouse); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci input_set_capability(input, EV_REL, REL_X); 1238c2ecf20Sopenharmony_ci input_set_capability(input, EV_REL, REL_Y); 1248c2ecf20Sopenharmony_ci if (gmouse->bleft) 1258c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, BTN_LEFT); 1268c2ecf20Sopenharmony_ci if (gmouse->bmiddle) 1278c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, BTN_MIDDLE); 1288c2ecf20Sopenharmony_ci if (gmouse->bright) 1298c2ecf20Sopenharmony_ci input_set_capability(input, EV_KEY, BTN_RIGHT); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci error = input_setup_polling(input, gpio_mouse_scan); 1328c2ecf20Sopenharmony_ci if (error) 1338c2ecf20Sopenharmony_ci return error; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci input_set_poll_interval(input, gmouse->scan_ms); 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci error = input_register_device(input); 1388c2ecf20Sopenharmony_ci if (error) { 1398c2ecf20Sopenharmony_ci dev_err(dev, "could not register input device\n"); 1408c2ecf20Sopenharmony_ci return error; 1418c2ecf20Sopenharmony_ci } 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci dev_dbg(dev, "%d ms scan time, buttons: %s%s%s\n", 1448c2ecf20Sopenharmony_ci gmouse->scan_ms, 1458c2ecf20Sopenharmony_ci gmouse->bleft ? "" : "left ", 1468c2ecf20Sopenharmony_ci gmouse->bmiddle ? "" : "middle ", 1478c2ecf20Sopenharmony_ci gmouse->bright ? "" : "right"); 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci return 0; 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic const struct of_device_id gpio_mouse_of_match[] = { 1538c2ecf20Sopenharmony_ci { .compatible = "gpio-mouse", }, 1548c2ecf20Sopenharmony_ci { }, 1558c2ecf20Sopenharmony_ci}; 1568c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, gpio_mouse_of_match); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_cistatic struct platform_driver gpio_mouse_device_driver = { 1598c2ecf20Sopenharmony_ci .probe = gpio_mouse_probe, 1608c2ecf20Sopenharmony_ci .driver = { 1618c2ecf20Sopenharmony_ci .name = "gpio_mouse", 1628c2ecf20Sopenharmony_ci .of_match_table = gpio_mouse_of_match, 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci}; 1658c2ecf20Sopenharmony_cimodule_platform_driver(gpio_mouse_device_driver); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>"); 1688c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO mouse driver"); 1698c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1708c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:gpio_mouse"); /* work with hotplug and coldplug */ 171