Add keyboard LED class driver

This commit is contained in:
Jeremy Soller 2018-02-17 16:31:27 -07:00
parent 48a919d220
commit fb13ee2dfe
5 changed files with 193 additions and 87 deletions

View File

@ -1,6 +1,6 @@
/* /*
* led.c * led.c
* *
* Copyright (C) 2017 Jeremy Soller <jeremy@system76.com> * Copyright (C) 2017 Jeremy Soller <jeremy@system76.com>
* Copyright (C) 2014-2016 Arnoud Willemsen <mail@lynthium.com> * Copyright (C) 2014-2016 Arnoud Willemsen <mail@lynthium.com>
* Copyright (C) 2013-2015 TUXEDO Computers GmbH <tux@tuxedocomputers.com> * Copyright (C) 2013-2015 TUXEDO Computers GmbH <tux@tuxedocomputers.com>
@ -19,18 +19,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
static struct workqueue_struct *led_workqueue; static struct workqueue_struct *ap_led_workqueue;
static struct _led_work { static struct _ap_led_work {
struct work_struct work; struct work_struct work;
int wk; int wk;
} led_work; } ap_led_work;
static void airplane_led_update(struct work_struct *work) { static void ap_led_update(struct work_struct *work) {
u8 byte; u8 byte;
struct _led_work *w; struct _ap_led_work *w;
w = container_of(work, struct _led_work, work); w = container_of(work, struct _ap_led_work, work);
ec_read(0xD9, &byte); ec_read(0xD9, &byte);
@ -39,7 +39,7 @@ static void airplane_led_update(struct work_struct *work) {
/* wmbb 0x6C 1 (?) */ /* wmbb 0x6C 1 (?) */
} }
static enum led_brightness airplane_led_get(struct led_classdev *led_cdev) { static enum led_brightness ap_led_get(struct led_classdev *led_cdev) {
u8 byte; u8 byte;
ec_read(0xD9, &byte); ec_read(0xD9, &byte);
@ -48,30 +48,30 @@ static enum led_brightness airplane_led_get(struct led_classdev *led_cdev) {
} }
/* must not sleep */ /* must not sleep */
static void airplane_led_set(struct led_classdev *led_cdev, enum led_brightness value) { static void ap_led_set(struct led_classdev *led_cdev, enum led_brightness value) {
led_work.wk = value; ap_led_work.wk = value;
queue_work(led_workqueue, &led_work.work); queue_work(ap_led_workqueue, &ap_led_work.work);
} }
static struct led_classdev airplane_led = { static struct led_classdev ap_led = {
.name = "system76::airplane", .name = "system76::airplane",
.brightness_get = airplane_led_get, .brightness_get = ap_led_get,
.brightness_set = airplane_led_set, .brightness_set = ap_led_set,
.max_brightness = 1, .max_brightness = 1,
.default_trigger = "rfkill-any" .default_trigger = "rfkill-any"
}; };
static int __init s76_led_init(void) { static int __init ap_led_init(struct device *dev) {
int err; int err;
led_workqueue = create_singlethread_workqueue("led_workqueue"); ap_led_workqueue = create_singlethread_workqueue("ap_led_workqueue");
if (unlikely(!led_workqueue)) { if (unlikely(!ap_led_workqueue)) {
return -ENOMEM; return -ENOMEM;
} }
INIT_WORK(&led_work.work, airplane_led_update); INIT_WORK(&ap_led_work.work, ap_led_update);
err = led_classdev_register(&s76_platform_device->dev, &airplane_led); err = led_classdev_register(dev, &ap_led);
if (unlikely(err)) { if (unlikely(err)) {
goto err_destroy_workqueue; goto err_destroy_workqueue;
} }
@ -79,15 +79,15 @@ static int __init s76_led_init(void) {
return 0; return 0;
err_destroy_workqueue: err_destroy_workqueue:
destroy_workqueue(led_workqueue); destroy_workqueue(ap_led_workqueue);
led_workqueue = NULL; ap_led_workqueue = NULL;
return err; return err;
} }
static void __exit s76_led_exit(void) { static void __exit ap_led_exit(void) {
if (!IS_ERR_OR_NULL(airplane_led.dev)) if (!IS_ERR_OR_NULL(ap_led.dev))
led_classdev_unregister(&airplane_led); led_classdev_unregister(&ap_led);
if (led_workqueue) if (ap_led_workqueue)
destroy_workqueue(led_workqueue); destroy_workqueue(ap_led_workqueue);
} }

View File

@ -138,7 +138,7 @@ static void s76_input_close(struct input_dev *dev) {
s76_input_polling_task = NULL; s76_input_polling_task = NULL;
} }
static int __init s76_input_init(void) { static int __init s76_input_init(struct device *dev) {
int err; int err;
u8 byte; u8 byte;
@ -151,7 +151,7 @@ static int __init s76_input_init(void) {
s76_input_device->name = "System76 Airplane-Mode Hotkey"; s76_input_device->name = "System76 Airplane-Mode Hotkey";
s76_input_device->phys = "system76/input0"; s76_input_device->phys = "system76/input0";
s76_input_device->id.bustype = BUS_HOST; s76_input_device->id.bustype = BUS_HOST;
s76_input_device->dev.parent = &s76_platform_device->dev; s76_input_device->dev.parent = dev;
set_bit(EV_KEY, s76_input_device->evbit); set_bit(EV_KEY, s76_input_device->evbit);
set_bit(AIRPLANE_KEY, s76_input_device->keybit); set_bit(AIRPLANE_KEY, s76_input_device->keybit);
set_bit(KEY_TOUCHPAD_ON, s76_input_device->keybit); set_bit(KEY_TOUCHPAD_ON, s76_input_device->keybit);

4
kb.c
View File

@ -289,6 +289,8 @@ static void kb_full_color__set_brightness(unsigned i)
{ {
u8 lvl_to_raw[] = { 63, 126, 189, 252 }; u8 lvl_to_raw[] = { 63, 126, 189, 252 };
led_classdev_notify_brightness_hw_changed(&kb_led, i);
i = clamp_t(unsigned, i, 0, ARRAY_SIZE(lvl_to_raw) - 1); i = clamp_t(unsigned, i, 0, ARRAY_SIZE(lvl_to_raw) - 1);
if (!s76_wmbb(SET_KB_LED, if (!s76_wmbb(SET_KB_LED,
@ -334,9 +336,11 @@ static void kb_full_color__set_state(enum kb_state state)
switch (state) { switch (state) {
case KB_STATE_OFF: case KB_STATE_OFF:
led_classdev_notify_brightness_hw_changed(&kb_led, 0);
cmd |= 0x003001; cmd |= 0x003001;
break; break;
case KB_STATE_ON: case KB_STATE_ON:
led_classdev_notify_brightness_hw_changed(&kb_led, kb_backlight.brightness);
cmd |= 0x07F001; cmd |= 0x07F001;
break; break;
default: default:

95
kb_led.c Normal file
View File

@ -0,0 +1,95 @@
/*
* led.c
*
* Copyright (C) 2017 Jeremy Soller <jeremy@system76.com>
* Copyright (C) 2014-2016 Arnoud Willemsen <mail@lynthium.com>
* Copyright (C) 2013-2015 TUXEDO Computers GmbH <tux@tuxedocomputers.com>
*
* This program 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 2 of the License, or (at
* your option) any later version.
*
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
static struct workqueue_struct *kb_led_workqueue;
static struct _kb_led_work {
struct work_struct work;
int wk;
} kb_led_work;
static void kb_led_update(struct work_struct *work) {
u8 byte;
struct _kb_led_work *w;
w = container_of(work, struct _kb_led_work, work);
//
// ec_read(0xD9, &byte);
//
// ec_write(0xD9, w->wk ? byte & ~0x40 : byte | 0x40);
//
// /* wmbb 0x6C 1 (?) */
}
static enum led_brightness kb_led_get(struct led_classdev *led_cdev) {
// u8 byte;
//
// ec_read(0xD9, &byte);
//
// return byte & 0x40 ? LED_OFF : LED_FULL;
return LED_OFF;
}
/* must not sleep */
static void kb_led_set(struct led_classdev *led_cdev, enum led_brightness value) {
kb_led_work.wk = value;
queue_work(kb_led_workqueue, &kb_led_work.work);
}
static struct led_classdev kb_led = {
.name = "system76::kbd_backlight",
.flags = LED_BRIGHT_HW_CHANGED,
.brightness_get = kb_led_get,
.brightness_set = kb_led_set,
.max_brightness = 3,
};
static int __init kb_led_init(struct device *dev) {
int err;
kb_led_workqueue = create_singlethread_workqueue("kb_led_workqueue");
if (unlikely(!kb_led_workqueue)) {
return -ENOMEM;
}
INIT_WORK(&kb_led_work.work, kb_led_update);
err = led_classdev_register(dev, &kb_led);
if (unlikely(err)) {
goto err_destroy_workqueue;
}
return 0;
err_destroy_workqueue:
destroy_workqueue(kb_led_workqueue);
kb_led_workqueue = NULL;
return err;
}
static void __exit kb_led_exit(void) {
if (!IS_ERR_OR_NULL(kb_led.dev))
led_classdev_unregister(&kb_led);
if (kb_led_workqueue)
destroy_workqueue(kb_led_workqueue);
}

View File

@ -91,8 +91,9 @@ static int s76_wmbb(u32 method_id, u32 arg, u32 *retval) {
} }
#include "ec.c" #include "ec.c"
#include "led.c" #include "ap_led.c"
#include "input.c" #include "input.c"
#include "kb_led.c"
#include "kb.c" #include "kb.c"
#include "hwmon.c" #include "hwmon.c"
@ -177,11 +178,55 @@ static void s76_wmi_notify(u32 value, void *context) {
} }
static int s76_probe(struct platform_device *dev) { static int s76_probe(struct platform_device *dev) {
int status; int err;
status = wmi_install_notify_handler(S76_EVENT_GUID, s76_wmi_notify, NULL); err = ec_init();
if (unlikely(ACPI_FAILURE(status))) { if (unlikely(err)) {
S76_ERROR("Could not register WMI notify handler (%0#6x)\n", status); S76_ERROR("Could not register EC device\n");
}
err = ap_led_init(&dev->dev);
if (unlikely(err)) {
S76_ERROR("Could not register LED device\n");
}
err = kb_led_init(&dev->dev);
if (unlikely(err)) {
S76_ERROR("Could not register LED device\n");
}
err = s76_input_init(&dev->dev);
if (unlikely(err)) {
S76_ERROR("Could not register input device\n");
}
if (device_create_file(&dev->dev, &dev_attr_kb_brightness) != 0) {
S76_ERROR("Sysfs attribute creation failed for brightness\n");
}
if (device_create_file(&dev->dev, &dev_attr_kb_state) != 0) {
S76_ERROR("Sysfs attribute creation failed for state\n");
}
if (device_create_file(&dev->dev, &dev_attr_kb_mode) != 0) {
S76_ERROR("Sysfs attribute creation failed for mode\n");
}
if (device_create_file(&dev->dev, &dev_attr_kb_color) != 0) {
S76_ERROR("Sysfs attribute creation failed for color\n");
}
if (kb_backlight.ops) {
kb_backlight.ops->init();
}
#ifdef S76_HAS_HWMON
s76_hwmon_init(&dev->dev);
#endif
err = wmi_install_notify_handler(S76_EVENT_GUID, s76_wmi_notify, NULL);
if (unlikely(ACPI_FAILURE(err))) {
S76_ERROR("Could not register WMI notify handler (%0#6x)\n", err);
return -EIO; return -EIO;
} }
@ -193,16 +238,27 @@ static int s76_probe(struct platform_device *dev) {
//i8042_command(NULL, 0x97); //i8042_command(NULL, 0x97);
//i8042_unlock_chip(); //i8042_unlock_chip();
if (kb_backlight.ops) {
kb_backlight.ops->init();
}
return 0; return 0;
} }
static int s76_remove(struct platform_device *dev) { static int s76_remove(struct platform_device *dev) {
wmi_remove_notify_handler(S76_EVENT_GUID); wmi_remove_notify_handler(S76_EVENT_GUID);
#ifdef S76_HAS_HWMON
s76_hwmon_fini(&dev->dev);
#endif
device_remove_file(&dev->dev, &dev_attr_kb_color);
device_remove_file(&dev->dev, &dev_attr_kb_mode);
device_remove_file(&dev->dev, &dev_attr_kb_state);
device_remove_file(&dev->dev, &dev_attr_kb_brightness);
s76_input_exit();
kb_led_exit();
ap_led_exit();
ec_exit();
return 0; return 0;
} }
@ -280,59 +336,10 @@ static int __init s76_init(void) {
return PTR_ERR(s76_platform_device); return PTR_ERR(s76_platform_device);
} }
err = s76_input_init();
if (unlikely(err)) {
S76_ERROR("Could not register input device\n");
}
err = s76_led_init();
if (unlikely(err)) {
S76_ERROR("Could not register LED device\n");
}
if (device_create_file(&s76_platform_device->dev, &dev_attr_kb_brightness) != 0) {
S76_ERROR("Sysfs attribute creation failed for brightness\n");
}
if (device_create_file(&s76_platform_device->dev, &dev_attr_kb_state) != 0) {
S76_ERROR("Sysfs attribute creation failed for state\n");
}
if (device_create_file(&s76_platform_device->dev, &dev_attr_kb_mode) != 0) {
S76_ERROR("Sysfs attribute creation failed for mode\n");
}
if (device_create_file(&s76_platform_device->dev, &dev_attr_kb_color) != 0) {
S76_ERROR("Sysfs attribute creation failed for color\n");
}
#ifdef S76_HAS_HWMON
s76_hwmon_init(&s76_platform_device->dev);
#endif
err = ec_init();
if (unlikely(err)) {
S76_ERROR("Could not register EC device\n");
}
return 0; return 0;
} }
static void __exit s76_exit(void) { static void __exit s76_exit(void) {
ec_exit();
#ifdef S76_HAS_HWMON
s76_hwmon_fini(&s76_platform_device->dev);
#endif
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_color);
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_mode);
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_state);
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_brightness);
s76_led_exit();
s76_input_exit();
platform_device_unregister(s76_platform_device); platform_device_unregister(s76_platform_device);
platform_driver_unregister(&s76_platform_driver); platform_driver_unregister(&s76_platform_driver);
} }