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