System76 DKMS driver
This commit is contained in:
commit
0872338fab
|
|
@ -0,0 +1,22 @@
|
|||
MODULE=system76
|
||||
VERSION=0.1
|
||||
|
||||
all:
|
||||
-make uninstall
|
||||
make install
|
||||
|
||||
reload:
|
||||
-make remove
|
||||
make && make insert || cat /var/lib/dkms/$(MODULE)/$(VERSION)/build/make.log
|
||||
|
||||
install:
|
||||
sudo dkms install $(PWD)/$(MODULE) --force
|
||||
|
||||
uninstall:
|
||||
sudo dkms remove $(MODULE)/$(VERSION) --all
|
||||
|
||||
insert:
|
||||
sudo modprobe $(MODULE)
|
||||
|
||||
remove:
|
||||
sudo modprobe -r $(MODULE)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
obj-m := system76.o
|
||||
KVERSION := $(shell uname -r)
|
||||
|
||||
all:
|
||||
$(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
$(MAKE) -C /lib/modules/$(KVERSION)/build M=$(PWD) clean
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
PACKAGE_NAME="system76"
|
||||
PACKAGE_VERSION="0.1"
|
||||
CLEAN="make clean"
|
||||
MAKE[0]="make all KVERSION=$kernelver"
|
||||
BUILT_MODULE_NAME[0]="system76"
|
||||
DEST_MODULE_LOCATION[0]="/updates"
|
||||
AUTOINSTALL="yes"
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* dmi.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 int __init s76_dmi_matched(const struct dmi_system_id *id)
|
||||
{
|
||||
S76_INFO("Model %s found\n", id->ident);
|
||||
kb_backlight.ops = id->driver_data;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define DMI_TABLE(PRODUCT, DATA) { \
|
||||
.ident = "System76 " PRODUCT, \
|
||||
.matches = { \
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "System76"), \
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, PRODUCT), \
|
||||
}, \
|
||||
.callback = s76_dmi_matched, \
|
||||
.driver_data = &DATA, \
|
||||
}
|
||||
|
||||
static struct dmi_system_id s76_dmi_table[] __initdata = {
|
||||
DMI_TABLE("bonw13", kb_full_color_ops),
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(dmi, s76_dmi_table);
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* fan.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/>.
|
||||
*/
|
||||
|
||||
#if S76_HAS_HWMON
|
||||
struct s76_hwmon {
|
||||
struct device *dev;
|
||||
};
|
||||
|
||||
static struct s76_hwmon *s76_hwmon = NULL;
|
||||
|
||||
static int
|
||||
s76_read_fan(int idx)
|
||||
{
|
||||
u8 value;
|
||||
int raw_rpm;
|
||||
ec_read(0xd0 + 0x2 * idx, &value);
|
||||
raw_rpm = value << 8;
|
||||
ec_read(0xd1 + 0x2 * idx, &value);
|
||||
raw_rpm += value;
|
||||
if (!raw_rpm)
|
||||
return 0;
|
||||
return 2156220 / raw_rpm;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_name(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, S76_DRIVER_NAME "\n");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_fan1_input(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%i\n", s76_read_fan(0));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_fan1_label(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "CPU fan\n");
|
||||
}
|
||||
|
||||
#ifdef EXPERIMENTAL
|
||||
static ssize_t
|
||||
s76_hwmon_show_fan2_input(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "%i\n", s76_read_fan(1));
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_fan2_label(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "GPU fan\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_temp1_input(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
u8 value;
|
||||
ec_read(0x07, &value);
|
||||
return sprintf(buf, "%i\n", value * 1000);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_temp1_label(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "CPU temperature\n");
|
||||
}
|
||||
|
||||
#ifdef EXPERIMENTAL
|
||||
static ssize_t
|
||||
s76_hwmon_show_temp2_input(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
u8 value;
|
||||
ec_read(0xcd, &value);
|
||||
return sprintf(buf, "%i\n", value * 1000);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
s76_hwmon_show_temp2_label(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sprintf(buf, "GPU temperature\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static SENSOR_DEVICE_ATTR(name, S_IRUGO, s76_hwmon_show_name, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, s76_hwmon_show_fan1_input, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, s76_hwmon_show_fan1_label, NULL, 0);
|
||||
#ifdef EXPERIMENTAL
|
||||
static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, s76_hwmon_show_fan2_input, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, s76_hwmon_show_fan2_label, NULL, 0);
|
||||
#endif
|
||||
static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, s76_hwmon_show_temp1_input, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, s76_hwmon_show_temp1_label, NULL, 0);
|
||||
#ifdef EXPERIMENTAL
|
||||
static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, s76_hwmon_show_temp2_input, NULL, 0);
|
||||
static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, s76_hwmon_show_temp2_label, NULL, 0);
|
||||
#endif
|
||||
|
||||
static struct attribute *hwmon_default_attributes[] = {
|
||||
&sensor_dev_attr_name.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan1_label.dev_attr.attr,
|
||||
#ifdef EXPERIMENTAL
|
||||
&sensor_dev_attr_fan2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_fan2_label.dev_attr.attr,
|
||||
#endif
|
||||
&sensor_dev_attr_temp1_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp1_label.dev_attr.attr,
|
||||
#ifdef EXPERIMENTAL
|
||||
&sensor_dev_attr_temp2_input.dev_attr.attr,
|
||||
&sensor_dev_attr_temp2_label.dev_attr.attr,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group hwmon_default_attrgroup = {
|
||||
.attrs = hwmon_default_attributes,
|
||||
};
|
||||
|
||||
static int
|
||||
s76_hwmon_init(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
s76_hwmon = kzalloc(sizeof(*s76_hwmon), GFP_KERNEL);
|
||||
if (!s76_hwmon)
|
||||
return -ENOMEM;
|
||||
s76_hwmon->dev = hwmon_device_register(dev);
|
||||
if (IS_ERR(s76_hwmon->dev)) {
|
||||
ret = PTR_ERR(s76_hwmon->dev);
|
||||
s76_hwmon->dev = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sysfs_create_group(&s76_hwmon->dev->kobj, &hwmon_default_attrgroup);
|
||||
if (ret)
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
s76_hwmon_fini(struct device *dev)
|
||||
{
|
||||
if (!s76_hwmon || !s76_hwmon->dev)
|
||||
return 0;
|
||||
sysfs_remove_group(&s76_hwmon->dev->kobj, &hwmon_default_attrgroup);
|
||||
hwmon_device_unregister(s76_hwmon->dev);
|
||||
kfree(s76_hwmon);
|
||||
return 0;
|
||||
}
|
||||
#endif // S76_HAS_HWMON
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
* input.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/>.
|
||||
*/
|
||||
|
||||
#define AIRPLANE_KEY KEY_RFKILL
|
||||
|
||||
static struct input_dev *s76_input_device;
|
||||
static DEFINE_MUTEX(s76_input_report_mutex);
|
||||
|
||||
#define POLL_FREQ_MIN 1
|
||||
#define POLL_FREQ_MAX 20
|
||||
#define POLL_FREQ_DEFAULT 5
|
||||
|
||||
static int param_set_poll_freq(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = param_set_byte(val, kp);
|
||||
|
||||
if (!ret)
|
||||
*((unsigned char *) kp->arg) = clamp_t(unsigned char,
|
||||
*((unsigned char *) kp->arg),
|
||||
POLL_FREQ_MIN, POLL_FREQ_MAX);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static const struct kernel_param_ops param_ops_poll_freq = {
|
||||
.set = param_set_poll_freq,
|
||||
.get = param_get_byte,
|
||||
};
|
||||
|
||||
static unsigned char param_poll_freq = POLL_FREQ_DEFAULT;
|
||||
#define param_check_poll_freq param_check_byte
|
||||
module_param_named(poll_freq, param_poll_freq, poll_freq, S_IRUSR);
|
||||
MODULE_PARM_DESC(poll_freq, "Set polling frequency");
|
||||
|
||||
static struct task_struct *s76_input_polling_task;
|
||||
|
||||
static int s76_input_polling_thread(void *data) {
|
||||
S76_INFO("Polling thread started (PID: %i), polling at %i Hz\n",
|
||||
current->pid, param_poll_freq);
|
||||
|
||||
while (!kthread_should_stop()) {
|
||||
|
||||
u8 byte;
|
||||
|
||||
ec_read(0xDB, &byte);
|
||||
if (byte & 0x40) {
|
||||
ec_write(0xDB, byte & ~0x40);
|
||||
|
||||
S76_INFO("Airplane-Mode Hotkey pressed (EC)\n");
|
||||
|
||||
mutex_lock(&s76_input_report_mutex);
|
||||
|
||||
input_report_key(s76_input_device, AIRPLANE_KEY, 1);
|
||||
input_sync(s76_input_device);
|
||||
|
||||
input_report_key(s76_input_device, AIRPLANE_KEY, 0);
|
||||
input_sync(s76_input_device);
|
||||
|
||||
S76_INFO("Led status: %d",
|
||||
airplane_led_get(&airplane_led));
|
||||
|
||||
airplane_led_set(&airplane_led,
|
||||
(airplane_led_get(&airplane_led) ? 0 : 1));
|
||||
|
||||
mutex_unlock(&s76_input_report_mutex);
|
||||
}
|
||||
msleep_interruptible(1000 / param_poll_freq);
|
||||
}
|
||||
|
||||
S76_INFO("Polling thread exiting\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void airplane_wmi(void) {
|
||||
S76_INFO("Airplane-Mode Hotkey pressed (WMI)\n");
|
||||
|
||||
if (s76_input_polling_task) {
|
||||
S76_INFO("Stopping polling thread\n");
|
||||
kthread_stop(s76_input_polling_task);
|
||||
s76_input_polling_task = NULL;
|
||||
}
|
||||
|
||||
mutex_lock(&s76_input_report_mutex);
|
||||
|
||||
input_report_key(s76_input_device, AIRPLANE_KEY, 1);
|
||||
input_sync(s76_input_device);
|
||||
|
||||
input_report_key(s76_input_device, AIRPLANE_KEY, 0);
|
||||
input_sync(s76_input_device);
|
||||
|
||||
mutex_unlock(&s76_input_report_mutex);
|
||||
}
|
||||
|
||||
static int s76_input_open(struct input_dev *dev)
|
||||
{
|
||||
s76_input_polling_task = kthread_run(
|
||||
s76_input_polling_thread,
|
||||
NULL, "system76-polld");
|
||||
|
||||
if (unlikely(IS_ERR(s76_input_polling_task))) {
|
||||
s76_input_polling_task = NULL;
|
||||
S76_ERROR("Could not create polling thread\n");
|
||||
return PTR_ERR(s76_input_polling_task);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void s76_input_close(struct input_dev *dev)
|
||||
{
|
||||
if (unlikely(IS_ERR_OR_NULL(s76_input_polling_task)))
|
||||
return;
|
||||
|
||||
kthread_stop(s76_input_polling_task);
|
||||
s76_input_polling_task = NULL;
|
||||
}
|
||||
|
||||
static int __init s76_input_init(void)
|
||||
{
|
||||
int err;
|
||||
u8 byte;
|
||||
|
||||
s76_input_device = input_allocate_device();
|
||||
if (unlikely(!s76_input_device)) {
|
||||
S76_ERROR("Error allocating input device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
s76_input_device->name = "System76 Airplane-Mode Hotkey";
|
||||
s76_input_device->phys = "system76/input0";
|
||||
s76_input_device->id.bustype = BUS_HOST;
|
||||
s76_input_device->dev.parent = &s76_platform_device->dev;
|
||||
set_bit(EV_KEY, s76_input_device->evbit);
|
||||
set_bit(AIRPLANE_KEY, s76_input_device->keybit);
|
||||
|
||||
s76_input_device->open = s76_input_open;
|
||||
s76_input_device->close = s76_input_close;
|
||||
|
||||
ec_read(0xDB, &byte);
|
||||
ec_write(0xDB, byte & ~0x40);
|
||||
|
||||
err = input_register_device(s76_input_device);
|
||||
if (unlikely(err)) {
|
||||
S76_ERROR("Error registering input device\n");
|
||||
goto err_free_input_device;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_input_device:
|
||||
input_free_device(s76_input_device);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit s76_input_exit(void)
|
||||
{
|
||||
if (unlikely(!s76_input_device))
|
||||
return;
|
||||
|
||||
input_unregister_device(s76_input_device);
|
||||
s76_input_device = NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
* kb.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/>.
|
||||
*/
|
||||
|
||||
#define SET_KB_LED 0x67 /* 103 */
|
||||
|
||||
#define COLORS { \
|
||||
C(white, 0xFFFFFF), \
|
||||
C(blue, 0x0000FF), \
|
||||
C(red, 0xFF0000), \
|
||||
C(magenta, 0xFF00FF), \
|
||||
C(green, 0x00FF00), \
|
||||
C(cyan, 0x00FFFF), \
|
||||
C(yellow, 0xFFFF00), \
|
||||
}
|
||||
#undef C
|
||||
|
||||
|
||||
#define C(n, v) KB_COLOR_##n
|
||||
enum kb_color COLORS;
|
||||
#undef C
|
||||
|
||||
union kb_rgb_color {
|
||||
u32 rgb;
|
||||
struct { u32 b:8, g:8, r:8, : 8; };
|
||||
};
|
||||
|
||||
#define C(n, v) { .name = #n, .value = { .rgb = v, }, }
|
||||
struct {
|
||||
const char *const name;
|
||||
union kb_rgb_color value;
|
||||
} kb_colors[] = COLORS;
|
||||
#undef C
|
||||
|
||||
#define KB_COLOR_DEFAULT KB_COLOR_white
|
||||
#define KB_BRIGHTNESS_MAX 10
|
||||
#define KB_BRIGHTNESS_DEFAULT KB_BRIGHTNESS_MAX
|
||||
|
||||
static int param_set_kb_color(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!val)
|
||||
return -EINVAL;
|
||||
|
||||
if (!val[0]) {
|
||||
*((enum kb_color *) kp->arg) = KB_COLOR_DEFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
|
||||
if (!strcmp(val, kb_colors[i].name)) {
|
||||
*((enum kb_color *) kp->arg) = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int param_get_kb_color(char *buffer, const struct kernel_param *kp)
|
||||
{
|
||||
return sprintf(buffer, "%s",
|
||||
kb_colors[*((enum kb_color *) kp->arg)].name);
|
||||
}
|
||||
|
||||
static const struct kernel_param_ops param_ops_kb_color = {
|
||||
.set = param_set_kb_color,
|
||||
.get = param_get_kb_color,
|
||||
};
|
||||
|
||||
static enum kb_color param_kb_color[] = { [0 ... 3] = KB_COLOR_DEFAULT };
|
||||
static int param_kb_color_num;
|
||||
#define param_check_kb_color(name, p) __param_check(name, p, enum kb_color)
|
||||
module_param_array_named(kb_color, param_kb_color, kb_color,
|
||||
¶m_kb_color_num, S_IRUSR);
|
||||
MODULE_PARM_DESC(kb_color, "Set the color(s) of the keyboard (sections)");
|
||||
|
||||
|
||||
static int param_set_kb_brightness(const char *val,
|
||||
const struct kernel_param *kp)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = param_set_byte(val, kp);
|
||||
|
||||
if (!ret && *((unsigned char *) kp->arg) > KB_BRIGHTNESS_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct kernel_param_ops param_ops_kb_brightness = {
|
||||
.set = param_set_kb_brightness,
|
||||
.get = param_get_byte,
|
||||
};
|
||||
|
||||
static unsigned char param_kb_brightness = KB_BRIGHTNESS_DEFAULT;
|
||||
#define param_check_kb_brightness param_check_byte
|
||||
module_param_named(kb_brightness, param_kb_brightness, kb_brightness, S_IRUSR);
|
||||
MODULE_PARM_DESC(kb_brightness, "Set the brightness of the keyboard backlight");
|
||||
|
||||
|
||||
static bool param_kb_off;
|
||||
module_param_named(kb_off, param_kb_off, bool, S_IRUSR);
|
||||
MODULE_PARM_DESC(kb_off, "Switch keyboard backlight off");
|
||||
|
||||
static bool param_kb_cycle_colors = true;
|
||||
module_param_named(kb_cycle_colors, param_kb_cycle_colors, bool, S_IRUSR);
|
||||
MODULE_PARM_DESC(kb_cycle_colors, "Cycle colors rather than modes");
|
||||
|
||||
static struct {
|
||||
enum kb_extra {
|
||||
KB_HAS_EXTRA_TRUE,
|
||||
KB_HAS_EXTRA_FALSE,
|
||||
} extra;
|
||||
|
||||
enum kb_state {
|
||||
KB_STATE_OFF,
|
||||
KB_STATE_ON,
|
||||
} state;
|
||||
|
||||
struct {
|
||||
unsigned left;
|
||||
unsigned center;
|
||||
unsigned right;
|
||||
unsigned extra;
|
||||
} color;
|
||||
|
||||
unsigned brightness;
|
||||
|
||||
enum kb_mode {
|
||||
KB_MODE_RANDOM_COLOR,
|
||||
KB_MODE_CUSTOM,
|
||||
KB_MODE_BREATHE,
|
||||
KB_MODE_CYCLE,
|
||||
KB_MODE_WAVE,
|
||||
KB_MODE_DANCE,
|
||||
KB_MODE_TEMPO,
|
||||
KB_MODE_FLASH,
|
||||
} mode;
|
||||
|
||||
struct kb_backlight_ops {
|
||||
void (*set_state)(enum kb_state state);
|
||||
void (*set_color)(unsigned left, unsigned center,
|
||||
unsigned right, unsigned extra);
|
||||
void (*set_brightness)(unsigned brightness);
|
||||
void (*set_mode)(enum kb_mode);
|
||||
void (*init)(void);
|
||||
} *ops;
|
||||
|
||||
} kb_backlight = { .ops = NULL, };
|
||||
|
||||
|
||||
static void kb_dec_brightness(void)
|
||||
{
|
||||
if (kb_backlight.state == KB_STATE_OFF)
|
||||
return;
|
||||
if (kb_backlight.brightness == 0)
|
||||
return;
|
||||
|
||||
S76_DEBUG();
|
||||
|
||||
kb_backlight.ops->set_brightness(kb_backlight.brightness - 1);
|
||||
}
|
||||
|
||||
static void kb_inc_brightness(void)
|
||||
{
|
||||
if (kb_backlight.state == KB_STATE_OFF)
|
||||
return;
|
||||
|
||||
S76_DEBUG();
|
||||
|
||||
kb_backlight.ops->set_brightness(kb_backlight.brightness + 1);
|
||||
}
|
||||
|
||||
static void kb_toggle_state(void)
|
||||
{
|
||||
switch (kb_backlight.state) {
|
||||
case KB_STATE_OFF:
|
||||
kb_backlight.ops->set_state(KB_STATE_ON);
|
||||
break;
|
||||
case KB_STATE_ON:
|
||||
kb_backlight.ops->set_state(KB_STATE_OFF);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
}
|
||||
|
||||
static void kb_next_mode(void)
|
||||
{
|
||||
static enum kb_mode modes[] = {
|
||||
KB_MODE_RANDOM_COLOR,
|
||||
KB_MODE_DANCE,
|
||||
KB_MODE_TEMPO,
|
||||
KB_MODE_FLASH,
|
||||
KB_MODE_WAVE,
|
||||
KB_MODE_BREATHE,
|
||||
KB_MODE_CYCLE,
|
||||
KB_MODE_CUSTOM,
|
||||
};
|
||||
|
||||
size_t i;
|
||||
|
||||
if (kb_backlight.state == KB_STATE_OFF)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(modes); i++) {
|
||||
if (modes[i] == kb_backlight.mode)
|
||||
break;
|
||||
}
|
||||
|
||||
BUG_ON(i == ARRAY_SIZE(modes));
|
||||
|
||||
kb_backlight.ops->set_mode(modes[(i + 1) % ARRAY_SIZE(modes)]);
|
||||
}
|
||||
|
||||
static void kb_next_color(void)
|
||||
{
|
||||
size_t i;
|
||||
unsigned int nc;
|
||||
|
||||
if (kb_backlight.state == KB_STATE_OFF)
|
||||
return;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(kb_colors); i++) {
|
||||
if (i == kb_backlight.color.left)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i + 1 >= ARRAY_SIZE(kb_colors))
|
||||
nc = 0;
|
||||
else
|
||||
nc = i + 1;
|
||||
|
||||
kb_backlight.ops->set_color(nc, nc, nc, nc);
|
||||
}
|
||||
|
||||
/* full color backlight keyboard */
|
||||
|
||||
static void kb_full_color__set_color(unsigned left, unsigned center,
|
||||
unsigned right, unsigned extra)
|
||||
{
|
||||
u32 cmd;
|
||||
|
||||
S76_INFO(
|
||||
"Left: %s (%X), Center: %s (%X), Right: %s (%X), Extra: %s (%X)\n",
|
||||
kb_colors[left].name, (unsigned int)kb_colors[left].value.rgb,
|
||||
kb_colors[center].name, (unsigned int)kb_colors[center].value.rgb,
|
||||
kb_colors[right].name, (unsigned int)kb_colors[right].value.rgb,
|
||||
kb_colors[extra].name, (unsigned int)kb_colors[extra].value.rgb
|
||||
);
|
||||
|
||||
cmd = 0xF0000000;
|
||||
cmd |= kb_colors[left].value.b << 16;
|
||||
cmd |= kb_colors[left].value.r << 8;
|
||||
cmd |= kb_colors[left].value.g << 0;
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
|
||||
kb_backlight.color.left = left;
|
||||
|
||||
cmd = 0xF1000000;
|
||||
cmd |= kb_colors[center].value.b << 16;
|
||||
cmd |= kb_colors[center].value.r << 8;
|
||||
cmd |= kb_colors[center].value.g << 0;
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
|
||||
kb_backlight.color.center = center;
|
||||
|
||||
cmd = 0xF2000000;
|
||||
cmd |= kb_colors[right].value.b << 16;
|
||||
cmd |= kb_colors[right].value.r << 8;
|
||||
cmd |= kb_colors[right].value.g << 0;
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
|
||||
kb_backlight.color.right = right;
|
||||
|
||||
if (kb_backlight.extra == KB_HAS_EXTRA_TRUE) {
|
||||
cmd = 0xF3000000;
|
||||
cmd |= kb_colors[extra].value.b << 16;
|
||||
cmd |= kb_colors[extra].value.r << 8;
|
||||
cmd |= kb_colors[extra].value.g << 0;
|
||||
|
||||
if(!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
|
||||
kb_backlight.color.extra = extra;
|
||||
}
|
||||
|
||||
kb_backlight.mode = KB_MODE_CUSTOM;
|
||||
}
|
||||
|
||||
static void kb_full_color__set_brightness(unsigned i)
|
||||
{
|
||||
u8 lvl_to_raw[] = { 63, 126, 189, 252 };
|
||||
|
||||
i = clamp_t(unsigned, i, 0, ARRAY_SIZE(lvl_to_raw) - 1);
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED,
|
||||
0xF4000000 | lvl_to_raw[i], NULL))
|
||||
kb_backlight.brightness = i;
|
||||
}
|
||||
|
||||
static void kb_full_color__set_mode(unsigned mode)
|
||||
{
|
||||
static u32 cmds[] = {
|
||||
[KB_MODE_BREATHE] = 0x1002a000,
|
||||
[KB_MODE_CUSTOM] = 0,
|
||||
[KB_MODE_CYCLE] = 0x33010000,
|
||||
[KB_MODE_DANCE] = 0x80000000,
|
||||
[KB_MODE_FLASH] = 0xA0000000,
|
||||
[KB_MODE_RANDOM_COLOR] = 0x70000000,
|
||||
[KB_MODE_TEMPO] = 0x90000000,
|
||||
[KB_MODE_WAVE] = 0xB0000000,
|
||||
};
|
||||
|
||||
BUG_ON(mode >= ARRAY_SIZE(cmds));
|
||||
|
||||
s76_wmi_evaluate_wmbb_method(SET_KB_LED, 0x10000000, NULL);
|
||||
|
||||
if (mode == KB_MODE_CUSTOM) {
|
||||
kb_full_color__set_color(kb_backlight.color.left,
|
||||
kb_backlight.color.center,
|
||||
kb_backlight.color.right,
|
||||
kb_backlight.color.extra);
|
||||
kb_full_color__set_brightness(kb_backlight.brightness);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmds[mode], NULL))
|
||||
kb_backlight.mode = mode;
|
||||
}
|
||||
|
||||
static void kb_full_color__set_state(enum kb_state state)
|
||||
{
|
||||
u32 cmd = 0xE0000000;
|
||||
|
||||
S76_DEBUG("State: %d\n", state);
|
||||
|
||||
switch (state) {
|
||||
case KB_STATE_OFF:
|
||||
cmd |= 0x003001;
|
||||
break;
|
||||
case KB_STATE_ON:
|
||||
cmd |= 0x07F001;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (!s76_wmi_evaluate_wmbb_method(SET_KB_LED, cmd, NULL))
|
||||
kb_backlight.state = state;
|
||||
}
|
||||
|
||||
static void kb_full_color__init(void)
|
||||
{
|
||||
S76_DEBUG();
|
||||
|
||||
kb_backlight.extra = KB_HAS_EXTRA_FALSE;
|
||||
|
||||
kb_full_color__set_state(param_kb_off ? KB_STATE_OFF : KB_STATE_ON);
|
||||
kb_full_color__set_color(param_kb_color[0], param_kb_color[1],
|
||||
param_kb_color[2], param_kb_color[3]);
|
||||
kb_full_color__set_brightness(param_kb_brightness);
|
||||
}
|
||||
|
||||
static struct kb_backlight_ops kb_full_color_ops = {
|
||||
.set_state = kb_full_color__set_state,
|
||||
.set_color = kb_full_color__set_color,
|
||||
.set_brightness = kb_full_color__set_brightness,
|
||||
.set_mode = kb_full_color__set_mode,
|
||||
.init = kb_full_color__init,
|
||||
};
|
||||
|
||||
static void kb_full_color__init_extra(void)
|
||||
{
|
||||
S76_DEBUG();
|
||||
|
||||
kb_backlight.extra = KB_HAS_EXTRA_TRUE;
|
||||
|
||||
kb_full_color__set_state(param_kb_off ? KB_STATE_OFF : KB_STATE_ON);
|
||||
kb_full_color__set_color(param_kb_color[0], param_kb_color[1],
|
||||
param_kb_color[2], param_kb_color[3]);
|
||||
kb_full_color__set_brightness(param_kb_brightness);
|
||||
}
|
||||
|
||||
static struct kb_backlight_ops kb_full_color_with_extra_ops = {
|
||||
.set_state = kb_full_color__set_state,
|
||||
.set_color = kb_full_color__set_color,
|
||||
.set_brightness = kb_full_color__set_brightness,
|
||||
.set_mode = kb_full_color__set_mode,
|
||||
.init = kb_full_color__init_extra,
|
||||
};
|
||||
|
||||
static void kb_wmi(u32 event) {
|
||||
if (!kb_backlight.ops)
|
||||
return;
|
||||
|
||||
switch (event) {
|
||||
case 0x81:
|
||||
kb_dec_brightness();
|
||||
break;
|
||||
case 0x82:
|
||||
kb_inc_brightness();
|
||||
break;
|
||||
case 0x83:
|
||||
if (!param_kb_cycle_colors)
|
||||
kb_next_mode();
|
||||
else
|
||||
kb_next_color();
|
||||
break;
|
||||
case 0x9F:
|
||||
kb_toggle_state();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sysfs interface */
|
||||
|
||||
static ssize_t s76_brightness_show(struct device *child,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", kb_backlight.brightness);
|
||||
}
|
||||
|
||||
static ssize_t s76_brightness_store(struct device *child,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (!kb_backlight.ops)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtouint(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
kb_backlight.ops->set_brightness(val);
|
||||
|
||||
return ret ? : size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(kb_brightness, 0644,
|
||||
s76_brightness_show, s76_brightness_store);
|
||||
|
||||
static ssize_t s76_state_show(struct device *child,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", kb_backlight.state);
|
||||
}
|
||||
|
||||
static ssize_t s76_state_store(struct device *child,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (!kb_backlight.ops)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtouint(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = clamp_t(unsigned, val, 0, 1);
|
||||
kb_backlight.ops->set_state(val);
|
||||
|
||||
return ret ? : size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(kb_state, 0644,
|
||||
s76_state_show, s76_state_store);
|
||||
|
||||
static ssize_t s76_mode_show(struct device *child,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", kb_backlight.mode);
|
||||
}
|
||||
|
||||
static ssize_t s76_mode_store(struct device *child,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
static enum kb_mode modes[] = {
|
||||
KB_MODE_RANDOM_COLOR,
|
||||
KB_MODE_CUSTOM,
|
||||
KB_MODE_BREATHE,
|
||||
KB_MODE_CYCLE,
|
||||
KB_MODE_WAVE,
|
||||
KB_MODE_DANCE,
|
||||
KB_MODE_TEMPO,
|
||||
KB_MODE_FLASH,
|
||||
};
|
||||
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
if (!kb_backlight.ops)
|
||||
return -EINVAL;
|
||||
|
||||
ret = kstrtouint(buf, 0, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
val = clamp_t(unsigned, val, 0, 7);
|
||||
kb_backlight.ops->set_mode(modes[val]);
|
||||
|
||||
return ret ? : size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(kb_mode, 0644,
|
||||
s76_mode_show, s76_mode_store);
|
||||
|
||||
static ssize_t s76_color_show(struct device *child,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
if (kb_backlight.extra == KB_HAS_EXTRA_TRUE)
|
||||
return sprintf(buf, "%s %s %s %s\n",
|
||||
kb_colors[kb_backlight.color.left].name,
|
||||
kb_colors[kb_backlight.color.center].name,
|
||||
kb_colors[kb_backlight.color.right].name,
|
||||
kb_colors[kb_backlight.color.extra].name);
|
||||
else
|
||||
return sprintf(buf, "%s %s %s\n",
|
||||
kb_colors[kb_backlight.color.left].name,
|
||||
kb_colors[kb_backlight.color.center].name,
|
||||
kb_colors[kb_backlight.color.right].name);
|
||||
}
|
||||
|
||||
static ssize_t s76_color_store(struct device *child,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
unsigned int i, j;
|
||||
unsigned int val[4] = {0};
|
||||
char left[8];
|
||||
char right[8];
|
||||
char center[8];
|
||||
char extra[8];
|
||||
|
||||
if (!kb_backlight.ops)
|
||||
return -EINVAL;
|
||||
|
||||
i = sscanf(buf, "%7s %7s %7s %7s", left, center, right, extra);
|
||||
|
||||
if (i == 1) {
|
||||
for (j = 0; j < ARRAY_SIZE(kb_colors); j++) {
|
||||
if (!strcmp(left, kb_colors[j].name))
|
||||
val[0] = j;
|
||||
}
|
||||
val[0] = clamp_t(unsigned, val[0], 0, ARRAY_SIZE(kb_colors));
|
||||
val[3] = val[2] = val[1] = val[0];
|
||||
|
||||
} else if (i == 3 || i == 4) {
|
||||
for (j = 0; j < ARRAY_SIZE(kb_colors); j++) {
|
||||
if (!strcmp(left, kb_colors[j].name))
|
||||
val[0] = j;
|
||||
if (!strcmp(center, kb_colors[j].name))
|
||||
val[1] = j;
|
||||
if (!strcmp(right, kb_colors[j].name))
|
||||
val[2] = j;
|
||||
if (!strcmp(extra, kb_colors[j].name))
|
||||
val[3] = j;
|
||||
}
|
||||
val[0] = clamp_t(unsigned, val[0], 0, ARRAY_SIZE(kb_colors));
|
||||
val[1] = clamp_t(unsigned, val[1], 0, ARRAY_SIZE(kb_colors));
|
||||
val[2] = clamp_t(unsigned, val[2], 0, ARRAY_SIZE(kb_colors));
|
||||
val[3] = clamp_t(unsigned, val[3], 0, ARRAY_SIZE(kb_colors));
|
||||
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
kb_backlight.ops->set_color(val[0], val[1], val[2], val[3]);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR(kb_color, 0644,
|
||||
s76_color_show, s76_color_store);
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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 bool param_led_invert;
|
||||
module_param_named(led_invert, param_led_invert, bool, 0);
|
||||
MODULE_PARM_DESC(led_invert, "Invert airplane mode LED state.");
|
||||
|
||||
static struct workqueue_struct *led_workqueue;
|
||||
|
||||
static struct _led_work {
|
||||
struct work_struct work;
|
||||
int wk;
|
||||
} led_work;
|
||||
|
||||
static void airplane_led_update(struct work_struct *work)
|
||||
{
|
||||
u8 byte;
|
||||
struct _led_work *w;
|
||||
|
||||
w = container_of(work, struct _led_work, work);
|
||||
|
||||
ec_read(0xD9, &byte);
|
||||
|
||||
if (param_led_invert)
|
||||
ec_write(0xD9, w->wk ? byte & ~0x40 : byte | 0x40);
|
||||
else
|
||||
ec_write(0xD9, w->wk ? byte | 0x40 : byte & ~0x40);
|
||||
|
||||
/* wmbb 0x6C 1 (?) */
|
||||
}
|
||||
|
||||
static enum led_brightness airplane_led_get(struct led_classdev *led_cdev)
|
||||
{
|
||||
u8 byte;
|
||||
|
||||
ec_read(0xD9, &byte);
|
||||
|
||||
if (param_led_invert)
|
||||
return byte & 0x40 ? LED_OFF : LED_FULL;
|
||||
else
|
||||
return byte & 0x40 ? LED_FULL : LED_OFF;
|
||||
}
|
||||
|
||||
/* must not sleep */
|
||||
static void airplane_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
S76_INFO("Set airplane LED to %X", value);
|
||||
led_work.wk = value;
|
||||
queue_work(led_workqueue, &led_work.work);
|
||||
}
|
||||
|
||||
static struct led_classdev airplane_led = {
|
||||
.name = "system76::airplane",
|
||||
.brightness_get = airplane_led_get,
|
||||
.brightness_set = airplane_led_set,
|
||||
.max_brightness = 1,
|
||||
.default_trigger = "rfkill-any"
|
||||
};
|
||||
|
||||
static int __init s76_led_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
param_led_invert = TRUE;
|
||||
|
||||
led_workqueue = create_singlethread_workqueue("led_workqueue");
|
||||
if (unlikely(!led_workqueue))
|
||||
return -ENOMEM;
|
||||
|
||||
INIT_WORK(&led_work.work, airplane_led_update);
|
||||
|
||||
err = led_classdev_register(&s76_platform_device->dev,
|
||||
&airplane_led);
|
||||
if (unlikely(err))
|
||||
goto err_destroy_workqueue;
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_workqueue:
|
||||
destroy_workqueue(led_workqueue);
|
||||
led_workqueue = NULL;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit s76_led_exit(void)
|
||||
{
|
||||
if (!IS_ERR_OR_NULL(airplane_led.dev))
|
||||
led_classdev_unregister(&airplane_led);
|
||||
if (led_workqueue)
|
||||
destroy_workqueue(led_workqueue);
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* system76.c
|
||||
*
|
||||
* Copyright (C) 2017 Jeremy Soller <jeremy@system76.com>
|
||||
*
|
||||
* Copyright (C) 2014-2016 Arnoud Willemsen <mail@lynthium.com>
|
||||
*
|
||||
* Based on tuxedo-wmi by TUXEDO Computers GmbH
|
||||
* Copyright (C) 2013-2015 TUXEDO Computers GmbH <tux@tuxedocomputers.com>
|
||||
* Custom build Linux Notebooks and Computers: www.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/>.
|
||||
*/
|
||||
|
||||
#define S76_DRIVER_NAME KBUILD_MODNAME
|
||||
#define pr_fmt(fmt) S76_DRIVER_NAME ": " fmt
|
||||
|
||||
//#define EXPERIMENTAL
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/hwmon.h>
|
||||
#include <linux/hwmon-sysfs.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rfkill.h>
|
||||
#include <linux/stringify.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#define __S76_PR(lvl, fmt, ...) do { pr_##lvl(fmt, ##__VA_ARGS__); } \
|
||||
while (0)
|
||||
#define S76_INFO(fmt, ...) __S76_PR(info, fmt, ##__VA_ARGS__)
|
||||
#define S76_ERROR(fmt, ...) __S76_PR(err, fmt, ##__VA_ARGS__)
|
||||
#define S76_DEBUG(fmt, ...) __S76_PR(debug, "[%s:%u] " fmt, \
|
||||
__func__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
#define S76_EVENT_GUID "ABBC0F6B-8EA1-11D1-00A0-C90629100000"
|
||||
#define S76_EMAIL_GUID "ABBC0F6C-8EA1-11D1-00A0-C90629100000"
|
||||
#define S76_GET_GUID "ABBC0F6D-8EA1-11D1-00A0-C90629100000"
|
||||
|
||||
#define S76_HAS_HWMON (defined(CONFIG_HWMON) || (defined(MODULE) && defined(CONFIG_HWMON_MODULE)))
|
||||
|
||||
/* method IDs for S76_GET */
|
||||
#define GET_EVENT 0x01 /* 1 */
|
||||
|
||||
struct platform_device *s76_platform_device;
|
||||
|
||||
static int s76_wmi_evaluate_wmbb_method(u32 method_id, u32 arg,
|
||||
u32 *retval)
|
||||
{
|
||||
struct acpi_buffer in = { (acpi_size) sizeof(arg), &arg };
|
||||
struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
u32 tmp;
|
||||
|
||||
S76_DEBUG("%0#4x IN : %0#6x\n", method_id, arg);
|
||||
|
||||
status = wmi_evaluate_method(S76_GET_GUID, 0x01,
|
||||
method_id, &in, &out);
|
||||
|
||||
if (unlikely(ACPI_FAILURE(status)))
|
||||
goto exit;
|
||||
|
||||
obj = (union acpi_object *) out.pointer;
|
||||
if (obj && obj->type == ACPI_TYPE_INTEGER)
|
||||
tmp = (u32) obj->integer.value;
|
||||
else
|
||||
tmp = 0;
|
||||
|
||||
S76_DEBUG("%0#4x OUT: %0#6x (IN: %0#6x)\n", method_id, tmp, arg);
|
||||
|
||||
if (likely(retval))
|
||||
*retval = tmp;
|
||||
|
||||
kfree(obj);
|
||||
|
||||
exit:
|
||||
if (unlikely(ACPI_FAILURE(status)))
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "led.c"
|
||||
#include "input.c"
|
||||
|
||||
#include "kb.c"
|
||||
|
||||
#include "wmi.c"
|
||||
|
||||
#include "fan.c"
|
||||
|
||||
#include "dmi.c"
|
||||
|
||||
static int __init s76_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
switch (param_kb_color_num) {
|
||||
case 1:
|
||||
param_kb_color[1] = param_kb_color[2] = param_kb_color[0] = param_kb_color[3];
|
||||
break;
|
||||
case 2:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dmi_check_system(s76_dmi_table);
|
||||
|
||||
if (!wmi_has_guid(S76_EVENT_GUID)) {
|
||||
S76_INFO("No known WMI event notification GUID found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (!wmi_has_guid(S76_GET_GUID)) {
|
||||
S76_INFO("No known WMI control method GUID found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
s76_platform_device =
|
||||
platform_create_bundle(&s76_platform_driver,
|
||||
s76_wmi_probe, NULL, 0, NULL, 0);
|
||||
|
||||
if (unlikely(IS_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
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit s76_exit(void)
|
||||
{
|
||||
s76_led_exit();
|
||||
// s76_input_exit();
|
||||
|
||||
#ifdef S76_HAS_HWMON
|
||||
s76_hwmon_fini(&s76_platform_device->dev);
|
||||
#endif
|
||||
device_remove_file(&s76_platform_device->dev,
|
||||
&dev_attr_kb_brightness);
|
||||
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_state);
|
||||
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_mode);
|
||||
device_remove_file(&s76_platform_device->dev, &dev_attr_kb_color);
|
||||
|
||||
platform_device_unregister(s76_platform_device);
|
||||
platform_driver_unregister(&s76_platform_driver);
|
||||
}
|
||||
|
||||
module_init(s76_init);
|
||||
module_exit(s76_exit);
|
||||
|
||||
MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>");
|
||||
MODULE_DESCRIPTION("System76 laptop driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("0.1");
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* wmi.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 void s76_wmi_notify(u32 value, void *context)
|
||||
{
|
||||
u32 event;
|
||||
|
||||
if (value != 0xD0) {
|
||||
S76_INFO("Unexpected WMI event (%0#6x)\n", value);
|
||||
return;
|
||||
}
|
||||
|
||||
s76_wmi_evaluate_wmbb_method(GET_EVENT, 0, &event);
|
||||
|
||||
S76_INFO("WMI event code (%x)\n", event);
|
||||
|
||||
switch (event) {
|
||||
case 0xF4:
|
||||
airplane_wmi();
|
||||
break;
|
||||
default:
|
||||
kb_wmi(event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int s76_wmi_probe(struct platform_device *dev)
|
||||
{
|
||||
int status;
|
||||
|
||||
status = wmi_install_notify_handler(S76_EVENT_GUID,
|
||||
s76_wmi_notify, NULL);
|
||||
if (unlikely(ACPI_FAILURE(status))) {
|
||||
S76_ERROR("Could not register WMI notify handler (%0#6x)\n",
|
||||
status);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (kb_backlight.ops)
|
||||
kb_backlight.ops->init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s76_wmi_remove(struct platform_device *dev)
|
||||
{
|
||||
wmi_remove_notify_handler(S76_EVENT_GUID);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s76_wmi_resume(struct platform_device *dev)
|
||||
{
|
||||
if (kb_backlight.ops && kb_backlight.state == KB_STATE_ON)
|
||||
kb_backlight.ops->set_mode(kb_backlight.mode);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver s76_platform_driver = {
|
||||
.remove = s76_wmi_remove,
|
||||
.resume = s76_wmi_resume,
|
||||
.driver = {
|
||||
.name = S76_DRIVER_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue