Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | // SPDX-License-Identifier: GPL-2.0 // // Driver for the Winmate FM07 front-panel keys // // Author: Daniel Beer <daniel.beer@tirotech.co.nz> #include <linux/init.h> #include <linux/module.h> #include <linux/input.h> #include <linux/ioport.h> #include <linux/platform_device.h> #include <linux/dmi.h> #include <linux/io.h> #define DRV_NAME "winmate-fm07keys" #define PORT_CMD 0x6c #define PORT_DATA 0x68 #define EC_ADDR_KEYS 0x3b #define EC_CMD_READ 0x80 #define BASE_KEY KEY_F13 #define NUM_KEYS 5 /* Typically we're done in fewer than 10 iterations */ #define LOOP_TIMEOUT 1000 static void fm07keys_poll(struct input_dev *input) { uint8_t k; int i; /* Flush output buffer */ i = 0; while (inb(PORT_CMD) & 0x01) { if (++i >= LOOP_TIMEOUT) goto timeout; inb(PORT_DATA); } /* Send request and wait for write completion */ outb(EC_CMD_READ, PORT_CMD); i = 0; while (inb(PORT_CMD) & 0x02) if (++i >= LOOP_TIMEOUT) goto timeout; outb(EC_ADDR_KEYS, PORT_DATA); i = 0; while (inb(PORT_CMD) & 0x02) if (++i >= LOOP_TIMEOUT) goto timeout; /* Wait for data ready */ i = 0; while (!(inb(PORT_CMD) & 0x01)) if (++i >= LOOP_TIMEOUT) goto timeout; k = inb(PORT_DATA); /* Notify of new key states */ for (i = 0; i < NUM_KEYS; i++) { input_report_key(input, BASE_KEY + i, (~k) & 1); k >>= 1; } input_sync(input); return; timeout: dev_warn_ratelimited(&input->dev, "timeout polling IO memory\n"); } static int fm07keys_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct input_dev *input; int ret; int i; input = devm_input_allocate_device(dev); if (!input) { dev_err(dev, "no memory for input device\n"); return -ENOMEM; } if (!devm_request_region(dev, PORT_CMD, 1, "Winmate FM07 EC")) return -EBUSY; if (!devm_request_region(dev, PORT_DATA, 1, "Winmate FM07 EC")) return -EBUSY; input->name = "Winmate FM07 front-panel keys"; input->phys = DRV_NAME "/input0"; input->id.bustype = BUS_HOST; input->id.vendor = 0x0001; input->id.product = 0x0001; input->id.version = 0x0100; __set_bit(EV_KEY, input->evbit); for (i = 0; i < NUM_KEYS; i++) __set_bit(BASE_KEY + i, input->keybit); ret = input_setup_polling(input, fm07keys_poll); if (ret) { dev_err(dev, "unable to set up polling, err=%d\n", ret); return ret; } /* These are silicone buttons. They can't be pressed in rapid * succession too quickly, and 50 Hz seems to be an adequate * sampling rate without missing any events when tested. */ input_set_poll_interval(input, 20); ret = input_register_device(input); if (ret) { dev_err(dev, "unable to register polled device, err=%d\n", ret); return ret; } input_sync(input); return 0; } static struct platform_driver fm07keys_driver = { .probe = fm07keys_probe, .driver = { .name = DRV_NAME }, }; static struct platform_device *dev; static const struct dmi_system_id fm07keys_dmi_table[] __initconst = { { /* FM07 and FM07P */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Winmate Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "IP30"), }, }, { } }; MODULE_DEVICE_TABLE(dmi, fm07keys_dmi_table); static int __init fm07keys_init(void) { int ret; if (!dmi_check_system(fm07keys_dmi_table)) return -ENODEV; ret = platform_driver_register(&fm07keys_driver); if (ret) { pr_err("fm07keys: failed to register driver, err=%d\n", ret); return ret; } dev = platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, 0); if (IS_ERR(dev)) { ret = PTR_ERR(dev); pr_err("fm07keys: failed to allocate device, err = %d\n", ret); goto fail_register; } return 0; fail_register: platform_driver_unregister(&fm07keys_driver); return ret; } static void __exit fm07keys_exit(void) { platform_driver_unregister(&fm07keys_driver); platform_device_unregister(dev); } module_init(fm07keys_init); module_exit(fm07keys_exit); MODULE_AUTHOR("Daniel Beer <daniel.beer@tirotech.co.nz>"); MODULE_DESCRIPTION("Winmate FM07 front-panel keys driver"); MODULE_LICENSE("GPL"); |