commit 0872338fab52276200039c9fab4749a7c3e32b32 Author: Jeremy Soller Date: Wed Dec 27 10:41:17 2017 -0700 System76 DKMS driver diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7bc5fa3 --- /dev/null +++ b/Makefile @@ -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) diff --git a/system76/Makefile b/system76/Makefile new file mode 100644 index 0000000..c43dcfd --- /dev/null +++ b/system76/Makefile @@ -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 diff --git a/system76/dkms.conf b/system76/dkms.conf new file mode 100644 index 0000000..3e4523d --- /dev/null +++ b/system76/dkms.conf @@ -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" diff --git a/system76/dmi.c b/system76/dmi.c new file mode 100644 index 0000000..562a314 --- /dev/null +++ b/system76/dmi.c @@ -0,0 +1,45 @@ +/* + * dmi.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +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); \ No newline at end of file diff --git a/system76/fan.c b/system76/fan.c new file mode 100644 index 0000000..758a413 --- /dev/null +++ b/system76/fan.c @@ -0,0 +1,180 @@ +/* + * fan.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +#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 \ No newline at end of file diff --git a/system76/input.c b/system76/input.c new file mode 100644 index 0000000..e7a823d --- /dev/null +++ b/system76/input.c @@ -0,0 +1,185 @@ +/* + * input.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +#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; +} \ No newline at end of file diff --git a/system76/kb.c b/system76/kb.c new file mode 100644 index 0000000..ae0d3a4 --- /dev/null +++ b/system76/kb.c @@ -0,0 +1,593 @@ +/* + * kb.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +#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); \ No newline at end of file diff --git a/system76/led.c b/system76/led.c new file mode 100644 index 0000000..4dc2b0b --- /dev/null +++ b/system76/led.c @@ -0,0 +1,111 @@ +/* + * led.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +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); +} \ No newline at end of file diff --git a/system76/system76.c b/system76/system76.c new file mode 100644 index 0000000..b0ec458 --- /dev/null +++ b/system76/system76.c @@ -0,0 +1,200 @@ +/* + * system76.c + * + * Copyright (C) 2017 Jeremy Soller + * + * Copyright (C) 2014-2016 Arnoud Willemsen + * + * Based on tuxedo-wmi by TUXEDO Computers GmbH + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * 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 . + */ + +#define S76_DRIVER_NAME KBUILD_MODNAME +#define pr_fmt(fmt) S76_DRIVER_NAME ": " fmt + +//#define EXPERIMENTAL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("System76 laptop driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.1"); diff --git a/system76/wmi.c b/system76/wmi.c new file mode 100644 index 0000000..890bed0 --- /dev/null +++ b/system76/wmi.c @@ -0,0 +1,84 @@ +/* + * wmi.c + * + * Copyright (C) 2017 Jeremy Soller + * Copyright (C) 2014-2016 Arnoud Willemsen + * Copyright (C) 2013-2015 TUXEDO Computers GmbH + * + * 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 . + */ + +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, + }, +}; \ No newline at end of file