162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/module.h>
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/platform_device.h>
762306a36Sopenharmony_ci#include <linux/err.h>
862306a36Sopenharmony_ci#include <linux/leds.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci#include <linux/dmi.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/i8042.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define CLEVO_MAIL_LED_OFF		0x0084
1662306a36Sopenharmony_ci#define CLEVO_MAIL_LED_BLINK_1HZ	0x008A
1762306a36Sopenharmony_ci#define CLEVO_MAIL_LED_BLINK_0_5HZ	0x0083
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciMODULE_AUTHOR("Márton Németh <nm127@freemail.hu>");
2062306a36Sopenharmony_ciMODULE_DESCRIPTION("Clevo mail LED driver");
2162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic bool nodetect;
2462306a36Sopenharmony_cimodule_param_named(nodetect, nodetect, bool, 0);
2562306a36Sopenharmony_ciMODULE_PARM_DESC(nodetect, "Skip DMI hardware detection");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic struct platform_device *pdev;
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	pr_info("'%s' found\n", id->ident);
3262306a36Sopenharmony_ci	return 1;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci/*
3662306a36Sopenharmony_ci * struct clevo_mail_led_dmi_table - List of known good models
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci * Contains the known good models this driver is compatible with.
3962306a36Sopenharmony_ci * When adding a new model try to be as strict as possible. This
4062306a36Sopenharmony_ci * makes it possible to keep the false positives (the model is
4162306a36Sopenharmony_ci * detected as working, but in reality it is not) as low as
4262306a36Sopenharmony_ci * possible.
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_cistatic const struct dmi_system_id clevo_mail_led_dmi_table[] __initconst = {
4562306a36Sopenharmony_ci	{
4662306a36Sopenharmony_ci		.callback = clevo_mail_led_dmi_callback,
4762306a36Sopenharmony_ci		.ident = "Clevo D410J",
4862306a36Sopenharmony_ci		.matches = {
4962306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "VIA"),
5062306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "K8N800"),
5162306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_VERSION, "VT8204B")
5262306a36Sopenharmony_ci		}
5362306a36Sopenharmony_ci	},
5462306a36Sopenharmony_ci	{
5562306a36Sopenharmony_ci		.callback = clevo_mail_led_dmi_callback,
5662306a36Sopenharmony_ci		.ident = "Clevo M5x0N",
5762306a36Sopenharmony_ci		.matches = {
5862306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "CLEVO Co."),
5962306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "M5x0N")
6062306a36Sopenharmony_ci		}
6162306a36Sopenharmony_ci	},
6262306a36Sopenharmony_ci	{
6362306a36Sopenharmony_ci		.callback = clevo_mail_led_dmi_callback,
6462306a36Sopenharmony_ci		.ident = "Clevo M5x0V",
6562306a36Sopenharmony_ci		.matches = {
6662306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "),
6762306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "M5X0V "),
6862306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198")
6962306a36Sopenharmony_ci		}
7062306a36Sopenharmony_ci	},
7162306a36Sopenharmony_ci	{
7262306a36Sopenharmony_ci		.callback = clevo_mail_led_dmi_callback,
7362306a36Sopenharmony_ci		.ident = "Clevo D400P",
7462306a36Sopenharmony_ci		.matches = {
7562306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_VENDOR, "Clevo"),
7662306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "D400P"),
7762306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_VERSION, "Rev.A"),
7862306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_VERSION, "0106")
7962306a36Sopenharmony_ci		}
8062306a36Sopenharmony_ci	},
8162306a36Sopenharmony_ci	{
8262306a36Sopenharmony_ci		.callback = clevo_mail_led_dmi_callback,
8362306a36Sopenharmony_ci		.ident = "Clevo D410V",
8462306a36Sopenharmony_ci		.matches = {
8562306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_VENDOR, "Clevo, Co."),
8662306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_NAME, "D400V/D470V"),
8762306a36Sopenharmony_ci			DMI_MATCH(DMI_BOARD_VERSION, "SS78B"),
8862306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_VERSION, "Rev. A1")
8962306a36Sopenharmony_ci		}
9062306a36Sopenharmony_ci	},
9162306a36Sopenharmony_ci	{ }
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic void clevo_mail_led_set(struct led_classdev *led_cdev,
9662306a36Sopenharmony_ci				enum led_brightness value)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	i8042_lock_chip();
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (value == LED_OFF)
10162306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_OFF);
10262306a36Sopenharmony_ci	else if (value <= LED_HALF)
10362306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
10462306a36Sopenharmony_ci	else
10562306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	i8042_unlock_chip();
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_cistatic int clevo_mail_led_blink(struct led_classdev *led_cdev,
11262306a36Sopenharmony_ci				unsigned long *delay_on,
11362306a36Sopenharmony_ci				unsigned long *delay_off)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	int status = -EINVAL;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	i8042_lock_chip();
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) {
12062306a36Sopenharmony_ci		/* Special case: the leds subsystem requested us to
12162306a36Sopenharmony_ci		 * chose one user friendly blinking of the LED, and
12262306a36Sopenharmony_ci		 * start it. Let's blink the led slowly (0.5Hz).
12362306a36Sopenharmony_ci		 */
12462306a36Sopenharmony_ci		*delay_on = 1000; /* ms */
12562306a36Sopenharmony_ci		*delay_off = 1000; /* ms */
12662306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
12762306a36Sopenharmony_ci		status = 0;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	} else if (*delay_on == 500 /* ms */ && *delay_off == 500 /* ms */) {
13062306a36Sopenharmony_ci		/* blink the led with 1Hz */
13162306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);
13262306a36Sopenharmony_ci		status = 0;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	} else if (*delay_on == 1000 /* ms */ && *delay_off == 1000 /* ms */) {
13562306a36Sopenharmony_ci		/* blink the led with 0.5Hz */
13662306a36Sopenharmony_ci		i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
13762306a36Sopenharmony_ci		status = 0;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	} else {
14062306a36Sopenharmony_ci		pr_debug("clevo_mail_led_blink(..., %lu, %lu),"
14162306a36Sopenharmony_ci		       " returning -EINVAL (unsupported)\n",
14262306a36Sopenharmony_ci		       *delay_on, *delay_off);
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	i8042_unlock_chip();
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return status;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic struct led_classdev clevo_mail_led = {
15162306a36Sopenharmony_ci	.name			= "clevo::mail",
15262306a36Sopenharmony_ci	.brightness_set		= clevo_mail_led_set,
15362306a36Sopenharmony_ci	.blink_set		= clevo_mail_led_blink,
15462306a36Sopenharmony_ci	.flags			= LED_CORE_SUSPENDRESUME,
15562306a36Sopenharmony_ci};
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic int __init clevo_mail_led_probe(struct platform_device *pdev)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	return led_classdev_register(&pdev->dev, &clevo_mail_led);
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int clevo_mail_led_remove(struct platform_device *pdev)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	led_classdev_unregister(&clevo_mail_led);
16562306a36Sopenharmony_ci	return 0;
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic struct platform_driver clevo_mail_led_driver = {
16962306a36Sopenharmony_ci	.remove		= clevo_mail_led_remove,
17062306a36Sopenharmony_ci	.driver		= {
17162306a36Sopenharmony_ci		.name		= KBUILD_MODNAME,
17262306a36Sopenharmony_ci	},
17362306a36Sopenharmony_ci};
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic int __init clevo_mail_led_init(void)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	int error = 0;
17862306a36Sopenharmony_ci	int count = 0;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	/* Check with the help of DMI if we are running on supported hardware */
18162306a36Sopenharmony_ci	if (!nodetect) {
18262306a36Sopenharmony_ci		count = dmi_check_system(clevo_mail_led_dmi_table);
18362306a36Sopenharmony_ci	} else {
18462306a36Sopenharmony_ci		count = 1;
18562306a36Sopenharmony_ci		pr_err("Skipping DMI detection. "
18662306a36Sopenharmony_ci		       "If the driver works on your hardware please "
18762306a36Sopenharmony_ci		       "report model and the output of dmidecode in tracker "
18862306a36Sopenharmony_ci		       "at http://sourceforge.net/projects/clevo-mailled/\n");
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (!count)
19262306a36Sopenharmony_ci		return -ENODEV;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
19562306a36Sopenharmony_ci	if (!IS_ERR(pdev)) {
19662306a36Sopenharmony_ci		error = platform_driver_probe(&clevo_mail_led_driver,
19762306a36Sopenharmony_ci					      clevo_mail_led_probe);
19862306a36Sopenharmony_ci		if (error) {
19962306a36Sopenharmony_ci			pr_err("Can't probe platform driver\n");
20062306a36Sopenharmony_ci			platform_device_unregister(pdev);
20162306a36Sopenharmony_ci		}
20262306a36Sopenharmony_ci	} else
20362306a36Sopenharmony_ci		error = PTR_ERR(pdev);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	return error;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic void __exit clevo_mail_led_exit(void)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	platform_device_unregister(pdev);
21162306a36Sopenharmony_ci	platform_driver_unregister(&clevo_mail_led_driver);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	clevo_mail_led_set(NULL, LED_OFF);
21462306a36Sopenharmony_ci}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cimodule_init(clevo_mail_led_init);
21762306a36Sopenharmony_cimodule_exit(clevo_mail_led_exit);
218