1fd13c862SJeremy Soller // SPDX-License-Identifier: GPL-2.0+ 2fd13c862SJeremy Soller /* 3fd13c862SJeremy Soller * System76 ACPI Driver 4fd13c862SJeremy Soller * 5fd13c862SJeremy Soller * Copyright (C) 2019 System76 6fd13c862SJeremy Soller * 7fd13c862SJeremy Soller * This program is free software; you can redistribute it and/or modify 8fd13c862SJeremy Soller * it under the terms of the GNU General Public License version 2 as 9fd13c862SJeremy Soller * published by the Free Software Foundation. 10fd13c862SJeremy Soller */ 11fd13c862SJeremy Soller 12fd13c862SJeremy Soller #include <linux/acpi.h> 13*95563d45SJeremy Soller #include <linux/hwmon.h> 14*95563d45SJeremy Soller #include <linux/hwmon-sysfs.h> 15fd13c862SJeremy Soller #include <linux/init.h> 16fd13c862SJeremy Soller #include <linux/kernel.h> 17fd13c862SJeremy Soller #include <linux/leds.h> 18fd13c862SJeremy Soller #include <linux/module.h> 19fd13c862SJeremy Soller #include <linux/pci_ids.h> 20fd13c862SJeremy Soller #include <linux/types.h> 21fd13c862SJeremy Soller 22fd13c862SJeremy Soller struct system76_data { 23fd13c862SJeremy Soller struct acpi_device *acpi_dev; 24fd13c862SJeremy Soller struct led_classdev ap_led; 25fd13c862SJeremy Soller struct led_classdev kb_led; 26fd13c862SJeremy Soller enum led_brightness kb_brightness; 27fd13c862SJeremy Soller enum led_brightness kb_toggle_brightness; 28fd13c862SJeremy Soller int kb_color; 29*95563d45SJeremy Soller struct device *therm; 30*95563d45SJeremy Soller union acpi_object *nfan; 31*95563d45SJeremy Soller union acpi_object *ntmp; 32fd13c862SJeremy Soller }; 33fd13c862SJeremy Soller 34fd13c862SJeremy Soller static const struct acpi_device_id device_ids[] = { 35fd13c862SJeremy Soller {"17761776", 0}, 36fd13c862SJeremy Soller {"", 0}, 37fd13c862SJeremy Soller }; 38fd13c862SJeremy Soller MODULE_DEVICE_TABLE(acpi, device_ids); 39fd13c862SJeremy Soller 40fd13c862SJeremy Soller // Array of keyboard LED brightness levels 41fd13c862SJeremy Soller static const enum led_brightness kb_levels[] = { 42fd13c862SJeremy Soller 48, 43fd13c862SJeremy Soller 72, 44fd13c862SJeremy Soller 96, 45fd13c862SJeremy Soller 144, 46fd13c862SJeremy Soller 192, 47fd13c862SJeremy Soller 255 48fd13c862SJeremy Soller }; 49fd13c862SJeremy Soller 50fd13c862SJeremy Soller // Array of keyboard LED colors in 24-bit RGB format 51fd13c862SJeremy Soller static const int kb_colors[] = { 52fd13c862SJeremy Soller 0xFFFFFF, 53fd13c862SJeremy Soller 0x0000FF, 54fd13c862SJeremy Soller 0xFF0000, 55fd13c862SJeremy Soller 0xFF00FF, 56fd13c862SJeremy Soller 0x00FF00, 57fd13c862SJeremy Soller 0x00FFFF, 58fd13c862SJeremy Soller 0xFFFF00 59fd13c862SJeremy Soller }; 60fd13c862SJeremy Soller 61fd13c862SJeremy Soller // Get a System76 ACPI device value by name 62fd13c862SJeremy Soller static int system76_get(struct system76_data *data, char *method) 63fd13c862SJeremy Soller { 64fd13c862SJeremy Soller acpi_handle handle; 65fd13c862SJeremy Soller acpi_status status; 66fd13c862SJeremy Soller unsigned long long ret = 0; 67fd13c862SJeremy Soller 68fd13c862SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 69fd13c862SJeremy Soller status = acpi_evaluate_integer(handle, method, NULL, &ret); 70fd13c862SJeremy Soller if (ACPI_SUCCESS(status)) 71*95563d45SJeremy Soller return ret; 72*95563d45SJeremy Soller return -ENODEV; 73*95563d45SJeremy Soller } 74*95563d45SJeremy Soller 75*95563d45SJeremy Soller // Get a System76 ACPI device value by name with index 76*95563d45SJeremy Soller static int system76_get_index(struct system76_data *data, char *method, int index) 77*95563d45SJeremy Soller { 78*95563d45SJeremy Soller union acpi_object obj; 79*95563d45SJeremy Soller struct acpi_object_list obj_list; 80*95563d45SJeremy Soller acpi_handle handle; 81*95563d45SJeremy Soller acpi_status status; 82*95563d45SJeremy Soller unsigned long long ret = 0; 83*95563d45SJeremy Soller 84*95563d45SJeremy Soller obj.type = ACPI_TYPE_INTEGER; 85*95563d45SJeremy Soller obj.integer.value = index; 86*95563d45SJeremy Soller obj_list.count = 1; 87*95563d45SJeremy Soller obj_list.pointer = &obj; 88*95563d45SJeremy Soller 89*95563d45SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 90*95563d45SJeremy Soller status = acpi_evaluate_integer(handle, method, &obj_list, &ret); 91*95563d45SJeremy Soller if (ACPI_SUCCESS(status)) 92*95563d45SJeremy Soller return ret; 93*95563d45SJeremy Soller return -ENODEV; 94*95563d45SJeremy Soller } 95*95563d45SJeremy Soller 96*95563d45SJeremy Soller // Get a System76 ACPI device object by name 97*95563d45SJeremy Soller static int system76_get_object(struct system76_data *data, char *method, union acpi_object **obj) 98*95563d45SJeremy Soller { 99*95563d45SJeremy Soller acpi_handle handle; 100*95563d45SJeremy Soller acpi_status status; 101*95563d45SJeremy Soller struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 102*95563d45SJeremy Soller 103*95563d45SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 104*95563d45SJeremy Soller status = acpi_evaluate_object(handle, method, NULL, &buf); 105*95563d45SJeremy Soller if (ACPI_SUCCESS(status)) { 106*95563d45SJeremy Soller *obj = buf.pointer; 107*95563d45SJeremy Soller return 0; 108*95563d45SJeremy Soller } 109*95563d45SJeremy Soller 110*95563d45SJeremy Soller return -ENODEV; 111*95563d45SJeremy Soller } 112*95563d45SJeremy Soller 113*95563d45SJeremy Soller // Get a name from a System76 ACPI device object 114*95563d45SJeremy Soller static char *system76_name(union acpi_object *obj, int index) 115*95563d45SJeremy Soller { 116*95563d45SJeremy Soller if (obj && obj->type == ACPI_TYPE_PACKAGE && index <= obj->package.count) { 117*95563d45SJeremy Soller if (obj->package.elements[index].type == ACPI_TYPE_STRING) 118*95563d45SJeremy Soller return obj->package.elements[index].string.pointer; 119*95563d45SJeremy Soller } 120*95563d45SJeremy Soller 121*95563d45SJeremy Soller return NULL; 122fd13c862SJeremy Soller } 123fd13c862SJeremy Soller 124fd13c862SJeremy Soller // Set a System76 ACPI device value by name 125fd13c862SJeremy Soller static int system76_set(struct system76_data *data, char *method, int value) 126fd13c862SJeremy Soller { 127fd13c862SJeremy Soller union acpi_object obj; 128fd13c862SJeremy Soller struct acpi_object_list obj_list; 129fd13c862SJeremy Soller acpi_handle handle; 130fd13c862SJeremy Soller acpi_status status; 131fd13c862SJeremy Soller 132fd13c862SJeremy Soller obj.type = ACPI_TYPE_INTEGER; 133fd13c862SJeremy Soller obj.integer.value = value; 134fd13c862SJeremy Soller obj_list.count = 1; 135fd13c862SJeremy Soller obj_list.pointer = &obj; 136fd13c862SJeremy Soller handle = acpi_device_handle(data->acpi_dev); 137fd13c862SJeremy Soller status = acpi_evaluate_object(handle, method, &obj_list, NULL); 138fd13c862SJeremy Soller if (ACPI_SUCCESS(status)) 139fd13c862SJeremy Soller return 0; 140fd13c862SJeremy Soller else 141fd13c862SJeremy Soller return -1; 142fd13c862SJeremy Soller } 143fd13c862SJeremy Soller 144fd13c862SJeremy Soller // Get the airplane mode LED brightness 145fd13c862SJeremy Soller static enum led_brightness ap_led_get(struct led_classdev *led) 146fd13c862SJeremy Soller { 147fd13c862SJeremy Soller struct system76_data *data; 148fd13c862SJeremy Soller int value; 149fd13c862SJeremy Soller 150fd13c862SJeremy Soller data = container_of(led, struct system76_data, ap_led); 151fd13c862SJeremy Soller value = system76_get(data, "GAPL"); 152fd13c862SJeremy Soller if (value > 0) 153fd13c862SJeremy Soller return (enum led_brightness)value; 154fd13c862SJeremy Soller else 155fd13c862SJeremy Soller return LED_OFF; 156fd13c862SJeremy Soller } 157fd13c862SJeremy Soller 158fd13c862SJeremy Soller // Set the airplane mode LED brightness 1595b36398dSNick Shipp static int ap_led_set(struct led_classdev *led, enum led_brightness value) 160fd13c862SJeremy Soller { 161fd13c862SJeremy Soller struct system76_data *data; 162fd13c862SJeremy Soller 163fd13c862SJeremy Soller data = container_of(led, struct system76_data, ap_led); 1645b36398dSNick Shipp return system76_set(data, "SAPL", value == LED_OFF ? 0 : 1); 165fd13c862SJeremy Soller } 166fd13c862SJeremy Soller 167fd13c862SJeremy Soller // Get the last set keyboard LED brightness 168fd13c862SJeremy Soller static enum led_brightness kb_led_get(struct led_classdev *led) 169fd13c862SJeremy Soller { 170fd13c862SJeremy Soller struct system76_data *data; 171fd13c862SJeremy Soller 172fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 173fd13c862SJeremy Soller return data->kb_brightness; 174fd13c862SJeremy Soller } 175fd13c862SJeremy Soller 176fd13c862SJeremy Soller // Set the keyboard LED brightness 1775b36398dSNick Shipp static int kb_led_set(struct led_classdev *led, enum led_brightness value) 178fd13c862SJeremy Soller { 179fd13c862SJeremy Soller struct system76_data *data; 180fd13c862SJeremy Soller 181fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 182fd13c862SJeremy Soller data->kb_brightness = value; 1835b36398dSNick Shipp return system76_set(data, "SKBL", (int)data->kb_brightness); 184fd13c862SJeremy Soller } 185fd13c862SJeremy Soller 186fd13c862SJeremy Soller // Get the last set keyboard LED color 187fd13c862SJeremy Soller static ssize_t kb_led_color_show( 188fd13c862SJeremy Soller struct device *dev, 189fd13c862SJeremy Soller struct device_attribute *dev_attr, 190fd13c862SJeremy Soller char *buf) 191fd13c862SJeremy Soller { 192fd13c862SJeremy Soller struct led_classdev *led; 193fd13c862SJeremy Soller struct system76_data *data; 194fd13c862SJeremy Soller 195fd13c862SJeremy Soller led = (struct led_classdev *)dev->driver_data; 196fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 197fd13c862SJeremy Soller return sprintf(buf, "%06X\n", data->kb_color); 198fd13c862SJeremy Soller } 199fd13c862SJeremy Soller 200fd13c862SJeremy Soller // Set the keyboard LED color 201fd13c862SJeremy Soller static ssize_t kb_led_color_store( 202fd13c862SJeremy Soller struct device *dev, 203fd13c862SJeremy Soller struct device_attribute *dev_attr, 204fd13c862SJeremy Soller const char *buf, 205fd13c862SJeremy Soller size_t size) 206fd13c862SJeremy Soller { 207fd13c862SJeremy Soller struct led_classdev *led; 208fd13c862SJeremy Soller struct system76_data *data; 209fd13c862SJeremy Soller unsigned int val; 210fd13c862SJeremy Soller int ret; 211fd13c862SJeremy Soller 212fd13c862SJeremy Soller led = (struct led_classdev *)dev->driver_data; 213fd13c862SJeremy Soller data = container_of(led, struct system76_data, kb_led); 214fd13c862SJeremy Soller ret = kstrtouint(buf, 16, &val); 215fd13c862SJeremy Soller if (ret) 216fd13c862SJeremy Soller return ret; 217fd13c862SJeremy Soller if (val > 0xFFFFFF) 218fd13c862SJeremy Soller return -EINVAL; 219fd13c862SJeremy Soller data->kb_color = (int)val; 220fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 221fd13c862SJeremy Soller 222fd13c862SJeremy Soller return size; 223fd13c862SJeremy Soller } 224fd13c862SJeremy Soller 225fd13c862SJeremy Soller static const struct device_attribute kb_led_color_dev_attr = { 226fd13c862SJeremy Soller .attr = { 227fd13c862SJeremy Soller .name = "color", 228fd13c862SJeremy Soller .mode = 0644, 229fd13c862SJeremy Soller }, 230fd13c862SJeremy Soller .show = kb_led_color_show, 231fd13c862SJeremy Soller .store = kb_led_color_store, 232fd13c862SJeremy Soller }; 233fd13c862SJeremy Soller 234fd13c862SJeremy Soller // Notify that the keyboard LED was changed by hardware 235fd13c862SJeremy Soller static void kb_led_notify(struct system76_data *data) 236fd13c862SJeremy Soller { 237fd13c862SJeremy Soller led_classdev_notify_brightness_hw_changed( 238fd13c862SJeremy Soller &data->kb_led, 239fd13c862SJeremy Soller data->kb_brightness 240fd13c862SJeremy Soller ); 241fd13c862SJeremy Soller } 242fd13c862SJeremy Soller 243fd13c862SJeremy Soller // Read keyboard LED brightness as set by hardware 244fd13c862SJeremy Soller static void kb_led_hotkey_hardware(struct system76_data *data) 245fd13c862SJeremy Soller { 246fd13c862SJeremy Soller int value; 247fd13c862SJeremy Soller 248fd13c862SJeremy Soller value = system76_get(data, "GKBL"); 249fd13c862SJeremy Soller if (value < 0) 250fd13c862SJeremy Soller return; 251fd13c862SJeremy Soller data->kb_brightness = value; 252fd13c862SJeremy Soller kb_led_notify(data); 253fd13c862SJeremy Soller } 254fd13c862SJeremy Soller 255fd13c862SJeremy Soller // Toggle the keyboard LED 256fd13c862SJeremy Soller static void kb_led_hotkey_toggle(struct system76_data *data) 257fd13c862SJeremy Soller { 258fd13c862SJeremy Soller if (data->kb_brightness > 0) { 259fd13c862SJeremy Soller data->kb_toggle_brightness = data->kb_brightness; 260fd13c862SJeremy Soller kb_led_set(&data->kb_led, 0); 261fd13c862SJeremy Soller } else { 262fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 263fd13c862SJeremy Soller } 264fd13c862SJeremy Soller kb_led_notify(data); 265fd13c862SJeremy Soller } 266fd13c862SJeremy Soller 267fd13c862SJeremy Soller // Decrease the keyboard LED brightness 268fd13c862SJeremy Soller static void kb_led_hotkey_down(struct system76_data *data) 269fd13c862SJeremy Soller { 270fd13c862SJeremy Soller int i; 271fd13c862SJeremy Soller 272fd13c862SJeremy Soller if (data->kb_brightness > 0) { 273fd13c862SJeremy Soller for (i = ARRAY_SIZE(kb_levels); i > 0; i--) { 274fd13c862SJeremy Soller if (kb_levels[i - 1] < data->kb_brightness) { 275fd13c862SJeremy Soller kb_led_set(&data->kb_led, kb_levels[i - 1]); 276fd13c862SJeremy Soller break; 277fd13c862SJeremy Soller } 278fd13c862SJeremy Soller } 279fd13c862SJeremy Soller } else { 280fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 281fd13c862SJeremy Soller } 282fd13c862SJeremy Soller kb_led_notify(data); 283fd13c862SJeremy Soller } 284fd13c862SJeremy Soller 285fd13c862SJeremy Soller // Increase the keyboard LED brightness 286fd13c862SJeremy Soller static void kb_led_hotkey_up(struct system76_data *data) 287fd13c862SJeremy Soller { 288fd13c862SJeremy Soller int i; 289fd13c862SJeremy Soller 290fd13c862SJeremy Soller if (data->kb_brightness > 0) { 291fd13c862SJeremy Soller for (i = 0; i < ARRAY_SIZE(kb_levels); i++) { 292fd13c862SJeremy Soller if (kb_levels[i] > data->kb_brightness) { 293fd13c862SJeremy Soller kb_led_set(&data->kb_led, kb_levels[i]); 294fd13c862SJeremy Soller break; 295fd13c862SJeremy Soller } 296fd13c862SJeremy Soller } 297fd13c862SJeremy Soller } else { 298fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 299fd13c862SJeremy Soller } 300fd13c862SJeremy Soller kb_led_notify(data); 301fd13c862SJeremy Soller } 302fd13c862SJeremy Soller 303fd13c862SJeremy Soller // Cycle the keyboard LED color 304fd13c862SJeremy Soller static void kb_led_hotkey_color(struct system76_data *data) 305fd13c862SJeremy Soller { 306fd13c862SJeremy Soller int i; 307fd13c862SJeremy Soller 308fd13c862SJeremy Soller if (data->kb_color < 0) 309fd13c862SJeremy Soller return; 310fd13c862SJeremy Soller if (data->kb_brightness > 0) { 311fd13c862SJeremy Soller for (i = 0; i < ARRAY_SIZE(kb_colors); i++) { 312fd13c862SJeremy Soller if (kb_colors[i] == data->kb_color) 313fd13c862SJeremy Soller break; 314fd13c862SJeremy Soller } 315fd13c862SJeremy Soller i += 1; 316fd13c862SJeremy Soller if (i >= ARRAY_SIZE(kb_colors)) 317fd13c862SJeremy Soller i = 0; 318fd13c862SJeremy Soller data->kb_color = kb_colors[i]; 319fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 320fd13c862SJeremy Soller } else { 321fd13c862SJeremy Soller kb_led_set(&data->kb_led, data->kb_toggle_brightness); 322fd13c862SJeremy Soller } 323fd13c862SJeremy Soller kb_led_notify(data); 324fd13c862SJeremy Soller } 325fd13c862SJeremy Soller 326*95563d45SJeremy Soller static umode_t thermal_is_visible(const void *drvdata, enum hwmon_sensor_types type, 327*95563d45SJeremy Soller u32 attr, int channel) 328*95563d45SJeremy Soller { 329*95563d45SJeremy Soller const struct system76_data *data = drvdata; 330*95563d45SJeremy Soller 331*95563d45SJeremy Soller switch (type) { 332*95563d45SJeremy Soller case hwmon_fan: 333*95563d45SJeremy Soller case hwmon_pwm: 334*95563d45SJeremy Soller if (system76_name(data->nfan, channel)) 335*95563d45SJeremy Soller return 0444; 336*95563d45SJeremy Soller break; 337*95563d45SJeremy Soller 338*95563d45SJeremy Soller case hwmon_temp: 339*95563d45SJeremy Soller if (system76_name(data->ntmp, channel)) 340*95563d45SJeremy Soller return 0444; 341*95563d45SJeremy Soller break; 342*95563d45SJeremy Soller 343*95563d45SJeremy Soller default: 344*95563d45SJeremy Soller return 0; 345*95563d45SJeremy Soller } 346*95563d45SJeremy Soller 347*95563d45SJeremy Soller return 0; 348*95563d45SJeremy Soller } 349*95563d45SJeremy Soller 350*95563d45SJeremy Soller static int thermal_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, 351*95563d45SJeremy Soller int channel, long *val) 352*95563d45SJeremy Soller { 353*95563d45SJeremy Soller struct system76_data *data = dev_get_drvdata(dev); 354*95563d45SJeremy Soller int raw; 355*95563d45SJeremy Soller 356*95563d45SJeremy Soller switch (type) { 357*95563d45SJeremy Soller case hwmon_fan: 358*95563d45SJeremy Soller if (attr == hwmon_fan_input) { 359*95563d45SJeremy Soller raw = system76_get_index(data, "GFAN", channel); 360*95563d45SJeremy Soller if (raw < 0) 361*95563d45SJeremy Soller return raw; 362*95563d45SJeremy Soller *val = (raw >> 8) & 0xFFFF; 363*95563d45SJeremy Soller return 0; 364*95563d45SJeremy Soller } 365*95563d45SJeremy Soller break; 366*95563d45SJeremy Soller 367*95563d45SJeremy Soller case hwmon_pwm: 368*95563d45SJeremy Soller if (attr == hwmon_pwm_input) { 369*95563d45SJeremy Soller raw = system76_get_index(data, "GFAN", channel); 370*95563d45SJeremy Soller if (raw < 0) 371*95563d45SJeremy Soller return raw; 372*95563d45SJeremy Soller *val = raw & 0xFF; 373*95563d45SJeremy Soller return 0; 374*95563d45SJeremy Soller } 375*95563d45SJeremy Soller break; 376*95563d45SJeremy Soller 377*95563d45SJeremy Soller case hwmon_temp: 378*95563d45SJeremy Soller if (attr == hwmon_temp_input) { 379*95563d45SJeremy Soller raw = system76_get_index(data, "GTMP", channel); 380*95563d45SJeremy Soller if (raw < 0) 381*95563d45SJeremy Soller return raw; 382*95563d45SJeremy Soller *val = raw * 1000; 383*95563d45SJeremy Soller return 0; 384*95563d45SJeremy Soller } 385*95563d45SJeremy Soller break; 386*95563d45SJeremy Soller 387*95563d45SJeremy Soller default: 388*95563d45SJeremy Soller return -EOPNOTSUPP; 389*95563d45SJeremy Soller } 390*95563d45SJeremy Soller 391*95563d45SJeremy Soller return -EOPNOTSUPP; 392*95563d45SJeremy Soller } 393*95563d45SJeremy Soller 394*95563d45SJeremy Soller static int thermal_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, 395*95563d45SJeremy Soller int channel, const char **str) 396*95563d45SJeremy Soller { 397*95563d45SJeremy Soller struct system76_data *data = dev_get_drvdata(dev); 398*95563d45SJeremy Soller 399*95563d45SJeremy Soller switch (type) { 400*95563d45SJeremy Soller case hwmon_fan: 401*95563d45SJeremy Soller if (attr == hwmon_fan_label) { 402*95563d45SJeremy Soller *str = system76_name(data->nfan, channel); 403*95563d45SJeremy Soller if (*str) 404*95563d45SJeremy Soller return 0; 405*95563d45SJeremy Soller } 406*95563d45SJeremy Soller break; 407*95563d45SJeremy Soller 408*95563d45SJeremy Soller case hwmon_temp: 409*95563d45SJeremy Soller if (attr == hwmon_temp_label) { 410*95563d45SJeremy Soller *str = system76_name(data->ntmp, channel); 411*95563d45SJeremy Soller if (*str) 412*95563d45SJeremy Soller return 0; 413*95563d45SJeremy Soller } 414*95563d45SJeremy Soller break; 415*95563d45SJeremy Soller 416*95563d45SJeremy Soller default: 417*95563d45SJeremy Soller return -EOPNOTSUPP; 418*95563d45SJeremy Soller } 419*95563d45SJeremy Soller 420*95563d45SJeremy Soller return -EOPNOTSUPP; 421*95563d45SJeremy Soller } 422*95563d45SJeremy Soller 423*95563d45SJeremy Soller static const struct hwmon_ops thermal_ops = { 424*95563d45SJeremy Soller .is_visible = thermal_is_visible, 425*95563d45SJeremy Soller .read = thermal_read, 426*95563d45SJeremy Soller .read_string = thermal_read_string, 427*95563d45SJeremy Soller }; 428*95563d45SJeremy Soller 429*95563d45SJeremy Soller // Allocate up to 8 fans and temperatures 430*95563d45SJeremy Soller static const struct hwmon_channel_info *thermal_channel_info[] = { 431*95563d45SJeremy Soller HWMON_CHANNEL_INFO(fan, 432*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 433*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 434*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 435*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 436*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 437*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 438*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL, 439*95563d45SJeremy Soller HWMON_F_INPUT | HWMON_F_LABEL), 440*95563d45SJeremy Soller HWMON_CHANNEL_INFO(pwm, 441*95563d45SJeremy Soller HWMON_PWM_INPUT, 442*95563d45SJeremy Soller HWMON_PWM_INPUT, 443*95563d45SJeremy Soller HWMON_PWM_INPUT, 444*95563d45SJeremy Soller HWMON_PWM_INPUT, 445*95563d45SJeremy Soller HWMON_PWM_INPUT, 446*95563d45SJeremy Soller HWMON_PWM_INPUT, 447*95563d45SJeremy Soller HWMON_PWM_INPUT, 448*95563d45SJeremy Soller HWMON_PWM_INPUT), 449*95563d45SJeremy Soller HWMON_CHANNEL_INFO(temp, 450*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 451*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 452*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 453*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 454*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 455*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 456*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL, 457*95563d45SJeremy Soller HWMON_T_INPUT | HWMON_T_LABEL), 458*95563d45SJeremy Soller NULL 459*95563d45SJeremy Soller }; 460*95563d45SJeremy Soller 461*95563d45SJeremy Soller static const struct hwmon_chip_info thermal_chip_info = { 462*95563d45SJeremy Soller .ops = &thermal_ops, 463*95563d45SJeremy Soller .info = thermal_channel_info, 464*95563d45SJeremy Soller }; 465*95563d45SJeremy Soller 466fd13c862SJeremy Soller // Handle ACPI notification 467fd13c862SJeremy Soller static void system76_notify(struct acpi_device *acpi_dev, u32 event) 468fd13c862SJeremy Soller { 469fd13c862SJeremy Soller struct system76_data *data; 470fd13c862SJeremy Soller 471fd13c862SJeremy Soller data = acpi_driver_data(acpi_dev); 472fd13c862SJeremy Soller switch (event) { 473fd13c862SJeremy Soller case 0x80: 474fd13c862SJeremy Soller kb_led_hotkey_hardware(data); 475fd13c862SJeremy Soller break; 476fd13c862SJeremy Soller case 0x81: 477fd13c862SJeremy Soller kb_led_hotkey_toggle(data); 478fd13c862SJeremy Soller break; 479fd13c862SJeremy Soller case 0x82: 480fd13c862SJeremy Soller kb_led_hotkey_down(data); 481fd13c862SJeremy Soller break; 482fd13c862SJeremy Soller case 0x83: 483fd13c862SJeremy Soller kb_led_hotkey_up(data); 484fd13c862SJeremy Soller break; 485fd13c862SJeremy Soller case 0x84: 486fd13c862SJeremy Soller kb_led_hotkey_color(data); 487fd13c862SJeremy Soller break; 488fd13c862SJeremy Soller } 489fd13c862SJeremy Soller } 490fd13c862SJeremy Soller 491fd13c862SJeremy Soller // Add a System76 ACPI device 492fd13c862SJeremy Soller static int system76_add(struct acpi_device *acpi_dev) 493fd13c862SJeremy Soller { 494fd13c862SJeremy Soller struct system76_data *data; 495fd13c862SJeremy Soller int err; 496fd13c862SJeremy Soller 497fd13c862SJeremy Soller data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL); 498fd13c862SJeremy Soller if (!data) 499fd13c862SJeremy Soller return -ENOMEM; 500fd13c862SJeremy Soller acpi_dev->driver_data = data; 501fd13c862SJeremy Soller data->acpi_dev = acpi_dev; 502fd13c862SJeremy Soller 503fd13c862SJeremy Soller err = system76_get(data, "INIT"); 504fd13c862SJeremy Soller if (err) 505fd13c862SJeremy Soller return err; 506fd13c862SJeremy Soller data->ap_led.name = "system76_acpi::airplane"; 507fd13c862SJeremy Soller data->ap_led.flags = LED_CORE_SUSPENDRESUME; 508fd13c862SJeremy Soller data->ap_led.brightness_get = ap_led_get; 5095b36398dSNick Shipp data->ap_led.brightness_set_blocking = ap_led_set; 510fd13c862SJeremy Soller data->ap_led.max_brightness = 1; 511fd13c862SJeremy Soller data->ap_led.default_trigger = "rfkill-none"; 512fd13c862SJeremy Soller err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led); 513fd13c862SJeremy Soller if (err) 514fd13c862SJeremy Soller return err; 515fd13c862SJeremy Soller 516fd13c862SJeremy Soller data->kb_led.name = "system76_acpi::kbd_backlight"; 517fd13c862SJeremy Soller data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME; 518fd13c862SJeremy Soller data->kb_led.brightness_get = kb_led_get; 5195b36398dSNick Shipp data->kb_led.brightness_set_blocking = kb_led_set; 520fd13c862SJeremy Soller if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) { 521fd13c862SJeremy Soller data->kb_led.max_brightness = 255; 522fd13c862SJeremy Soller data->kb_toggle_brightness = 72; 523fd13c862SJeremy Soller data->kb_color = 0xffffff; 524fd13c862SJeremy Soller system76_set(data, "SKBC", data->kb_color); 525fd13c862SJeremy Soller } else { 526fd13c862SJeremy Soller data->kb_led.max_brightness = 5; 527fd13c862SJeremy Soller data->kb_color = -1; 528fd13c862SJeremy Soller } 529fd13c862SJeremy Soller err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led); 530fd13c862SJeremy Soller if (err) 531fd13c862SJeremy Soller return err; 532fd13c862SJeremy Soller 533fd13c862SJeremy Soller if (data->kb_color >= 0) { 534fd13c862SJeremy Soller err = device_create_file( 535fd13c862SJeremy Soller data->kb_led.dev, 536fd13c862SJeremy Soller &kb_led_color_dev_attr 537fd13c862SJeremy Soller ); 538fd13c862SJeremy Soller if (err) 539fd13c862SJeremy Soller return err; 540fd13c862SJeremy Soller } 541fd13c862SJeremy Soller 542*95563d45SJeremy Soller err = system76_get_object(data, "NFAN", &data->nfan); 543*95563d45SJeremy Soller if (err) 544*95563d45SJeremy Soller goto error; 545*95563d45SJeremy Soller 546*95563d45SJeremy Soller err = system76_get_object(data, "NTMP", &data->ntmp); 547*95563d45SJeremy Soller if (err) 548*95563d45SJeremy Soller goto error; 549*95563d45SJeremy Soller 550*95563d45SJeremy Soller data->therm = devm_hwmon_device_register_with_info(&acpi_dev->dev, 551*95563d45SJeremy Soller "system76_acpi", data, &thermal_chip_info, NULL); 552*95563d45SJeremy Soller err = PTR_ERR_OR_ZERO(data->therm); 553*95563d45SJeremy Soller if (err) 554*95563d45SJeremy Soller goto error; 555*95563d45SJeremy Soller 556fd13c862SJeremy Soller return 0; 557*95563d45SJeremy Soller 558*95563d45SJeremy Soller error: 559*95563d45SJeremy Soller kfree(data->ntmp); 560*95563d45SJeremy Soller kfree(data->nfan); 561*95563d45SJeremy Soller return err; 562fd13c862SJeremy Soller } 563fd13c862SJeremy Soller 564fd13c862SJeremy Soller // Remove a System76 ACPI device 565fd13c862SJeremy Soller static int system76_remove(struct acpi_device *acpi_dev) 566fd13c862SJeremy Soller { 567fd13c862SJeremy Soller struct system76_data *data; 568fd13c862SJeremy Soller 569fd13c862SJeremy Soller data = acpi_driver_data(acpi_dev); 570fd13c862SJeremy Soller if (data->kb_color >= 0) 571fd13c862SJeremy Soller device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); 572fd13c862SJeremy Soller 573fd13c862SJeremy Soller devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led); 574fd13c862SJeremy Soller devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led); 575fd13c862SJeremy Soller 576*95563d45SJeremy Soller kfree(data->nfan); 577*95563d45SJeremy Soller kfree(data->ntmp); 578*95563d45SJeremy Soller 579fd13c862SJeremy Soller system76_get(data, "FINI"); 580fd13c862SJeremy Soller 581fd13c862SJeremy Soller return 0; 582fd13c862SJeremy Soller } 583fd13c862SJeremy Soller 584fd13c862SJeremy Soller static struct acpi_driver system76_driver = { 585fd13c862SJeremy Soller .name = "System76 ACPI Driver", 586fd13c862SJeremy Soller .class = "hotkey", 587fd13c862SJeremy Soller .ids = device_ids, 588fd13c862SJeremy Soller .ops = { 589fd13c862SJeremy Soller .add = system76_add, 590fd13c862SJeremy Soller .remove = system76_remove, 591fd13c862SJeremy Soller .notify = system76_notify, 592fd13c862SJeremy Soller }, 593fd13c862SJeremy Soller }; 594fd13c862SJeremy Soller module_acpi_driver(system76_driver); 595fd13c862SJeremy Soller 596fd13c862SJeremy Soller MODULE_DESCRIPTION("System76 ACPI Driver"); 597fd13c862SJeremy Soller MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>"); 598fd13c862SJeremy Soller MODULE_LICENSE("GPL"); 599