From ac767a54b569198b12b0d60cae535793ce5e7f7f Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Fri, 20 Apr 2018 14:34:49 -0600 Subject: [PATCH 1/5] Add system76 platform driver --- MAINTAINERS | 8 + drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/system76.c | 354 +++++++++++++++++++++++++ drivers/platform/x86/system76_ap-led.c | 127 +++++++++ drivers/platform/x86/system76_hwmon.c | 279 +++++++++++++++++++ drivers/platform/x86/system76_input.c | 189 +++++++++++++ drivers/platform/x86/system76_kb-led.c | 336 +++++++++++++++++++++++ 8 files changed, 1307 insertions(+) create mode 100644 drivers/platform/x86/system76.c create mode 100644 drivers/platform/x86/system76_ap-led.c create mode 100644 drivers/platform/x86/system76_hwmon.c create mode 100644 drivers/platform/x86/system76_input.c create mode 100644 drivers/platform/x86/system76_kb-led.c diff --git a/MAINTAINERS b/MAINTAINERS index 9f64f8d3740edc..fcd037a8240471 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14766,6 +14766,14 @@ F: drivers/hwtracing/stm/ F: include/linux/stm.h F: include/uapi/linux/stm.h +SYSTEM76 PLATFORM DRIVERS +M: Jeremy Soller +L: productdev@system76.com +L: platform-driver-x86@vger.kernel.org +W: https://system76.com +S: Maintained +F: drivers/platform/x86/system76*.c + SYSV FILESYSTEM M: Christoph Hellwig S: Maintained diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 5e2109c54c7ce1..6e4606bd7d8444 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -483,6 +483,19 @@ config SURFACE3_WMI To compile this driver as a module, choose M here: the module will be called surface3-wmi. +config SYSTEM76 + tristate "System76 Laptop Extras" + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on RFKILL || RFKILL = n + depends on SERIO_I8042 + select HWMON + select LEDS_CLASS + select NEW_LEDS + ---help--- + This driver adds support for System76 laptops. + config THINKPAD_ACPI tristate "ThinkPad ACPI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index ce8da260c2234c..c772d7564bff7c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o obj-$(CONFIG_PEAQ_WMI) += peaq-wmi.o obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o +obj-$(CONFIG_SYSTEM76) += system76.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o diff --git a/drivers/platform/x86/system76.c b/drivers/platform/x86/system76.c new file mode 100644 index 00000000000000..5c88b99674ed5e --- /dev/null +++ b/drivers/platform/x86/system76.c @@ -0,0 +1,354 @@ +/* + * system76.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 S76_DRIVER_NAME KBUILD_MODNAME +#define pr_fmt(fmt) S76_DRIVER_NAME ": " fmt + +#include +#include +#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_WMBB_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_wmbb(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_WMBB_GUID, 0, method_id, &in, &out); + + if (unlikely(ACPI_FAILURE(status))) { + return -EIO; + } + + 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); + + return 0; +} + +#include "system76_ap-led.c" +#include "system76_input.c" +#include "system76_kb-led.c" +#include "system76_hwmon.c" + +static void s76_debug_wmi(void) { + S76_INFO("Debug WMI\n"); + + u32 val = 0; + + #define DEBUG_WMI(V, N) { \ + s76_wmbb(V, 0, &val); \ + S76_INFO("%x %s = %x\n", V, #N, val); \ + } + + DEBUG_WMI(1, GetEvent) + DEBUG_WMI(5, GetRadioStateForWirless) + DEBUG_WMI(6, GetPowerStateForCamera) + DEBUG_WMI(7, GetPowerStateForBluetooth) + DEBUG_WMI(8, GetSRSState) + DEBUG_WMI(9, GetTouchPadState) + DEBUG_WMI(10, GetPowerStateFor3G) + DEBUG_WMI(11, GetPowerStateForODD) + DEBUG_WMI(12, GetECLiveInfo) + DEBUG_WMI(13, PackageReadEC) + DEBUG_WMI(15, GetVGA2tempThermalIC) + DEBUG_WMI(16, GetCPUPerformance) + DEBUG_WMI(17, GetCPUFANControl) + DEBUG_WMI(18, GetXMP) + DEBUG_WMI(50, GetBatteryDesignCpacity) + DEBUG_WMI(51, GetBatteryAverageTimeToFullCharge) + DEBUG_WMI(52, GetBatteryAverageTimeToEmpty) + DEBUG_WMI(53, GetCPUFANDuty) + DEBUG_WMI(54, GetVGA1FANDuty) + DEBUG_WMI(55, GetVGA2FANDuty) + DEBUG_WMI(56, GetFANCount) + DEBUG_WMI(57, GetBoardId) + DEBUG_WMI(58, GetOem1) + //DEBUG_WMI(59, GetLCDResolution) + DEBUG_WMI(60, GetHDMIport) + DEBUG_WMI(61, GetWhiteLedKB) + DEBUG_WMI(62, GetGSensorMode) + DEBUG_WMI(63, GetHDPollingTime) + DEBUG_WMI(64, GetRFID) + DEBUG_WMI(66, GetBarCode) + DEBUG_WMI(67, GetBIOS_SF) + DEBUG_WMI(68, GetVolumeLED) + DEBUG_WMI(69, GetCurrentBrightness) + DEBUG_WMI(70, GetAP) + DEBUG_WMI(71, SetFactoryMode) + DEBUG_WMI(72, OS_S3_S4) + DEBUG_WMI(74, RapidStarMode) + DEBUG_WMI(80, GetACmA) + DEBUG_WMI(81, GetDCmAmV) + DEBUG_WMI(82, BIOS_special_feature_list) + DEBUG_WMI(83, GetLux) + DEBUG_WMI(84, Aero) + DEBUG_WMI(96, GetTP_SW) + DEBUG_WMI(97, GetFanStatus) + DEBUG_WMI(98, RapidStar) + DEBUG_WMI(99, Fan1Info) + DEBUG_WMI(100, Fan2Info) + DEBUG_WMI(109, AirplaneButton) + DEBUG_WMI(110, Fan3Info) + DEBUG_WMI(111, Fan4Info) + DEBUG_WMI(112, GetFan12RPM) + DEBUG_WMI(113, GetFan34RPM) + DEBUG_WMI(115, GetH2RAMData) + DEBUG_WMI(119, GetChargingStatus) + DEBUG_WMI(122, BiosSpecialFeature) +} + +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_wmbb(GET_EVENT, 0, &event); + + S76_INFO("WMI event code (%x)\n", event); + + switch (event) { + case 0x81: + kb_wmi_dec(); + break; + case 0x82: + kb_wmi_inc(); + break; + case 0x83: + kb_wmi_color(); + break; + case 0x7b: + //TODO: Fn+Backspace + break; + case 0x95: + s76_debug_wmi(); + break; + case 0x9F: + kb_wmi_toggle(); + break; + case 0xF4: + s76_input_airplane_wmi(); + break; + case 0xFC: + s76_input_touchpad_wmi(false); + break; + case 0xFD: + s76_input_touchpad_wmi(true); + break; + default: + S76_INFO("Unknown WMI event code (%x)\n", event); + break; + } +} + +static int s76_probe(struct platform_device *dev) { + int err; + + 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"); + } + +#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; + } + + // Enable hotkey support + s76_wmbb(0x46, 0, NULL); + + // Enable touchpad lock + i8042_lock_chip(); + i8042_command(NULL, 0x97); + i8042_unlock_chip(); + + return 0; +} + +static int s76_remove(struct platform_device *dev) { + wmi_remove_notify_handler(S76_EVENT_GUID); + + #ifdef S76_HAS_HWMON + s76_hwmon_fini(&dev->dev); + #endif + + s76_input_exit(); + kb_led_exit(); + ap_led_exit(); + + return 0; +} + +static int s76_suspend(struct platform_device *dev, pm_message_t status) { + S76_INFO("s76_suspend\n"); + + kb_led_suspend(); + + return 0; +} + +static int s76_resume(struct platform_device *dev) { + S76_INFO("s76_resume\n"); + + msleep(2000); + + // Enable hotkey support + s76_wmbb(0x46, 0, NULL); + + ap_led_resume(); + kb_led_resume(); + + return 0; +} + +static struct platform_driver s76_platform_driver = { + .remove = s76_remove, + .suspend = s76_suspend, + .resume = s76_resume, + .driver = { + .name = S76_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init s76_dmi_matched(const struct dmi_system_id *id) { + S76_INFO("Model %s found\n", id->ident); + + return 1; +} + +#define DMI_TABLE(PRODUCT) { \ + .ident = "System76 " PRODUCT, \ + .matches = { \ + DMI_MATCH(DMI_SYS_VENDOR, "System76"), \ + DMI_MATCH(DMI_PRODUCT_VERSION, PRODUCT), \ + }, \ + .callback = s76_dmi_matched, \ + .driver_data = NULL, \ +} + +static struct dmi_system_id s76_dmi_table[] __initdata = { + DMI_TABLE("oryp3-jeremy"), + {} +}; + +MODULE_DEVICE_TABLE(dmi, s76_dmi_table); + +static int __init s76_init(void) { + 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_WMBB_GUID)) { + S76_INFO("No known WMI control method GUID found\n"); + return -ENODEV; + } + + s76_platform_device = + platform_create_bundle(&s76_platform_driver, s76_probe, NULL, 0, NULL, 0); + + if (unlikely(IS_ERR(s76_platform_device))) { + return PTR_ERR(s76_platform_device); + } + + return 0; +} + +static void __exit s76_exit(void) { + 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/drivers/platform/x86/system76_ap-led.c b/drivers/platform/x86/system76_ap-led.c new file mode 100644 index 00000000000000..80d928994cc1c3 --- /dev/null +++ b/drivers/platform/x86/system76_ap-led.c @@ -0,0 +1,127 @@ +/* + * ap_led.c + * + * Copyright (C) 2017 Jeremy Soller + * + * 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 enum led_brightness ap_led_brightness = 1; + +static bool ap_led_invert = TRUE; + +static enum led_brightness ap_led_get(struct led_classdev *led_cdev) { + return ap_led_brightness; +} + +static int ap_led_set(struct led_classdev *led_cdev, enum led_brightness value) { + u8 byte; + + ec_read(0xD9, &byte); + + if (value > 0) { + ap_led_brightness = 1; + + if (ap_led_invert) { + byte &= ~0x40; + } else { + byte |= 0x40; + } + } else { + ap_led_brightness = 0; + + if (ap_led_invert) { + byte |= 0x40; + } else { + byte &= ~0x40; + } + } + + ec_write(0xD9, byte); + + return 0; +} + +static struct led_classdev ap_led = { + .name = "system76::airplane", + .brightness_get = ap_led_get, + .brightness_set_blocking = ap_led_set, + .max_brightness = 1, + .default_trigger = "rfkill-any" +}; + +static ssize_t ap_led_invert_show(struct device *dev, struct device_attribute *attr, char *buf) { + return sprintf(buf, "%d\n", (int)ap_led_invert); +} + +static ssize_t ap_led_invert_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + unsigned int val; + int ret; + enum led_brightness brightness; + + ret = kstrtouint(buf, 0, &val); + if (ret) { + return ret; + } + + brightness = ap_led_get(&ap_led); + + if (val) { + ap_led_invert = TRUE; + } else { + ap_led_invert = FALSE; + } + + ap_led_set(&ap_led, brightness); + + return size; +} + +static struct device_attribute ap_led_invert_dev_attr = { + .attr = { + .name = "invert", + .mode = 0644, + }, + .show = ap_led_invert_show, + .store = ap_led_invert_store, +}; + +static void ap_led_resume(void) { + ap_led_set(&ap_led, ap_led_brightness); +} + +static int __init ap_led_init(struct device *dev) { + int err; + + err = led_classdev_register(dev, &ap_led); + if (unlikely(err)) { + return err; + } + + if (device_create_file(ap_led.dev, &ap_led_invert_dev_attr) != 0) { + S76_ERROR("failed to create ap_led_invert\n"); + } + + ap_led_resume(); + + return 0; +} + +static void __exit ap_led_exit(void) { + device_remove_file(ap_led.dev, &ap_led_invert_dev_attr); + + if (!IS_ERR_OR_NULL(ap_led.dev)) { + led_classdev_unregister(&ap_led); + } +} diff --git a/drivers/platform/x86/system76_hwmon.c b/drivers/platform/x86/system76_hwmon.c new file mode 100644 index 00000000000000..045035c2f4d306 --- /dev/null +++ b/drivers/platform/x86/system76_hwmon.c @@ -0,0 +1,279 @@ +/* + * 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 . + */ + +#define EXPERIMENTAL + +#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 int +s76_read_pwm(int idx) { + u8 value; + ec_read(0xce + idx, &value); + return value; +} + +static int +s76_write_pwm(int idx, u8 duty) { + u8 values[] = {idx + 1, duty}; + return ec_transaction(0x99, values, sizeof(values), NULL, 0); +} + +static int +s76_write_pwm_auto(int idx) { + u8 values[] = {0xff, idx + 1}; + return ec_transaction(0x99, values, sizeof(values), NULL, 0); +} + +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_fan_input(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%i\n", s76_read_fan(index)); +} + +static ssize_t +s76_hwmon_show_fan_label(struct device *dev, struct device_attribute *attr, + char *buf) +{ + switch (to_sensor_dev_attr(attr)->index) { + case 0: + return sprintf(buf, "CPU fan\n"); + case 1: + return sprintf(buf, "GPU fan\n"); + } + return 0; +} + +static int pwm_enabled[] = {2, 2}; + +static ssize_t +s76_hwmon_show_pwm(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%i\n", s76_read_pwm(index)); +} + +static ssize_t +s76_hwmon_set_pwm(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 value; + int err; + int index = to_sensor_dev_attr(attr)->index; + + err = kstrtou32(buf, 10, &value); + if (err) return err; + if (value > 255) return -EINVAL; + err = s76_write_pwm(index, value); + if (err) return err; + pwm_enabled[index] = 1; + return count; +} + +static ssize_t +s76_hwmon_show_pwm_enable(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int index = to_sensor_dev_attr(attr)->index; + return sprintf(buf, "%i\n", pwm_enabled[index]); +} + +static ssize_t +s76_hwmon_set_pwm_enable(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 value; + int err; + int index = to_sensor_dev_attr(attr)->index; + + err = kstrtou32(buf, 10, &value); + if (err) return err; + if (value == 0) { + err = s76_write_pwm(index, 255); + if (err) return err; + pwm_enabled[index] = value; + return count; + } + if (value == 1) { + err = s76_write_pwm(index, 0); + if (err) return err; + pwm_enabled[index] = value; + return count; + } + if (value == 2) { + err = s76_write_pwm_auto(index); + if (err) return err; + pwm_enabled[index] = value; + return count; + } + return -EINVAL; +} + +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_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, s76_hwmon_show_fan_label, NULL, 0); +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, s76_hwmon_show_pwm, s76_hwmon_set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, s76_hwmon_show_pwm_enable, s76_hwmon_set_pwm_enable, 0); +#ifdef EXPERIMENTAL +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, s76_hwmon_show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, s76_hwmon_show_fan_label, NULL, 1); +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, s76_hwmon_show_pwm, s76_hwmon_set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IRUGO | S_IWUSR, s76_hwmon_show_pwm_enable, s76_hwmon_set_pwm_enable, 1); +#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, 1); +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, s76_hwmon_show_temp2_label, NULL, 1); +#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, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm1_enable.dev_attr.attr, +#ifdef EXPERIMENTAL + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan2_label.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_pwm2_enable.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_reboot_callback(struct notifier_block *nb, + unsigned long action, void *data) { + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + return NOTIFY_DONE; +} + +static struct notifier_block s76_hwmon_reboot_notifier = { + .notifier_call = s76_hwmon_reboot_callback +}; + +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; + + register_reboot_notifier(&s76_hwmon_reboot_notifier); + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + return 0; +} + +static int +s76_hwmon_fini(struct device *dev) { + if (!s76_hwmon || !s76_hwmon->dev) + return 0; + s76_write_pwm_auto(0); + #ifdef EXPERIMENTAL + s76_write_pwm_auto(1); + #endif + unregister_reboot_notifier(&s76_hwmon_reboot_notifier); + 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 diff --git a/drivers/platform/x86/system76_input.c b/drivers/platform/x86/system76_input.c new file mode 100644 index 00000000000000..d1076debd0d582 --- /dev/null +++ b/drivers/platform/x86/system76_input.c @@ -0,0 +1,189 @@ +/* + * 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_WLAN +#define TOUCHPAD_ON_KEY KEY_F21 +#define TOUCHPAD_OFF_KEY KEY_F21 + +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 void s76_input_key(unsigned int code) { + S76_INFO("Send key %x\n", code); + + mutex_lock(&s76_input_report_mutex); + + input_report_key(s76_input_device, code, 1); + input_sync(s76_input_device); + + input_report_key(s76_input_device, code, 0); + input_sync(s76_input_device); + + mutex_unlock(&s76_input_report_mutex); +} + +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"); + + s76_input_key(AIRPLANE_KEY); + } + + msleep_interruptible(1000 / param_poll_freq); + } + + S76_INFO("Polling thread exiting\n"); + + return 0; +} + +static void s76_input_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; + } + + s76_input_key(AIRPLANE_KEY); +} + +static void s76_input_touchpad_wmi(bool enabled) { + S76_INFO("Touchpad Hotkey pressed (WMI) %d\n", enabled); + + if (enabled) { + s76_input_key(TOUCHPAD_ON_KEY); + } else { + s76_input_key(TOUCHPAD_OFF_KEY); + } +} + +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(struct device *dev) { + 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 = dev; + set_bit(EV_KEY, s76_input_device->evbit); + set_bit(AIRPLANE_KEY, s76_input_device->keybit); + set_bit(TOUCHPAD_ON_KEY, s76_input_device->keybit); + set_bit(TOUCHPAD_OFF_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; +} diff --git a/drivers/platform/x86/system76_kb-led.c b/drivers/platform/x86/system76_kb-led.c new file mode 100644 index 00000000000000..9dd52f814378d2 --- /dev/null +++ b/drivers/platform/x86/system76_kb-led.c @@ -0,0 +1,336 @@ +/* + * kb_led.c + * + * Copyright (C) 2017 Jeremy Soller + * + * 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 + +union kb_led_color { + u32 rgb; + struct { u32 b:8, g:8, r:8, : 8; }; +}; + +enum kb_led_region { + KB_LED_REGION_LEFT, + KB_LED_REGION_CENTER, + KB_LED_REGION_RIGHT, + KB_LED_REGION_EXTRA, +}; + +static enum led_brightness kb_led_brightness = 72; + +static enum led_brightness kb_led_toggle_brightness = 72; + +static enum led_brightness kb_led_levels[] = { 48, 72, 96, 144, 192, 255 }; + +static union kb_led_color kb_led_regions[] = { + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF }, + { .rgb = 0xFFFFFF } +}; + +static int kb_led_colors_i = 0; + +static union kb_led_color kb_led_colors[] = { + { .rgb = 0xFFFFFF }, + { .rgb = 0x0000FF }, + { .rgb = 0xFF0000 }, + { .rgb = 0xFF00FF }, + { .rgb = 0x00FF00 }, + { .rgb = 0x00FFFF }, + { .rgb = 0xFFFF00 } +}; + +static enum led_brightness kb_led_get(struct led_classdev *led_cdev) { + return kb_led_brightness; +} + +static int kb_led_set(struct led_classdev *led_cdev, enum led_brightness value) { + S76_INFO("kb_led_set %d\n", (int)value); + + if (!s76_wmbb(SET_KB_LED, 0xF4000000 | value, NULL)) { + kb_led_brightness = value; + } + + return 0; +} + +static void kb_led_color_set(enum kb_led_region region, union kb_led_color color) { + u32 cmd; + + S76_INFO("kb_led_color_set %d %06X\n", (int)region, (int)color.rgb); + + switch (region) { + case KB_LED_REGION_LEFT: + cmd = 0xF0000000; + break; + case KB_LED_REGION_CENTER: + cmd = 0xF1000000; + break; + case KB_LED_REGION_RIGHT: + cmd = 0xF2000000; + break; + case KB_LED_REGION_EXTRA: + cmd = 0xF3000000; + break; + default: + return; + } + + cmd |= color.b << 16; + cmd |= color.r << 8; + cmd |= color.g << 0; + + if (!s76_wmbb(SET_KB_LED, cmd, NULL)) { + kb_led_regions[region] = color; + } +} + +static struct led_classdev kb_led = { + .name = "system76::kbd_backlight", + .flags = LED_BRIGHT_HW_CHANGED, + .brightness_get = kb_led_get, + .brightness_set_blocking = kb_led_set, + .max_brightness = 255, +}; + +static ssize_t kb_led_color_show(enum kb_led_region region, char *buf) { + return sprintf(buf, "%06X\n", (int)kb_led_regions[region].rgb); +} + +static ssize_t kb_led_color_store(enum kb_led_region region, const char *buf, size_t size) { + unsigned int val; + int ret; + union kb_led_color color; + + ret = kstrtouint(buf, 16, &val); + if (ret) { + return ret; + } + + color.rgb = (u32)val; + kb_led_color_set(region, color); + + return size; +} + +static ssize_t kb_led_color_left_show(struct device *dev, struct device_attribute *attr, char *buf) { + return kb_led_color_show(KB_LED_REGION_LEFT, buf); +} + +static ssize_t kb_led_color_left_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + return kb_led_color_store(KB_LED_REGION_LEFT, buf, size); +} + +static struct device_attribute kb_led_color_left_dev_attr = { + .attr = { + .name = "color_left", + .mode = 0644, + }, + .show = kb_led_color_left_show, + .store = kb_led_color_left_store, +}; + +static ssize_t kb_led_color_center_show(struct device *dev, struct device_attribute *attr, char *buf) { + return kb_led_color_show(KB_LED_REGION_CENTER, buf); +} + +static ssize_t kb_led_color_center_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + return kb_led_color_store(KB_LED_REGION_CENTER, buf, size); +} + +static struct device_attribute kb_led_color_center_dev_attr = { + .attr = { + .name = "color_center", + .mode = 0644, + }, + .show = kb_led_color_center_show, + .store = kb_led_color_center_store, +}; + +static ssize_t kb_led_color_right_show(struct device *dev, struct device_attribute *attr, char *buf) { + return kb_led_color_show(KB_LED_REGION_RIGHT, buf); +} + +static ssize_t kb_led_color_right_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + return kb_led_color_store(KB_LED_REGION_RIGHT, buf, size); +} + +static struct device_attribute kb_led_color_right_dev_attr = { + .attr = { + .name = "color_right", + .mode = 0644, + }, + .show = kb_led_color_right_show, + .store = kb_led_color_right_store, +}; + +static ssize_t kb_led_color_extra_show(struct device *dev, struct device_attribute *attr, char *buf) { + return kb_led_color_show(KB_LED_REGION_EXTRA, buf); +} + +static ssize_t kb_led_color_extra_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { + return kb_led_color_store(KB_LED_REGION_EXTRA, buf, size); +} + +static struct device_attribute kb_led_color_extra_dev_attr = { + .attr = { + .name = "color_extra", + .mode = 0644, + }, + .show = kb_led_color_extra_show, + .store = kb_led_color_extra_store, +}; + +static void kb_led_enable(void) { + S76_INFO("kb_led_enable\n"); + + s76_wmbb(SET_KB_LED, 0xE007F001, NULL); +} + +static void kb_led_disable(void) { + S76_INFO("kb_led_disable\n"); + + s76_wmbb(SET_KB_LED, 0xE0003001, NULL); +} + +static void kb_led_suspend(void) { + S76_INFO("kb_led_suspend\n"); + + // Disable keyboard backlight + kb_led_disable(); +} + +static void kb_led_resume(void) { + enum kb_led_region region; + + S76_INFO("kb_led_resume\n"); + + // Disable keyboard backlight + kb_led_disable(); + + // Reset current color + for (region = 0; region < sizeof(kb_led_regions)/sizeof(union kb_led_color); region++) { + kb_led_color_set(region, kb_led_regions[region]); + } + + // Reset current brightness + kb_led_set(&kb_led, kb_led_brightness); + + // Enable keyboard backlight + kb_led_enable(); +} + +static int __init kb_led_init(struct device *dev) { + int err; + + err = led_classdev_register(dev, &kb_led); + if (unlikely(err)) { + return err; + } + + if (device_create_file(kb_led.dev, &kb_led_color_left_dev_attr) != 0) { + S76_ERROR("failed to create kb_led_color_left\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_center_dev_attr) != 0) { + S76_ERROR("failed to create kb_led_color_center\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_right_dev_attr) != 0) { + S76_ERROR("failed to create kb_led_color_right\n"); + } + + if (device_create_file(kb_led.dev, &kb_led_color_extra_dev_attr) != 0) { + S76_ERROR("failed to create kb_led_color_extra\n"); + } + + kb_led_resume(); + + return 0; +} + +static void __exit kb_led_exit(void) { + device_remove_file(kb_led.dev, &kb_led_color_extra_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_right_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_center_dev_attr); + device_remove_file(kb_led.dev, &kb_led_color_left_dev_attr); + + if (!IS_ERR_OR_NULL(kb_led.dev)) { + led_classdev_unregister(&kb_led); + } +} + +static void kb_wmi_brightness(enum led_brightness value) { + S76_INFO("kb_wmi_brightness %d\n", (int)value); + + kb_led_set(&kb_led, value); + led_classdev_notify_brightness_hw_changed(&kb_led, value); +} + +static void kb_wmi_toggle(void) { + if (kb_led_brightness > 0) { + kb_led_toggle_brightness = kb_led_brightness; + kb_wmi_brightness(LED_OFF); + } else { + kb_wmi_brightness(kb_led_toggle_brightness); + } +} + +static void kb_wmi_dec(void) { + int i; + + if (kb_led_brightness > 0) { + for (i = sizeof(kb_led_levels)/sizeof(enum led_brightness); i > 0; i--) { + if (kb_led_levels[i - 1] < kb_led_brightness) { + kb_wmi_brightness(kb_led_levels[i - 1]); + break; + } + } + } else { + kb_wmi_toggle(); + } +} + +static void kb_wmi_inc(void) { + int i; + + if (kb_led_brightness > 0) { + for (i = 0; i < sizeof(kb_led_levels)/sizeof(enum led_brightness); i++) { + if (kb_led_levels[i] > kb_led_brightness) { + kb_wmi_brightness(kb_led_levels[i]); + break; + } + } + } else { + kb_wmi_toggle(); + } +} + +static void kb_wmi_color(void) { + enum kb_led_region region; + + kb_led_colors_i += 1; + if (kb_led_colors_i >= sizeof(kb_led_colors)/sizeof(union kb_led_color)) { + kb_led_colors_i = 0; + } + + for (region = 0; region < sizeof(kb_led_regions)/sizeof(union kb_led_color); region++) { + kb_led_color_set(region, kb_led_colors[kb_led_colors_i]); + } +} From 2ecbade75a50b564e55b367a19e7c3f7da6f5bcc Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 8 Jan 2019 13:21:27 -0700 Subject: [PATCH 2/5] Add rebuild script --- rebuild.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 rebuild.sh diff --git a/rebuild.sh b/rebuild.sh new file mode 100755 index 00000000000000..c6c18f27fce0e6 --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +if [ ! -e .config ] +then + make olddefconfig + ./scripts/config --disable DEBUG_INFO +fi + +time make bindeb-pkg LOCALVERSION=-system76 -j "$(nproc)" From 630ac20d69d6b8019660c48537c26b7208acf8e2 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 29 Jan 2019 12:35:43 -0700 Subject: [PATCH 3/5] Headset microphone support for System76 darp5 --- sound/pci/hda/patch_realtek.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index b4f472157ebdf6..15fedb0eeae192 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -5591,6 +5591,7 @@ enum { ALC294_FIXUP_ASUS_HEADSET_MIC, ALC294_FIXUP_ASUS_SPK, ALC225_FIXUP_HEADSET_JACK, + ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE, }; static const struct hda_fixup alc269_fixups[] = { @@ -6537,6 +6538,15 @@ static const struct hda_fixup alc269_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc_fixup_headset_jack, }, + [ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + .chained = true, + .chain_id = ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC + }, }; static const struct snd_pci_quirk alc269_fixup_tbl[] = { @@ -6606,6 +6616,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x103c, 0x18e6, "HP", ALC269_FIXUP_HP_GPIO_LED), SND_PCI_QUIRK(0x103c, 0x218b, "HP", ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED), SND_PCI_QUIRK(0x103c, 0x225f, "HP", ALC280_FIXUP_HP_GPIO2_MIC_HOTKEY), + SND_PCI_QUIRK(0x1558, 0x1325, "System76", ALC293_FIXUP_SYSTEM76_MIC_NO_PRESENCE), /* ALC282 */ SND_PCI_QUIRK(0x103c, 0x21f9, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), SND_PCI_QUIRK(0x103c, 0x2210, "HP", ALC269_FIXUP_HP_MUTE_LED_MIC1), From 0feec64b3ccd8172682dcc0c39199318b512b2af Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 30 Jan 2019 15:20:00 -0700 Subject: [PATCH 4/5] Add script to set up git remotes --- remotes.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100755 remotes.sh diff --git a/remotes.sh b/remotes.sh new file mode 100755 index 00000000000000..03b52fc60bff67 --- /dev/null +++ b/remotes.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +function git_remote { + if git remote | grep "^$1\$" + then + git remote set-url "$1" "$2" + else + git remote add "$1" "$2" + fi + git fetch "$1" +} + +git_remote upstream https://github.com/torvalds/linux.git + +for distro in bionic cosmic disco +do + git_remote "ubuntu-$distro" "git://git.launchpad.net/~ubuntu-kernel/ubuntu/+source/linux/+git/$distro" +done From 9ff7bd82b59e0b383a3e03c1adaf9138a32268ea Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 30 Jan 2019 14:00:43 -0700 Subject: [PATCH 5/5] WIP: Support for oryp5 This adds the oryp5 to the patchset for the oryp4 that enables speaker output, and also attempts to fix headset microphone input --- sound/pci/hda/patch_realtek.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 15fedb0eeae192..5e9983f2d74cab 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -1847,6 +1847,8 @@ enum { ALC887_FIXUP_BASS_CHMAP, ALC1220_FIXUP_GB_DUAL_CODECS, ALC1220_FIXUP_CLEVO_P950, + ALC1220_FIXUP_CLEVO_P960, + ALC1220_FIXUP_CLEVO_P960_PINS, }; static void alc889_fixup_coef(struct hda_codec *codec, @@ -2048,6 +2050,16 @@ static void alc1220_fixup_clevo_p950(struct hda_codec *codec, snd_hda_override_conn_list(codec, 0x1b, 1, conn1); } +static void alc_fixup_headset_mode_no_hp_mic(struct hda_codec *codec, + const struct hda_fixup *fix, int action); + +static void alc1220_fixup_clevo_p960(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + alc1220_fixup_clevo_p950(codec, fix, action); + alc_fixup_headset_mode_no_hp_mic(codec, fix, action); +} + static const struct hda_fixup alc882_fixups[] = { [ALC882_FIXUP_ABIT_AW9D_MAX] = { .type = HDA_FIXUP_PINS, @@ -2292,6 +2304,19 @@ static const struct hda_fixup alc882_fixups[] = { .type = HDA_FIXUP_FUNC, .v.func = alc1220_fixup_clevo_p950, }, + [ALC1220_FIXUP_CLEVO_P960] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc1220_fixup_clevo_p960, + .chained = true, + .chain_id = ALC1220_FIXUP_CLEVO_P960_PINS, + }, + [ALC1220_FIXUP_CLEVO_P960_PINS] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x01a1913c }, /* use as headset mic, without its own jack detect */ + { } + }, + }, }; static const struct snd_pci_quirk alc882_fixup_tbl[] = { @@ -2368,6 +2393,7 @@ static const struct snd_pci_quirk alc882_fixup_tbl[] = { SND_PCI_QUIRK(0x1558, 0x9501, "Clevo P950HR", ALC1220_FIXUP_CLEVO_P950), SND_PCI_QUIRK(0x1558, 0x95e1, "Clevo P95xER", ALC1220_FIXUP_CLEVO_P950), SND_PCI_QUIRK(0x1558, 0x95e2, "Clevo P950ER", ALC1220_FIXUP_CLEVO_P950), + SND_PCI_QUIRK(0x1558, 0x96e1, "Clevo P960ED", ALC1220_FIXUP_CLEVO_P960), SND_PCI_QUIRK_VENDOR(0x1558, "Clevo laptop", ALC882_FIXUP_EAPD), SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_FIXUP_EAPD), SND_PCI_QUIRK(0x17aa, 0x3a0d, "Lenovo Y530", ALC882_FIXUP_LENOVO_Y530), @@ -4902,7 +4928,7 @@ static void alc_fixup_headset_mode_alc255_no_hp_mic(struct hda_codec *codec, struct alc_spec *spec = codec->spec; spec->parse_flags |= HDA_PINCFG_HEADSET_MIC; alc255_set_default_jack_type(codec); - } + } else alc_fixup_headset_mode(codec, fix, action); }