217 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt3 4#include <linux/module.h>5 6#include <linux/platform_device.h>7#include <linux/err.h>8#include <linux/leds.h>9 10#include <linux/io.h>11#include <linux/dmi.h>12 13#include <linux/i8042.h>14 15#define CLEVO_MAIL_LED_OFF 0x008416#define CLEVO_MAIL_LED_BLINK_1HZ 0x008A17#define CLEVO_MAIL_LED_BLINK_0_5HZ 0x008318 19MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>");20MODULE_DESCRIPTION("Clevo mail LED driver");21MODULE_LICENSE("GPL");22 23static bool nodetect;24module_param_named(nodetect, nodetect, bool, 0);25MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection");26 27static struct platform_device *pdev;28 29static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id)30{31 pr_info("'%s' found\n", id->ident);32 return 1;33}34 35/*36 * struct clevo_mail_led_dmi_table - List of known good models37 *38 * Contains the known good models this driver is compatible with.39 * When adding a new model try to be as strict as possible. This40 * makes it possible to keep the false positives (the model is41 * detected as working, but in reality it is not) as low as42 * possible.43 */44static const struct dmi_system_id clevo_mail_led_dmi_table[] __initconst = {45 {46 .callback = clevo_mail_led_dmi_callback,47 .ident = "Clevo D410J",48 .matches = {49 DMI_MATCH(DMI_SYS_VENDOR, "VIA"),50 DMI_MATCH(DMI_PRODUCT_NAME, "K8N800"),51 DMI_MATCH(DMI_PRODUCT_VERSION, "VT8204B")52 }53 },54 {55 .callback = clevo_mail_led_dmi_callback,56 .ident = "Clevo M5x0N",57 .matches = {58 DMI_MATCH(DMI_SYS_VENDOR, "CLEVO Co."),59 DMI_MATCH(DMI_PRODUCT_NAME, "M5x0N")60 }61 },62 {63 .callback = clevo_mail_led_dmi_callback,64 .ident = "Clevo M5x0V",65 .matches = {66 DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "),67 DMI_MATCH(DMI_BOARD_NAME, "M5X0V "),68 DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198")69 }70 },71 {72 .callback = clevo_mail_led_dmi_callback,73 .ident = "Clevo D400P",74 .matches = {75 DMI_MATCH(DMI_BOARD_VENDOR, "Clevo"),76 DMI_MATCH(DMI_BOARD_NAME, "D400P"),77 DMI_MATCH(DMI_BOARD_VERSION, "Rev.A"),78 DMI_MATCH(DMI_PRODUCT_VERSION, "0106")79 }80 },81 {82 .callback = clevo_mail_led_dmi_callback,83 .ident = "Clevo D410V",84 .matches = {85 DMI_MATCH(DMI_BOARD_VENDOR, "Clevo, Co."),86 DMI_MATCH(DMI_BOARD_NAME, "D400V/D470V"),87 DMI_MATCH(DMI_BOARD_VERSION, "SS78B"),88 DMI_MATCH(DMI_PRODUCT_VERSION, "Rev. A1")89 }90 },91 { }92};93MODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table);94 95static void clevo_mail_led_set(struct led_classdev *led_cdev,96 enum led_brightness value)97{98 i8042_lock_chip();99 100 if (value == LED_OFF)101 i8042_command(NULL, CLEVO_MAIL_LED_OFF);102 else if (value <= LED_HALF)103 i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);104 else105 i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);106 107 i8042_unlock_chip();108 109}110 111static int clevo_mail_led_blink(struct led_classdev *led_cdev,112 unsigned long *delay_on,113 unsigned long *delay_off)114{115 int status = -EINVAL;116 117 i8042_lock_chip();118 119 if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) {120 /* Special case: the leds subsystem requested us to121 * chose one user friendly blinking of the LED, and122 * start it. Let's blink the led slowly (0.5Hz).123 */124 *delay_on = 1000; /* ms */125 *delay_off = 1000; /* ms */126 i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);127 status = 0;128 129 } else if (*delay_on == 500 /* ms */ && *delay_off == 500 /* ms */) {130 /* blink the led with 1Hz */131 i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);132 status = 0;133 134 } else if (*delay_on == 1000 /* ms */ && *delay_off == 1000 /* ms */) {135 /* blink the led with 0.5Hz */136 i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);137 status = 0;138 139 } else {140 pr_debug("clevo_mail_led_blink(..., %lu, %lu),"141 " returning -EINVAL (unsupported)\n",142 *delay_on, *delay_off);143 }144 145 i8042_unlock_chip();146 147 return status;148}149 150static struct led_classdev clevo_mail_led = {151 .name = "clevo::mail",152 .brightness_set = clevo_mail_led_set,153 .blink_set = clevo_mail_led_blink,154 .flags = LED_CORE_SUSPENDRESUME,155};156 157static int __init clevo_mail_led_probe(struct platform_device *pdev)158{159 return led_classdev_register(&pdev->dev, &clevo_mail_led);160}161 162static void clevo_mail_led_remove(struct platform_device *pdev)163{164 led_classdev_unregister(&clevo_mail_led);165}166 167static struct platform_driver clevo_mail_led_driver = {168 .remove_new = clevo_mail_led_remove,169 .driver = {170 .name = KBUILD_MODNAME,171 },172};173 174static int __init clevo_mail_led_init(void)175{176 int error = 0;177 int count = 0;178 179 /* Check with the help of DMI if we are running on supported hardware */180 if (!nodetect) {181 count = dmi_check_system(clevo_mail_led_dmi_table);182 } else {183 count = 1;184 pr_err("Skipping DMI detection. "185 "If the driver works on your hardware please "186 "report model and the output of dmidecode in tracker "187 "at http://sourceforge.net/projects/clevo-mailled/\n");188 }189 190 if (!count)191 return -ENODEV;192 193 pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);194 if (!IS_ERR(pdev)) {195 error = platform_driver_probe(&clevo_mail_led_driver,196 clevo_mail_led_probe);197 if (error) {198 pr_err("Can't probe platform driver\n");199 platform_device_unregister(pdev);200 }201 } else202 error = PTR_ERR(pdev);203 204 return error;205}206 207static void __exit clevo_mail_led_exit(void)208{209 platform_device_unregister(pdev);210 platform_driver_unregister(&clevo_mail_led_driver);211 212 clevo_mail_led_set(NULL, LED_OFF);213}214 215module_init(clevo_mail_led_init);216module_exit(clevo_mail_led_exit);217