mirror of
https://github.com/wessel-novacustom/clevo-keyboard.git
synced 2024-11-15 03:34:01 +01:00
449 lines
14 KiB
C
449 lines
14 KiB
C
/*!
|
|
* Copyright (c) 2018-2020 TUXEDO Computers GmbH <tux@tuxedocomputers.com>
|
|
*
|
|
* This file is part of tuxedo-keyboard.
|
|
*
|
|
* tuxedo-keyboard is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This software is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this software. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifndef CLEVO_KEYBOARD_H
|
|
#define CLEVO_KEYBOARD_H
|
|
|
|
#include "tuxedo_keyboard_common.h"
|
|
#include "clevo_interfaces.h"
|
|
#include "clevo_leds.h"
|
|
|
|
// Clevo event codes
|
|
#define CLEVO_EVENT_KB_LEDS_DECREASE 0x81
|
|
#define CLEVO_EVENT_KB_LEDS_INCREASE 0x82
|
|
#define CLEVO_EVENT_KB_LEDS_CYCLE_MODE 0x83
|
|
#define CLEVO_EVENT_KB_LEDS_CYCLE_BRIGHTNESS 0x8A
|
|
#define CLEVO_EVENT_KB_LEDS_TOGGLE 0x9F
|
|
|
|
#define CLEVO_EVENT_TOUCHPAD_TOGGLE 0x5D
|
|
#define CLEVO_EVENT_TOUCHPAD_OFF 0xFC
|
|
#define CLEVO_EVENT_TOUCHPAD_ON 0xFD
|
|
|
|
#define CLEVO_EVENT_RFKILL1 0x85
|
|
#define CLEVO_EVENT_RFKILL2 0x86
|
|
|
|
#define CLEVO_KB_MODE_DEFAULT 0 // "CUSTOM"/Static Color
|
|
|
|
static struct clevo_interfaces_t {
|
|
struct clevo_interface_t *wmi;
|
|
struct clevo_interface_t *acpi;
|
|
} clevo_interfaces;
|
|
|
|
static struct clevo_interface_t *active_clevo_interface;
|
|
|
|
static DEFINE_MUTEX(clevo_keyboard_interface_modification_lock);
|
|
|
|
static struct key_entry clevo_keymap[] = {
|
|
// Keyboard backlight (RGB versions)
|
|
{ KE_KEY, CLEVO_EVENT_KB_LEDS_DECREASE, { KEY_KBDILLUMDOWN } },
|
|
{ KE_KEY, CLEVO_EVENT_KB_LEDS_INCREASE, { KEY_KBDILLUMUP } },
|
|
{ KE_KEY, CLEVO_EVENT_KB_LEDS_TOGGLE, { KEY_KBDILLUMTOGGLE } },
|
|
{ KE_KEY, CLEVO_EVENT_KB_LEDS_CYCLE_MODE, { KEY_LIGHTS_TOGGLE } },
|
|
// Single cycle key (white only versions)
|
|
{ KE_KEY, CLEVO_EVENT_KB_LEDS_CYCLE_BRIGHTNESS, { KEY_KBDILLUMTOGGLE } },
|
|
|
|
// Touchpad
|
|
// The weirdly named touchpad toggle key that is implemented as KEY_F21 "everywhere"
|
|
// (instead of KEY_TOUCHPAD_TOGGLE or on/off)
|
|
// Most "new" devices just provide one toggle event
|
|
{ KE_KEY, CLEVO_EVENT_TOUCHPAD_TOGGLE, { KEY_F21 } },
|
|
// Some "old" devices produces on/off events
|
|
{ KE_KEY, CLEVO_EVENT_TOUCHPAD_OFF, { KEY_F21 } },
|
|
{ KE_KEY, CLEVO_EVENT_TOUCHPAD_ON, { KEY_F21 } },
|
|
// The alternative key events (currently not used)
|
|
// { KE_KEY, CLEVO_EVENT_TOUCHPAD_OFF, { KEY_TOUCHPAD_OFF } },
|
|
// { KE_KEY, CLEVO_EVENT_TOUCHPAD_ON, { KEY_TOUCHPAD_ON } },
|
|
// { KE_KEY, CLEVO_EVENT_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } },
|
|
|
|
// Rfkill still needed by some devices
|
|
{ KE_KEY, CLEVO_EVENT_RFKILL1, { KEY_RFKILL } },
|
|
{ KE_IGNORE, CLEVO_EVENT_RFKILL2, { KEY_RFKILL } }, // Older rfkill event
|
|
// Note: Volume events need to be ignored as to not interfere with built-in functionality
|
|
{ KE_IGNORE, 0xfa, { KEY_UNKNOWN } }, // Appears by volume up/down
|
|
{ KE_IGNORE, 0xfb, { KEY_UNKNOWN } }, // Appears by mute toggle
|
|
|
|
{ KE_END, 0 }
|
|
};
|
|
|
|
// Keyboard struct
|
|
static struct kbd_led_state_t {
|
|
u8 has_mode;
|
|
u8 mode;
|
|
u8 whole_kbd_color;
|
|
} kbd_led_state = {
|
|
.has_mode = 1,
|
|
.mode = CLEVO_KB_MODE_DEFAULT,
|
|
.whole_kbd_color = 7,
|
|
};
|
|
|
|
static struct kbd_backlight_mode_t {
|
|
u8 key;
|
|
u32 value;
|
|
const char *const name;
|
|
} kbd_backlight_modes[] = {
|
|
{ .key = 0, .value = 0x00000000, .name = "CUSTOM"},
|
|
{ .key = 1, .value = 0x1002a000, .name = "BREATHE"},
|
|
{ .key = 2, .value = 0x33010000, .name = "CYCLE"},
|
|
{ .key = 3, .value = 0x80000000, .name = "DANCE"},
|
|
{ .key = 4, .value = 0xA0000000, .name = "FLASH"},
|
|
{ .key = 5, .value = 0x70000000, .name = "RANDOM_COLOR"},
|
|
{ .key = 6, .value = 0x90000000, .name = "TEMPO"},
|
|
{ .key = 7, .value = 0xB0000000, .name = "WAVE"}
|
|
};
|
|
|
|
u32 clevo_evaluate_method2(u8 cmd, u32 arg, union acpi_object **result)
|
|
{
|
|
if (IS_ERR_OR_NULL(active_clevo_interface)) {
|
|
pr_err("clevo_keyboard: no active interface while attempting cmd %02x arg %08x\n", cmd, arg);
|
|
return -ENODEV;
|
|
}
|
|
return active_clevo_interface->method_call(cmd, arg, result);
|
|
}
|
|
EXPORT_SYMBOL(clevo_evaluate_method2);
|
|
|
|
u32 clevo_evaluate_method(u8 cmd, u32 arg, u32 *result)
|
|
{
|
|
u32 status = 0;
|
|
union acpi_object *out_obj;
|
|
|
|
status = clevo_evaluate_method2(cmd, arg, &out_obj);
|
|
if (status) {
|
|
return status;
|
|
}
|
|
else {
|
|
if (out_obj->type == ACPI_TYPE_INTEGER) {
|
|
if (!IS_ERR_OR_NULL(result))
|
|
*result = (u32) out_obj->integer.value;
|
|
} else {
|
|
pr_err("return type not integer, use clevo_evaluate_method2\n");
|
|
status = -ENODATA;
|
|
}
|
|
ACPI_FREE(out_obj);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
EXPORT_SYMBOL(clevo_evaluate_method);
|
|
|
|
u32 clevo_get_active_interface_id(char **id_str)
|
|
{
|
|
if (IS_ERR_OR_NULL(active_clevo_interface))
|
|
return -ENODEV;
|
|
|
|
if (!IS_ERR_OR_NULL(id_str))
|
|
*id_str = active_clevo_interface->string_id;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clevo_get_active_interface_id);
|
|
|
|
static int set_enabled_cmd(u8 state)
|
|
{
|
|
u32 cmd = 0xE0000000;
|
|
TUXEDO_INFO("Set keyboard enabled to: %d\n", state);
|
|
|
|
if (state == 0) {
|
|
cmd |= 0x003001;
|
|
} else {
|
|
cmd |= 0x07F001;
|
|
}
|
|
|
|
return clevo_evaluate_method(CLEVO_CMD_SET_KB_RGB_LEDS, cmd, NULL);
|
|
}
|
|
|
|
static void set_next_color_whole_kb(void)
|
|
{
|
|
/* "Calculate" new to-be color */
|
|
u32 new_color_id;
|
|
u32 new_color_code;
|
|
|
|
new_color_id = kbd_led_state.whole_kbd_color + 1;
|
|
if (new_color_id >= color_list.size) {
|
|
new_color_id = 1; // Skip black
|
|
}
|
|
new_color_code = color_list.colors[new_color_id].code;
|
|
|
|
TUXEDO_INFO("set_next_color_whole_kb(): new_color_id: %i, new_color_code %X",
|
|
new_color_id, new_color_code);
|
|
|
|
/* Set color on all four regions*/
|
|
clevo_leds_set_color_extern(new_color_code);
|
|
kbd_led_state.whole_kbd_color = new_color_id;
|
|
}
|
|
|
|
static void set_kbd_backlight_mode(u8 kbd_backlight_mode)
|
|
{
|
|
TUXEDO_INFO("Set keyboard backlight mode on %s", kbd_backlight_modes[kbd_backlight_mode].name);
|
|
|
|
if (!clevo_evaluate_method(CLEVO_CMD_SET_KB_RGB_LEDS, kbd_backlight_modes[kbd_backlight_mode].value, NULL)) {
|
|
// method was succesfull so update ur internal state struct
|
|
kbd_led_state.mode = kbd_backlight_mode;
|
|
}
|
|
}
|
|
|
|
// Sysfs Interface for the keyboard backlight mode
|
|
static ssize_t list_kbd_backlight_modes_fs(struct device *child, struct device_attribute *attr,
|
|
char *buffer)
|
|
{
|
|
return sprintf(buffer, "%d\n", kbd_led_state.mode);
|
|
}
|
|
|
|
static ssize_t set_kbd_backlight_mode_fs(struct device *child,
|
|
struct device_attribute *attr,
|
|
const char *buffer, size_t size)
|
|
{
|
|
unsigned int kbd_backlight_mode;
|
|
|
|
int err = kstrtouint(buffer, 0, &kbd_backlight_mode);
|
|
if (err) {
|
|
return err;
|
|
}
|
|
|
|
kbd_backlight_mode = clamp_t(u8, kbd_backlight_mode, 0, ARRAY_SIZE(kbd_backlight_modes) - 1);
|
|
set_kbd_backlight_mode(kbd_backlight_mode);
|
|
|
|
return size;
|
|
}
|
|
|
|
// Sysfs attribute file permissions and method linking
|
|
static DEVICE_ATTR(kbd_backlight_mode, 0644, list_kbd_backlight_modes_fs, set_kbd_backlight_mode_fs);
|
|
|
|
static int kbd_backlight_mode_id_validator(const char *value,
|
|
const struct kernel_param *kbd_backlight_mode_param)
|
|
{
|
|
int kbd_backlight_mode = 0;
|
|
|
|
if (kstrtoint(value, 10, &kbd_backlight_mode) != 0
|
|
|| kbd_backlight_mode < 0
|
|
|| kbd_backlight_mode > (ARRAY_SIZE(kbd_backlight_modes) - 1)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
return param_set_int(value, kbd_backlight_mode_param);
|
|
}
|
|
|
|
static const struct kernel_param_ops param_ops_mode_ops = {
|
|
.set = kbd_backlight_mode_id_validator,
|
|
.get = param_get_int,
|
|
};
|
|
|
|
static u8 param_kbd_backlight_mode = CLEVO_KB_MODE_DEFAULT;
|
|
module_param_cb(kbd_backlight_mode, ¶m_ops_mode_ops, ¶m_kbd_backlight_mode, S_IRUSR);
|
|
MODULE_PARM_DESC(kbd_backlight_mode, "Set the keyboard backlight mode");
|
|
|
|
static void clevo_keyboard_event_callb(u32 event)
|
|
{
|
|
u32 key_event = event;
|
|
|
|
TUXEDO_DEBUG("Clevo event: %0#6x\n", event);
|
|
|
|
switch (key_event) {
|
|
case CLEVO_EVENT_KB_LEDS_CYCLE_MODE:
|
|
set_next_color_whole_kb();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (current_driver != NULL && current_driver->input_device != NULL) {
|
|
if (!sparse_keymap_report_known_event(current_driver->input_device, key_event, 1, true)) {
|
|
TUXEDO_DEBUG("Unknown key - %d (%0#6x)\n", key_event, key_event);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void clevo_keyboard_init_device_interface(struct platform_device *dev)
|
|
{
|
|
// Setup sysfs
|
|
if (clevo_leds_get_backlight_type() == CLEVO_KB_BACKLIGHT_TYPE_3_ZONE_RGB) {
|
|
if (device_create_file(&dev->dev, &dev_attr_kbd_backlight_mode) != 0) {
|
|
TUXEDO_ERROR("Sysfs attribute file creation failed for blinking pattern\n");
|
|
}
|
|
else {
|
|
kbd_led_state.has_mode = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* strstr version of dmi_match
|
|
*/
|
|
static bool dmi_string_in(enum dmi_field f, const char *str)
|
|
{
|
|
const char *info = dmi_get_system_info(f);
|
|
|
|
if (info == NULL || str == NULL)
|
|
return info == str;
|
|
|
|
return strstr(info, str) != NULL;
|
|
}
|
|
|
|
static void clevo_keyboard_init(void)
|
|
{
|
|
bool performance_profile_set_workaround;
|
|
|
|
kbd_led_state.mode = param_kbd_backlight_mode;
|
|
set_kbd_backlight_mode(kbd_led_state.mode);
|
|
|
|
clevo_evaluate_method(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL);
|
|
set_enabled_cmd(1);
|
|
|
|
// Workaround for firmware issue not setting selected performance profile.
|
|
// Explicitly set "performance" perf. profile on init regardless of what is chosen
|
|
// for these devices (Aura, XP14, IBS14v5)
|
|
performance_profile_set_workaround = false
|
|
|| dmi_string_in(DMI_BOARD_NAME, "AURA1501")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "EDUBOOK1502")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "NL5xRU")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "NV4XMB,ME,MZ")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "L140CU")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "NS50MU")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "NS50_70MU")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "PCX0DX")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "PCx0Dx_GN20")
|
|
|| dmi_string_in(DMI_BOARD_NAME, "L14xMU")
|
|
;
|
|
if (performance_profile_set_workaround) {
|
|
TUXEDO_INFO("Performance profile 'performance' set workaround applied\n");
|
|
clevo_evaluate_method(CLEVO_CMD_OPT, 0x19000002, NULL);
|
|
}
|
|
}
|
|
|
|
static int clevo_keyboard_probe(struct platform_device *dev)
|
|
{
|
|
clevo_leds_init(dev);
|
|
// clevo_keyboard_init_device_interface() must come after clevo_leds_init()
|
|
// to know keyboard backlight type
|
|
clevo_keyboard_init_device_interface(dev);
|
|
clevo_keyboard_init();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void clevo_keyboard_remove_device_interface(struct platform_device *dev)
|
|
{
|
|
if (kbd_led_state.has_mode == 1) {
|
|
device_remove_file(&dev->dev, &dev_attr_kbd_backlight_mode);
|
|
}
|
|
}
|
|
|
|
static int clevo_keyboard_remove(struct platform_device *dev)
|
|
{
|
|
clevo_keyboard_remove_device_interface(dev);
|
|
clevo_leds_remove(dev);
|
|
return 0;
|
|
}
|
|
|
|
static int clevo_keyboard_suspend(struct platform_device *dev, pm_message_t state)
|
|
{
|
|
// turning the keyboard off prevents default colours showing on resume
|
|
set_enabled_cmd(0);
|
|
return 0;
|
|
}
|
|
|
|
static int clevo_keyboard_resume(struct platform_device *dev)
|
|
{
|
|
clevo_evaluate_method(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL);
|
|
clevo_leds_restore_state_extern(); // Sometimes clevo devices forget their last state after
|
|
// suspend, so let the kernel ensure it.
|
|
set_enabled_cmd(1);
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver platform_driver_clevo = {
|
|
.remove = clevo_keyboard_remove,
|
|
.suspend = clevo_keyboard_suspend,
|
|
.resume = clevo_keyboard_resume,
|
|
.driver =
|
|
{
|
|
.name = DRIVER_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
};
|
|
|
|
static struct tuxedo_keyboard_driver clevo_keyboard_driver = {
|
|
.platform_driver = &platform_driver_clevo,
|
|
.probe = clevo_keyboard_probe,
|
|
.key_map = clevo_keymap,
|
|
};
|
|
|
|
u32 clevo_keyboard_add_interface(struct clevo_interface_t *new_interface)
|
|
{
|
|
mutex_lock(&clevo_keyboard_interface_modification_lock);
|
|
|
|
if (strcmp(new_interface->string_id, CLEVO_INTERFACE_WMI_STRID) == 0) {
|
|
clevo_interfaces.wmi = new_interface;
|
|
clevo_interfaces.wmi->event_callb = clevo_keyboard_event_callb;
|
|
|
|
// Only use wmi if there is no other current interface
|
|
if (ZERO_OR_NULL_PTR(active_clevo_interface)) {
|
|
pr_debug("enable wmi events\n");
|
|
clevo_interfaces.wmi->method_call(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL);
|
|
|
|
active_clevo_interface = clevo_interfaces.wmi;
|
|
}
|
|
} else if (strcmp(new_interface->string_id, CLEVO_INTERFACE_ACPI_STRID) == 0) {
|
|
clevo_interfaces.acpi = new_interface;
|
|
clevo_interfaces.acpi->event_callb = clevo_keyboard_event_callb;
|
|
|
|
pr_debug("enable acpi events (takes priority)\n");
|
|
clevo_interfaces.acpi->method_call(CLEVO_CMD_SET_EVENTS_ENABLED, 0, NULL);
|
|
active_clevo_interface = clevo_interfaces.acpi;
|
|
} else {
|
|
// Not recognized interface
|
|
pr_err("unrecognized interface\n");
|
|
mutex_unlock(&clevo_keyboard_interface_modification_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_unlock(&clevo_keyboard_interface_modification_lock);
|
|
|
|
if (active_clevo_interface != NULL)
|
|
tuxedo_keyboard_init_driver(&clevo_keyboard_driver);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clevo_keyboard_add_interface);
|
|
|
|
u32 clevo_keyboard_remove_interface(struct clevo_interface_t *interface)
|
|
{
|
|
mutex_lock(&clevo_keyboard_interface_modification_lock);
|
|
|
|
if (strcmp(interface->string_id, CLEVO_INTERFACE_WMI_STRID) == 0) {
|
|
clevo_interfaces.wmi = NULL;
|
|
} else if (strcmp(interface->string_id, CLEVO_INTERFACE_ACPI_STRID) == 0) {
|
|
clevo_interfaces.acpi = NULL;
|
|
} else {
|
|
mutex_unlock(&clevo_keyboard_interface_modification_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (active_clevo_interface == interface) {
|
|
tuxedo_keyboard_remove_driver(&clevo_keyboard_driver);
|
|
active_clevo_interface = NULL;
|
|
}
|
|
|
|
|
|
mutex_unlock(&clevo_keyboard_interface_modification_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(clevo_keyboard_remove_interface);
|
|
|
|
#endif // CLEVO_KEYBOARD_H
|