134354476SDaniel Beer // SPDX-License-Identifier: GPL-2.0
234354476SDaniel Beer //
334354476SDaniel Beer // Driver for the Winmate FM07 front-panel keys
434354476SDaniel Beer //
534354476SDaniel Beer // Author: Daniel Beer <daniel.beer@tirotech.co.nz>
634354476SDaniel Beer
734354476SDaniel Beer #include <linux/init.h>
834354476SDaniel Beer #include <linux/module.h>
934354476SDaniel Beer #include <linux/input.h>
1034354476SDaniel Beer #include <linux/ioport.h>
1134354476SDaniel Beer #include <linux/platform_device.h>
1234354476SDaniel Beer #include <linux/dmi.h>
1334354476SDaniel Beer #include <linux/io.h>
1434354476SDaniel Beer
1534354476SDaniel Beer #define DRV_NAME "winmate-fm07keys"
1634354476SDaniel Beer
1734354476SDaniel Beer #define PORT_CMD 0x6c
1834354476SDaniel Beer #define PORT_DATA 0x68
1934354476SDaniel Beer
2034354476SDaniel Beer #define EC_ADDR_KEYS 0x3b
2134354476SDaniel Beer #define EC_CMD_READ 0x80
2234354476SDaniel Beer
2334354476SDaniel Beer #define BASE_KEY KEY_F13
2434354476SDaniel Beer #define NUM_KEYS 5
2534354476SDaniel Beer
2634354476SDaniel Beer /* Typically we're done in fewer than 10 iterations */
2734354476SDaniel Beer #define LOOP_TIMEOUT 1000
2834354476SDaniel Beer
fm07keys_poll(struct input_dev * input)2934354476SDaniel Beer static void fm07keys_poll(struct input_dev *input)
3034354476SDaniel Beer {
3134354476SDaniel Beer uint8_t k;
3234354476SDaniel Beer int i;
3334354476SDaniel Beer
3434354476SDaniel Beer /* Flush output buffer */
3534354476SDaniel Beer i = 0;
3634354476SDaniel Beer while (inb(PORT_CMD) & 0x01) {
3734354476SDaniel Beer if (++i >= LOOP_TIMEOUT)
3834354476SDaniel Beer goto timeout;
3934354476SDaniel Beer inb(PORT_DATA);
4034354476SDaniel Beer }
4134354476SDaniel Beer
4234354476SDaniel Beer /* Send request and wait for write completion */
4334354476SDaniel Beer outb(EC_CMD_READ, PORT_CMD);
4434354476SDaniel Beer i = 0;
4534354476SDaniel Beer while (inb(PORT_CMD) & 0x02)
4634354476SDaniel Beer if (++i >= LOOP_TIMEOUT)
4734354476SDaniel Beer goto timeout;
4834354476SDaniel Beer
4934354476SDaniel Beer outb(EC_ADDR_KEYS, PORT_DATA);
5034354476SDaniel Beer i = 0;
5134354476SDaniel Beer while (inb(PORT_CMD) & 0x02)
5234354476SDaniel Beer if (++i >= LOOP_TIMEOUT)
5334354476SDaniel Beer goto timeout;
5434354476SDaniel Beer
5534354476SDaniel Beer /* Wait for data ready */
5634354476SDaniel Beer i = 0;
5734354476SDaniel Beer while (!(inb(PORT_CMD) & 0x01))
5834354476SDaniel Beer if (++i >= LOOP_TIMEOUT)
5934354476SDaniel Beer goto timeout;
6034354476SDaniel Beer k = inb(PORT_DATA);
6134354476SDaniel Beer
6234354476SDaniel Beer /* Notify of new key states */
6334354476SDaniel Beer for (i = 0; i < NUM_KEYS; i++) {
6434354476SDaniel Beer input_report_key(input, BASE_KEY + i, (~k) & 1);
6534354476SDaniel Beer k >>= 1;
6634354476SDaniel Beer }
6734354476SDaniel Beer
6834354476SDaniel Beer input_sync(input);
6934354476SDaniel Beer return;
7034354476SDaniel Beer
7134354476SDaniel Beer timeout:
7234354476SDaniel Beer dev_warn_ratelimited(&input->dev, "timeout polling IO memory\n");
7334354476SDaniel Beer }
7434354476SDaniel Beer
fm07keys_probe(struct platform_device * pdev)7534354476SDaniel Beer static int fm07keys_probe(struct platform_device *pdev)
7634354476SDaniel Beer {
7734354476SDaniel Beer struct device *dev = &pdev->dev;
7834354476SDaniel Beer struct input_dev *input;
7934354476SDaniel Beer int ret;
8034354476SDaniel Beer int i;
8134354476SDaniel Beer
8234354476SDaniel Beer input = devm_input_allocate_device(dev);
8334354476SDaniel Beer if (!input) {
8434354476SDaniel Beer dev_err(dev, "no memory for input device\n");
8534354476SDaniel Beer return -ENOMEM;
8634354476SDaniel Beer }
8734354476SDaniel Beer
8834354476SDaniel Beer if (!devm_request_region(dev, PORT_CMD, 1, "Winmate FM07 EC"))
8934354476SDaniel Beer return -EBUSY;
9034354476SDaniel Beer if (!devm_request_region(dev, PORT_DATA, 1, "Winmate FM07 EC"))
9134354476SDaniel Beer return -EBUSY;
9234354476SDaniel Beer
9334354476SDaniel Beer input->name = "Winmate FM07 front-panel keys";
9434354476SDaniel Beer input->phys = DRV_NAME "/input0";
9534354476SDaniel Beer
9634354476SDaniel Beer input->id.bustype = BUS_HOST;
9734354476SDaniel Beer input->id.vendor = 0x0001;
9834354476SDaniel Beer input->id.product = 0x0001;
9934354476SDaniel Beer input->id.version = 0x0100;
10034354476SDaniel Beer
10134354476SDaniel Beer __set_bit(EV_KEY, input->evbit);
10234354476SDaniel Beer
10334354476SDaniel Beer for (i = 0; i < NUM_KEYS; i++)
10434354476SDaniel Beer __set_bit(BASE_KEY + i, input->keybit);
10534354476SDaniel Beer
10634354476SDaniel Beer ret = input_setup_polling(input, fm07keys_poll);
10734354476SDaniel Beer if (ret) {
10834354476SDaniel Beer dev_err(dev, "unable to set up polling, err=%d\n", ret);
10934354476SDaniel Beer return ret;
11034354476SDaniel Beer }
11134354476SDaniel Beer
11234354476SDaniel Beer /* These are silicone buttons. They can't be pressed in rapid
11334354476SDaniel Beer * succession too quickly, and 50 Hz seems to be an adequate
11434354476SDaniel Beer * sampling rate without missing any events when tested.
11534354476SDaniel Beer */
11634354476SDaniel Beer input_set_poll_interval(input, 20);
11734354476SDaniel Beer
11834354476SDaniel Beer ret = input_register_device(input);
11934354476SDaniel Beer if (ret) {
12034354476SDaniel Beer dev_err(dev, "unable to register polled device, err=%d\n",
12134354476SDaniel Beer ret);
12234354476SDaniel Beer return ret;
12334354476SDaniel Beer }
12434354476SDaniel Beer
12534354476SDaniel Beer input_sync(input);
12634354476SDaniel Beer return 0;
12734354476SDaniel Beer }
12834354476SDaniel Beer
12934354476SDaniel Beer static struct platform_driver fm07keys_driver = {
13034354476SDaniel Beer .probe = fm07keys_probe,
13134354476SDaniel Beer .driver = {
13234354476SDaniel Beer .name = DRV_NAME
13334354476SDaniel Beer },
13434354476SDaniel Beer };
13534354476SDaniel Beer
13634354476SDaniel Beer static struct platform_device *dev;
13734354476SDaniel Beer
13834354476SDaniel Beer static const struct dmi_system_id fm07keys_dmi_table[] __initconst = {
13934354476SDaniel Beer {
14034354476SDaniel Beer /* FM07 and FM07P */
14134354476SDaniel Beer .matches = {
14234354476SDaniel Beer DMI_MATCH(DMI_SYS_VENDOR, "Winmate Inc."),
14334354476SDaniel Beer DMI_MATCH(DMI_PRODUCT_NAME, "IP30"),
14434354476SDaniel Beer },
14534354476SDaniel Beer },
14634354476SDaniel Beer { }
14734354476SDaniel Beer };
14834354476SDaniel Beer
14934354476SDaniel Beer MODULE_DEVICE_TABLE(dmi, fm07keys_dmi_table);
15034354476SDaniel Beer
fm07keys_init(void)15134354476SDaniel Beer static int __init fm07keys_init(void)
15234354476SDaniel Beer {
15334354476SDaniel Beer int ret;
15434354476SDaniel Beer
15534354476SDaniel Beer if (!dmi_check_system(fm07keys_dmi_table))
15634354476SDaniel Beer return -ENODEV;
15734354476SDaniel Beer
15834354476SDaniel Beer ret = platform_driver_register(&fm07keys_driver);
15934354476SDaniel Beer if (ret) {
16034354476SDaniel Beer pr_err("fm07keys: failed to register driver, err=%d\n", ret);
16134354476SDaniel Beer return ret;
16234354476SDaniel Beer }
16334354476SDaniel Beer
164*8d05fc03SBarnabás Pőcze dev = platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, 0);
16534354476SDaniel Beer if (IS_ERR(dev)) {
16634354476SDaniel Beer ret = PTR_ERR(dev);
16734354476SDaniel Beer pr_err("fm07keys: failed to allocate device, err = %d\n", ret);
16834354476SDaniel Beer goto fail_register;
16934354476SDaniel Beer }
17034354476SDaniel Beer
17134354476SDaniel Beer return 0;
17234354476SDaniel Beer
17334354476SDaniel Beer fail_register:
17434354476SDaniel Beer platform_driver_unregister(&fm07keys_driver);
17534354476SDaniel Beer return ret;
17634354476SDaniel Beer }
17734354476SDaniel Beer
fm07keys_exit(void)17834354476SDaniel Beer static void __exit fm07keys_exit(void)
17934354476SDaniel Beer {
18034354476SDaniel Beer platform_driver_unregister(&fm07keys_driver);
18134354476SDaniel Beer platform_device_unregister(dev);
18234354476SDaniel Beer }
18334354476SDaniel Beer
18434354476SDaniel Beer module_init(fm07keys_init);
18534354476SDaniel Beer module_exit(fm07keys_exit);
18634354476SDaniel Beer
18734354476SDaniel Beer MODULE_AUTHOR("Daniel Beer <daniel.beer@tirotech.co.nz>");
18834354476SDaniel Beer MODULE_DESCRIPTION("Winmate FM07 front-panel keys driver");
18934354476SDaniel Beer MODULE_LICENSE("GPL");
190