linux/drivers/leds/leds-clevo-mail.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
   3
   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              0x0084
  16#define CLEVO_MAIL_LED_BLINK_1HZ        0x008A
  17#define CLEVO_MAIL_LED_BLINK_0_5HZ      0x0083
  18
  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 models
  37 *
  38 * Contains the known good models this driver is compatible with.
  39 * When adding a new model try to be as strict as possible. This
  40 * makes it possible to keep the false positives (the model is
  41 * detected as working, but in reality it is not) as low as
  42 * 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        else
 105                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 to
 121                 * chose one user friendly blinking of the LED, and
 122                 * 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 int clevo_mail_led_remove(struct platform_device *pdev)
 163{
 164        led_classdev_unregister(&clevo_mail_led);
 165        return 0;
 166}
 167
 168static struct platform_driver clevo_mail_led_driver = {
 169        .remove         = clevo_mail_led_remove,
 170        .driver         = {
 171                .name           = KBUILD_MODNAME,
 172        },
 173};
 174
 175static int __init clevo_mail_led_init(void)
 176{
 177        int error = 0;
 178        int count = 0;
 179
 180        /* Check with the help of DMI if we are running on supported hardware */
 181        if (!nodetect) {
 182                count = dmi_check_system(clevo_mail_led_dmi_table);
 183        } else {
 184                count = 1;
 185                pr_err("Skipping DMI detection. "
 186                       "If the driver works on your hardware please "
 187                       "report model and the output of dmidecode in tracker "
 188                       "at http://sourceforge.net/projects/clevo-mailled/\n");
 189        }
 190
 191        if (!count)
 192                return -ENODEV;
 193
 194        pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
 195        if (!IS_ERR(pdev)) {
 196                error = platform_driver_probe(&clevo_mail_led_driver,
 197                                              clevo_mail_led_probe);
 198                if (error) {
 199                        pr_err("Can't probe platform driver\n");
 200                        platform_device_unregister(pdev);
 201                }
 202        } else
 203                error = PTR_ERR(pdev);
 204
 205        return error;
 206}
 207
 208static void __exit clevo_mail_led_exit(void)
 209{
 210        platform_device_unregister(pdev);
 211        platform_driver_unregister(&clevo_mail_led_driver);
 212
 213        clevo_mail_led_set(NULL, LED_OFF);
 214}
 215
 216module_init(clevo_mail_led_init);
 217module_exit(clevo_mail_led_exit);
 218