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