162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci// 362306a36Sopenharmony_ci// Driver for the Winmate FM07 front-panel keys 462306a36Sopenharmony_ci// 562306a36Sopenharmony_ci// Author: Daniel Beer <daniel.beer@tirotech.co.nz> 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/init.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/input.h> 1062306a36Sopenharmony_ci#include <linux/ioport.h> 1162306a36Sopenharmony_ci#include <linux/platform_device.h> 1262306a36Sopenharmony_ci#include <linux/dmi.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#define DRV_NAME "winmate-fm07keys" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define PORT_CMD 0x6c 1862306a36Sopenharmony_ci#define PORT_DATA 0x68 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define EC_ADDR_KEYS 0x3b 2162306a36Sopenharmony_ci#define EC_CMD_READ 0x80 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define BASE_KEY KEY_F13 2462306a36Sopenharmony_ci#define NUM_KEYS 5 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci/* Typically we're done in fewer than 10 iterations */ 2762306a36Sopenharmony_ci#define LOOP_TIMEOUT 1000 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic void fm07keys_poll(struct input_dev *input) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci uint8_t k; 3262306a36Sopenharmony_ci int i; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* Flush output buffer */ 3562306a36Sopenharmony_ci i = 0; 3662306a36Sopenharmony_ci while (inb(PORT_CMD) & 0x01) { 3762306a36Sopenharmony_ci if (++i >= LOOP_TIMEOUT) 3862306a36Sopenharmony_ci goto timeout; 3962306a36Sopenharmony_ci inb(PORT_DATA); 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* Send request and wait for write completion */ 4362306a36Sopenharmony_ci outb(EC_CMD_READ, PORT_CMD); 4462306a36Sopenharmony_ci i = 0; 4562306a36Sopenharmony_ci while (inb(PORT_CMD) & 0x02) 4662306a36Sopenharmony_ci if (++i >= LOOP_TIMEOUT) 4762306a36Sopenharmony_ci goto timeout; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci outb(EC_ADDR_KEYS, PORT_DATA); 5062306a36Sopenharmony_ci i = 0; 5162306a36Sopenharmony_ci while (inb(PORT_CMD) & 0x02) 5262306a36Sopenharmony_ci if (++i >= LOOP_TIMEOUT) 5362306a36Sopenharmony_ci goto timeout; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* Wait for data ready */ 5662306a36Sopenharmony_ci i = 0; 5762306a36Sopenharmony_ci while (!(inb(PORT_CMD) & 0x01)) 5862306a36Sopenharmony_ci if (++i >= LOOP_TIMEOUT) 5962306a36Sopenharmony_ci goto timeout; 6062306a36Sopenharmony_ci k = inb(PORT_DATA); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* Notify of new key states */ 6362306a36Sopenharmony_ci for (i = 0; i < NUM_KEYS; i++) { 6462306a36Sopenharmony_ci input_report_key(input, BASE_KEY + i, (~k) & 1); 6562306a36Sopenharmony_ci k >>= 1; 6662306a36Sopenharmony_ci } 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci input_sync(input); 6962306a36Sopenharmony_ci return; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_citimeout: 7262306a36Sopenharmony_ci dev_warn_ratelimited(&input->dev, "timeout polling IO memory\n"); 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int fm07keys_probe(struct platform_device *pdev) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct device *dev = &pdev->dev; 7862306a36Sopenharmony_ci struct input_dev *input; 7962306a36Sopenharmony_ci int ret; 8062306a36Sopenharmony_ci int i; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci input = devm_input_allocate_device(dev); 8362306a36Sopenharmony_ci if (!input) { 8462306a36Sopenharmony_ci dev_err(dev, "no memory for input device\n"); 8562306a36Sopenharmony_ci return -ENOMEM; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (!devm_request_region(dev, PORT_CMD, 1, "Winmate FM07 EC")) 8962306a36Sopenharmony_ci return -EBUSY; 9062306a36Sopenharmony_ci if (!devm_request_region(dev, PORT_DATA, 1, "Winmate FM07 EC")) 9162306a36Sopenharmony_ci return -EBUSY; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci input->name = "Winmate FM07 front-panel keys"; 9462306a36Sopenharmony_ci input->phys = DRV_NAME "/input0"; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci input->id.bustype = BUS_HOST; 9762306a36Sopenharmony_ci input->id.vendor = 0x0001; 9862306a36Sopenharmony_ci input->id.product = 0x0001; 9962306a36Sopenharmony_ci input->id.version = 0x0100; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci __set_bit(EV_KEY, input->evbit); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci for (i = 0; i < NUM_KEYS; i++) 10462306a36Sopenharmony_ci __set_bit(BASE_KEY + i, input->keybit); 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci ret = input_setup_polling(input, fm07keys_poll); 10762306a36Sopenharmony_ci if (ret) { 10862306a36Sopenharmony_ci dev_err(dev, "unable to set up polling, err=%d\n", ret); 10962306a36Sopenharmony_ci return ret; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* These are silicone buttons. They can't be pressed in rapid 11362306a36Sopenharmony_ci * succession too quickly, and 50 Hz seems to be an adequate 11462306a36Sopenharmony_ci * sampling rate without missing any events when tested. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ci input_set_poll_interval(input, 20); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci ret = input_register_device(input); 11962306a36Sopenharmony_ci if (ret) { 12062306a36Sopenharmony_ci dev_err(dev, "unable to register polled device, err=%d\n", 12162306a36Sopenharmony_ci ret); 12262306a36Sopenharmony_ci return ret; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci input_sync(input); 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic struct platform_driver fm07keys_driver = { 13062306a36Sopenharmony_ci .probe = fm07keys_probe, 13162306a36Sopenharmony_ci .driver = { 13262306a36Sopenharmony_ci .name = DRV_NAME 13362306a36Sopenharmony_ci }, 13462306a36Sopenharmony_ci}; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic struct platform_device *dev; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic const struct dmi_system_id fm07keys_dmi_table[] __initconst = { 13962306a36Sopenharmony_ci { 14062306a36Sopenharmony_ci /* FM07 and FM07P */ 14162306a36Sopenharmony_ci .matches = { 14262306a36Sopenharmony_ci DMI_MATCH(DMI_SYS_VENDOR, "Winmate Inc."), 14362306a36Sopenharmony_ci DMI_MATCH(DMI_PRODUCT_NAME, "IP30"), 14462306a36Sopenharmony_ci }, 14562306a36Sopenharmony_ci }, 14662306a36Sopenharmony_ci { } 14762306a36Sopenharmony_ci}; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, fm07keys_dmi_table); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_cistatic int __init fm07keys_init(void) 15262306a36Sopenharmony_ci{ 15362306a36Sopenharmony_ci int ret; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (!dmi_check_system(fm07keys_dmi_table)) 15662306a36Sopenharmony_ci return -ENODEV; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci ret = platform_driver_register(&fm07keys_driver); 15962306a36Sopenharmony_ci if (ret) { 16062306a36Sopenharmony_ci pr_err("fm07keys: failed to register driver, err=%d\n", ret); 16162306a36Sopenharmony_ci return ret; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci dev = platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, 0); 16562306a36Sopenharmony_ci if (IS_ERR(dev)) { 16662306a36Sopenharmony_ci ret = PTR_ERR(dev); 16762306a36Sopenharmony_ci pr_err("fm07keys: failed to allocate device, err = %d\n", ret); 16862306a36Sopenharmony_ci goto fail_register; 16962306a36Sopenharmony_ci } 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_cifail_register: 17462306a36Sopenharmony_ci platform_driver_unregister(&fm07keys_driver); 17562306a36Sopenharmony_ci return ret; 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_cistatic void __exit fm07keys_exit(void) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci platform_driver_unregister(&fm07keys_driver); 18162306a36Sopenharmony_ci platform_device_unregister(dev); 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cimodule_init(fm07keys_init); 18562306a36Sopenharmony_cimodule_exit(fm07keys_exit); 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ciMODULE_AUTHOR("Daniel Beer <daniel.beer@tirotech.co.nz>"); 18862306a36Sopenharmony_ciMODULE_DESCRIPTION("Winmate FM07 front-panel keys driver"); 18962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 190