| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Samsung Galaxy Book driver |
| * |
| * Copyright (c) 2025 Joshua Grisham <josh@joshuagrisham.com> |
| * |
| * With contributions to the SCAI ACPI device interface: |
| * Copyright (c) 2024 Giulio Girardi <giulio.girardi@protechgroup.it> |
| * |
| * Implementation inspired by existing x86 platform drivers. |
| * Thank you to the authors! |
| */ |
| |
| #include <linux/acpi.h> |
| #include <linux/bits.h> |
| #include <linux/err.h> |
| #include <linux/i8042.h> |
| #include <linux/init.h> |
| #include <linux/input.h> |
| #include <linux/kernel.h> |
| #include <linux/leds.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/platform_profile.h> |
| #include <linux/serio.h> |
| #include <linux/sysfs.h> |
| #include <linux/uuid.h> |
| #include <linux/workqueue.h> |
| #include <acpi/battery.h> |
| #include "firmware_attributes_class.h" |
| |
| #define DRIVER_NAME "samsung-galaxybook" |
| |
| struct samsung_galaxybook { |
| struct platform_device *platform; |
| struct acpi_device *acpi; |
| |
| struct device *fw_attrs_dev; |
| struct kset *fw_attrs_kset; |
| /* block in case firmware attributes are updated in multiple threads */ |
| struct mutex fw_attr_lock; |
| |
| bool has_kbd_backlight; |
| bool has_block_recording; |
| bool has_performance_mode; |
| |
| struct led_classdev kbd_backlight; |
| struct work_struct kbd_backlight_hotkey_work; |
| /* block in case brightness updated using hotkey and another thread */ |
| struct mutex kbd_backlight_lock; |
| |
| void *i8042_filter_ptr; |
| |
| struct work_struct block_recording_hotkey_work; |
| struct input_dev *camera_lens_cover_switch; |
| |
| struct acpi_battery_hook battery_hook; |
| |
| u8 profile_performance_modes[PLATFORM_PROFILE_LAST]; |
| }; |
| |
| enum galaxybook_fw_attr_id { |
| GB_ATTR_POWER_ON_LID_OPEN, |
| GB_ATTR_USB_CHARGING, |
| GB_ATTR_BLOCK_RECORDING, |
| }; |
| |
| static const char * const galaxybook_fw_attr_name[] = { |
| [GB_ATTR_POWER_ON_LID_OPEN] = "power_on_lid_open", |
| [GB_ATTR_USB_CHARGING] = "usb_charging", |
| [GB_ATTR_BLOCK_RECORDING] = "block_recording", |
| }; |
| |
| static const char * const galaxybook_fw_attr_desc[] = { |
| [GB_ATTR_POWER_ON_LID_OPEN] = "Power On Lid Open", |
| [GB_ATTR_USB_CHARGING] = "USB Charging", |
| [GB_ATTR_BLOCK_RECORDING] = "Block Recording", |
| }; |
| |
| #define GB_ATTR_LANGUAGE_CODE "en_US.UTF-8" |
| |
| struct galaxybook_fw_attr { |
| struct samsung_galaxybook *galaxybook; |
| enum galaxybook_fw_attr_id fw_attr_id; |
| struct attribute_group attr_group; |
| struct kobj_attribute display_name; |
| struct kobj_attribute current_value; |
| int (*get_value)(struct samsung_galaxybook *galaxybook, bool *value); |
| int (*set_value)(struct samsung_galaxybook *galaxybook, const bool value); |
| }; |
| |
| struct sawb { |
| u16 safn; |
| u16 sasb; |
| u8 rflg; |
| union { |
| struct { |
| u8 gunm; |
| u8 guds[250]; |
| } __packed; |
| struct { |
| u8 caid[16]; |
| u8 fncn; |
| u8 subn; |
| u8 iob0; |
| u8 iob1; |
| u8 iob2; |
| u8 iob3; |
| u8 iob4; |
| u8 iob5; |
| u8 iob6; |
| u8 iob7; |
| u8 iob8; |
| u8 iob9; |
| } __packed; |
| struct { |
| u8 iob_prefix[18]; |
| u8 iobs[10]; |
| } __packed; |
| } __packed; |
| } __packed; |
| |
| #define GB_SAWB_LEN_SETTINGS 0x15 |
| #define GB_SAWB_LEN_PERFORMANCE_MODE 0x100 |
| |
| #define GB_SAFN 0x5843 |
| |
| #define GB_SASB_KBD_BACKLIGHT 0x78 |
| #define GB_SASB_POWER_MANAGEMENT 0x7a |
| #define GB_SASB_USB_CHARGING_GET 0x67 |
| #define GB_SASB_USB_CHARGING_SET 0x68 |
| #define GB_SASB_NOTIFICATIONS 0x86 |
| #define GB_SASB_BLOCK_RECORDING 0x8a |
| #define GB_SASB_PERFORMANCE_MODE 0x91 |
| |
| #define GB_SAWB_RFLG_POS 4 |
| #define GB_SAWB_GB_GUNM_POS 5 |
| |
| #define GB_RFLG_SUCCESS 0xaa |
| #define GB_GUNM_FAIL 0xff |
| |
| #define GB_GUNM_FEATURE_ENABLE 0xbb |
| #define GB_GUNM_FEATURE_ENABLE_SUCCESS 0xdd |
| #define GB_GUDS_FEATURE_ENABLE 0xaa |
| #define GB_GUDS_FEATURE_ENABLE_SUCCESS 0xcc |
| |
| #define GB_GUNM_GET 0x81 |
| #define GB_GUNM_SET 0x82 |
| |
| #define GB_GUNM_POWER_MANAGEMENT 0x82 |
| |
| #define GB_GUNM_USB_CHARGING_GET 0x80 |
| #define GB_GUNM_USB_CHARGING_ON 0x81 |
| #define GB_GUNM_USB_CHARGING_OFF 0x80 |
| #define GB_GUDS_POWER_ON_LID_OPEN 0xa3 |
| #define GB_GUDS_POWER_ON_LID_OPEN_GET 0x81 |
| #define GB_GUDS_POWER_ON_LID_OPEN_SET 0x80 |
| #define GB_GUDS_BATTERY_CHARGE_CONTROL 0xe9 |
| #define GB_GUDS_BATTERY_CHARGE_CONTROL_GET 0x91 |
| #define GB_GUDS_BATTERY_CHARGE_CONTROL_SET 0x90 |
| #define GB_GUNM_ACPI_NOTIFY_ENABLE 0x80 |
| #define GB_GUDS_ACPI_NOTIFY_ENABLE 0x02 |
| |
| #define GB_BLOCK_RECORDING_ON 0x0 |
| #define GB_BLOCK_RECORDING_OFF 0x1 |
| |
| #define GB_FNCN_PERFORMANCE_MODE 0x51 |
| #define GB_SUBN_PERFORMANCE_MODE_LIST 0x01 |
| #define GB_SUBN_PERFORMANCE_MODE_GET 0x02 |
| #define GB_SUBN_PERFORMANCE_MODE_SET 0x03 |
| |
| /* guid 8246028d-8bca-4a55-ba0f-6f1e6b921b8f */ |
| static const guid_t performance_mode_guid = |
| GUID_INIT(0x8246028d, 0x8bca, 0x4a55, 0xba, 0x0f, 0x6f, 0x1e, 0x6b, 0x92, 0x1b, 0x8f); |
| #define GB_PERFORMANCE_MODE_GUID performance_mode_guid |
| |
| #define GB_PERFORMANCE_MODE_FANOFF 0xb |
| #define GB_PERFORMANCE_MODE_LOWNOISE 0xa |
| #define GB_PERFORMANCE_MODE_OPTIMIZED 0x0 |
| #define GB_PERFORMANCE_MODE_OPTIMIZED_V2 0x2 |
| #define GB_PERFORMANCE_MODE_PERFORMANCE 0x1 |
| #define GB_PERFORMANCE_MODE_PERFORMANCE_V2 0x15 |
| #define GB_PERFORMANCE_MODE_ULTRA 0x16 |
| #define GB_PERFORMANCE_MODE_IGNORE1 0x14 |
| #define GB_PERFORMANCE_MODE_IGNORE2 0xc |
| |
| #define GB_ACPI_METHOD_ENABLE "SDLS" |
| #define GB_ACPI_METHOD_ENABLE_ON 1 |
| #define GB_ACPI_METHOD_ENABLE_OFF 0 |
| #define GB_ACPI_METHOD_SETTINGS "CSFI" |
| #define GB_ACPI_METHOD_PERFORMANCE_MODE "CSXI" |
| |
| #define GB_KBD_BACKLIGHT_MAX_BRIGHTNESS 3 |
| |
| #define GB_ACPI_NOTIFY_BATTERY_STATE_CHANGED 0x61 |
| #define GB_ACPI_NOTIFY_DEVICE_ON_TABLE 0x6c |
| #define GB_ACPI_NOTIFY_DEVICE_OFF_TABLE 0x6d |
| #define GB_ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE 0x70 |
| |
| #define GB_KEY_KBD_BACKLIGHT_KEYDOWN 0x2c |
| #define GB_KEY_KBD_BACKLIGHT_KEYUP 0xac |
| #define GB_KEY_BLOCK_RECORDING_KEYDOWN 0x1f |
| #define GB_KEY_BLOCK_RECORDING_KEYUP 0x9f |
| #define GB_KEY_BATTERY_NOTIFY_KEYUP 0xf |
| #define GB_KEY_BATTERY_NOTIFY_KEYDOWN 0x8f |
| |
| /* |
| * Optional features which have been determined as not supported on a particular |
| * device will return GB_NOT_SUPPORTED from their init function. Positive |
| * EOPNOTSUPP is used as the underlying value instead of negative to |
| * differentiate this return code from valid upstream failures. |
| */ |
| #define GB_NOT_SUPPORTED EOPNOTSUPP /* Galaxy Book feature not supported */ |
| |
| /* |
| * ACPI method handling |
| */ |
| |
| static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method, |
| struct sawb *buf, size_t len) |
| { |
| struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; |
| union acpi_object in_obj, *out_obj; |
| struct acpi_object_list input; |
| acpi_status status; |
| int err; |
| |
| in_obj.type = ACPI_TYPE_BUFFER; |
| in_obj.buffer.length = len; |
| in_obj.buffer.pointer = (u8 *)buf; |
| |
| input.count = 1; |
| input.pointer = &in_obj; |
| |
| status = acpi_evaluate_object_typed(galaxybook->acpi->handle, method, &input, &output, |
| ACPI_TYPE_BUFFER); |
| |
| if (ACPI_FAILURE(status)) { |
| dev_err(&galaxybook->acpi->dev, "failed to execute method %s; got %s\n", |
| method, acpi_format_exception(status)); |
| return -EIO; |
| } |
| |
| out_obj = output.pointer; |
| |
| if (out_obj->buffer.length != len || out_obj->buffer.length < GB_SAWB_GB_GUNM_POS + 1) { |
| dev_err(&galaxybook->acpi->dev, |
| "failed to execute %s; response length mismatch\n", |
| method); |
| err = -EPROTO; |
| goto out_free; |
| } |
| if (out_obj->buffer.pointer[GB_SAWB_RFLG_POS] != GB_RFLG_SUCCESS) { |
| dev_err(&galaxybook->acpi->dev, |
| "failed to execute %s; device did not respond with success code 0x%x\n", |
| method, GB_RFLG_SUCCESS); |
| err = -ENXIO; |
| goto out_free; |
| } |
| if (out_obj->buffer.pointer[GB_SAWB_GB_GUNM_POS] == GB_GUNM_FAIL) { |
| dev_err(&galaxybook->acpi->dev, |
| "failed to execute %s; device responded with failure code 0x%x\n", |
| method, GB_GUNM_FAIL); |
| err = -ENXIO; |
| goto out_free; |
| } |
| |
| memcpy(buf, out_obj->buffer.pointer, len); |
| err = 0; |
| |
| out_free: |
| kfree(out_obj); |
| return err; |
| } |
| |
| static int galaxybook_enable_acpi_feature(struct samsung_galaxybook *galaxybook, const u16 sasb) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = sasb; |
| buf.gunm = GB_GUNM_FEATURE_ENABLE; |
| buf.guds[0] = GB_GUDS_FEATURE_ENABLE; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| if (buf.gunm != GB_GUNM_FEATURE_ENABLE_SUCCESS && |
| buf.guds[0] != GB_GUDS_FEATURE_ENABLE_SUCCESS) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| /* |
| * Keyboard Backlight |
| */ |
| |
| static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook, |
| enum led_brightness *brightness) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_KBD_BACKLIGHT; |
| buf.gunm = GB_GUNM_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| *brightness = buf.gunm; |
| |
| return 0; |
| } |
| |
| static int kbd_backlight_acpi_set(struct samsung_galaxybook *galaxybook, |
| const enum led_brightness brightness) |
| { |
| struct sawb buf = {}; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_KBD_BACKLIGHT; |
| buf.gunm = GB_GUNM_SET; |
| |
| buf.guds[0] = brightness; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| } |
| |
| static enum led_brightness kbd_backlight_show(struct led_classdev *led) |
| { |
| struct samsung_galaxybook *galaxybook = |
| container_of(led, struct samsung_galaxybook, kbd_backlight); |
| enum led_brightness brightness; |
| int err; |
| |
| err = kbd_backlight_acpi_get(galaxybook, &brightness); |
| if (err) |
| return err; |
| |
| return brightness; |
| } |
| |
| static int kbd_backlight_store(struct led_classdev *led, |
| const enum led_brightness brightness) |
| { |
| struct samsung_galaxybook *galaxybook = |
| container_of_const(led, struct samsung_galaxybook, kbd_backlight); |
| |
| return kbd_backlight_acpi_set(galaxybook, brightness); |
| } |
| |
| static int galaxybook_kbd_backlight_init(struct samsung_galaxybook *galaxybook) |
| { |
| struct led_init_data init_data = {}; |
| enum led_brightness brightness; |
| int err; |
| |
| err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->kbd_backlight_lock); |
| if (err) |
| return err; |
| |
| err = galaxybook_enable_acpi_feature(galaxybook, GB_SASB_KBD_BACKLIGHT); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to enable kbd_backlight feature, error %d\n", err); |
| return GB_NOT_SUPPORTED; |
| } |
| |
| err = kbd_backlight_acpi_get(galaxybook, &brightness); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to get initial kbd_backlight brightness, error %d\n", err); |
| return GB_NOT_SUPPORTED; |
| } |
| |
| init_data.devicename = DRIVER_NAME; |
| init_data.default_label = ":" LED_FUNCTION_KBD_BACKLIGHT; |
| init_data.devname_mandatory = true; |
| |
| galaxybook->kbd_backlight.brightness_get = kbd_backlight_show; |
| galaxybook->kbd_backlight.brightness_set_blocking = kbd_backlight_store; |
| galaxybook->kbd_backlight.flags = LED_BRIGHT_HW_CHANGED; |
| galaxybook->kbd_backlight.max_brightness = GB_KBD_BACKLIGHT_MAX_BRIGHTNESS; |
| |
| return devm_led_classdev_register_ext(&galaxybook->platform->dev, |
| &galaxybook->kbd_backlight, &init_data); |
| } |
| |
| /* |
| * Battery Extension (adds charge_control_end_threshold to the battery device) |
| */ |
| |
| static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook *galaxybook, u8 *value) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_POWER_MANAGEMENT; |
| buf.gunm = GB_GUNM_POWER_MANAGEMENT; |
| buf.guds[0] = GB_GUDS_BATTERY_CHARGE_CONTROL; |
| buf.guds[1] = GB_GUDS_BATTERY_CHARGE_CONTROL_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| *value = buf.guds[1]; |
| |
| return 0; |
| } |
| |
| static int charge_control_end_threshold_acpi_set(struct samsung_galaxybook *galaxybook, u8 value) |
| { |
| struct sawb buf = {}; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_POWER_MANAGEMENT; |
| buf.gunm = GB_GUNM_POWER_MANAGEMENT; |
| buf.guds[0] = GB_GUDS_BATTERY_CHARGE_CONTROL; |
| buf.guds[1] = GB_GUDS_BATTERY_CHARGE_CONTROL_SET; |
| buf.guds[2] = value; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| } |
| |
| static int galaxybook_battery_ext_property_get(struct power_supply *psy, |
| const struct power_supply_ext *ext, |
| void *ext_data, |
| enum power_supply_property psp, |
| union power_supply_propval *val) |
| { |
| struct samsung_galaxybook *galaxybook = ext_data; |
| int err; |
| |
| if (psp != POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) |
| return -EINVAL; |
| |
| err = charge_control_end_threshold_acpi_get(galaxybook, (u8 *)&val->intval); |
| if (err) |
| return err; |
| |
| /* |
| * device stores "no end threshold" as 0 instead of 100; |
| * if device has 0, report 100 |
| */ |
| if (val->intval == 0) |
| val->intval = 100; |
| |
| return 0; |
| } |
| |
| static int galaxybook_battery_ext_property_set(struct power_supply *psy, |
| const struct power_supply_ext *ext, |
| void *ext_data, |
| enum power_supply_property psp, |
| const union power_supply_propval *val) |
| { |
| struct samsung_galaxybook *galaxybook = ext_data; |
| u8 value; |
| |
| if (psp != POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) |
| return -EINVAL; |
| |
| value = val->intval; |
| |
| if (value < 1 || value > 100) |
| return -EINVAL; |
| |
| /* |
| * device stores "no end threshold" as 0 instead of 100; |
| * if setting to 100, send 0 |
| */ |
| if (value == 100) |
| value = 0; |
| |
| return charge_control_end_threshold_acpi_set(galaxybook, value); |
| } |
| |
| static int galaxybook_battery_ext_property_is_writeable(struct power_supply *psy, |
| const struct power_supply_ext *ext, |
| void *ext_data, |
| enum power_supply_property psp) |
| { |
| if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD) |
| return true; |
| |
| return false; |
| } |
| |
| static const enum power_supply_property galaxybook_battery_properties[] = { |
| POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, |
| }; |
| |
| static const struct power_supply_ext galaxybook_battery_ext = { |
| .name = DRIVER_NAME, |
| .properties = galaxybook_battery_properties, |
| .num_properties = ARRAY_SIZE(galaxybook_battery_properties), |
| .get_property = galaxybook_battery_ext_property_get, |
| .set_property = galaxybook_battery_ext_property_set, |
| .property_is_writeable = galaxybook_battery_ext_property_is_writeable, |
| }; |
| |
| static int galaxybook_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) |
| { |
| struct samsung_galaxybook *galaxybook = |
| container_of(hook, struct samsung_galaxybook, battery_hook); |
| |
| return power_supply_register_extension(battery, &galaxybook_battery_ext, |
| &battery->dev, galaxybook); |
| } |
| |
| static int galaxybook_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) |
| { |
| power_supply_unregister_extension(battery, &galaxybook_battery_ext); |
| return 0; |
| } |
| |
| static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybook) |
| { |
| u8 value; |
| int err; |
| |
| err = charge_control_end_threshold_acpi_get(galaxybook, &value); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to get initial battery charge end threshold, error %d\n", err); |
| return 0; |
| } |
| |
| galaxybook->battery_hook.add_battery = galaxybook_battery_add; |
| galaxybook->battery_hook.remove_battery = galaxybook_battery_remove; |
| galaxybook->battery_hook.name = "Samsung Galaxy Book Battery Extension"; |
| |
| return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); |
| } |
| |
| /* |
| * Platform Profile / Performance mode |
| */ |
| |
| static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook, u8 *performance_mode) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_PERFORMANCE_MODE; |
| export_guid(buf.caid, &GB_PERFORMANCE_MODE_GUID); |
| buf.fncn = GB_FNCN_PERFORMANCE_MODE; |
| buf.subn = GB_SUBN_PERFORMANCE_MODE_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_PERFORMANCE_MODE, |
| &buf, GB_SAWB_LEN_PERFORMANCE_MODE); |
| if (err) |
| return err; |
| |
| *performance_mode = buf.iob0; |
| |
| return 0; |
| } |
| |
| static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook, |
| const u8 performance_mode) |
| { |
| struct sawb buf = {}; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_PERFORMANCE_MODE; |
| export_guid(buf.caid, &GB_PERFORMANCE_MODE_GUID); |
| buf.fncn = GB_FNCN_PERFORMANCE_MODE; |
| buf.subn = GB_SUBN_PERFORMANCE_MODE_SET; |
| buf.iob0 = performance_mode; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_PERFORMANCE_MODE, |
| &buf, GB_SAWB_LEN_PERFORMANCE_MODE); |
| } |
| |
| static int get_performance_mode_profile(struct samsung_galaxybook *galaxybook, |
| const u8 performance_mode, |
| enum platform_profile_option *profile) |
| { |
| switch (performance_mode) { |
| case GB_PERFORMANCE_MODE_FANOFF: |
| *profile = PLATFORM_PROFILE_LOW_POWER; |
| break; |
| case GB_PERFORMANCE_MODE_LOWNOISE: |
| *profile = PLATFORM_PROFILE_QUIET; |
| break; |
| case GB_PERFORMANCE_MODE_OPTIMIZED: |
| case GB_PERFORMANCE_MODE_OPTIMIZED_V2: |
| *profile = PLATFORM_PROFILE_BALANCED; |
| break; |
| case GB_PERFORMANCE_MODE_PERFORMANCE: |
| case GB_PERFORMANCE_MODE_PERFORMANCE_V2: |
| case GB_PERFORMANCE_MODE_ULTRA: |
| *profile = PLATFORM_PROFILE_PERFORMANCE; |
| break; |
| case GB_PERFORMANCE_MODE_IGNORE1: |
| case GB_PERFORMANCE_MODE_IGNORE2: |
| return -EOPNOTSUPP; |
| default: |
| dev_warn(&galaxybook->platform->dev, |
| "unrecognized performance mode 0x%x\n", performance_mode); |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static int galaxybook_platform_profile_get(struct device *dev, |
| enum platform_profile_option *profile) |
| { |
| struct samsung_galaxybook *galaxybook = dev_get_drvdata(dev); |
| u8 performance_mode; |
| int err; |
| |
| err = performance_mode_acpi_get(galaxybook, &performance_mode); |
| if (err) |
| return err; |
| |
| return get_performance_mode_profile(galaxybook, performance_mode, profile); |
| } |
| |
| static int galaxybook_platform_profile_set(struct device *dev, |
| enum platform_profile_option profile) |
| { |
| struct samsung_galaxybook *galaxybook = dev_get_drvdata(dev); |
| |
| return performance_mode_acpi_set(galaxybook, |
| galaxybook->profile_performance_modes[profile]); |
| } |
| |
| static int galaxybook_platform_profile_probe(void *drvdata, unsigned long *choices) |
| { |
| struct samsung_galaxybook *galaxybook = drvdata; |
| u8 *perfmodes = galaxybook->profile_performance_modes; |
| enum platform_profile_option profile; |
| struct sawb buf = {}; |
| unsigned int i; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_PERFORMANCE_MODE; |
| export_guid(buf.caid, &GB_PERFORMANCE_MODE_GUID); |
| buf.fncn = GB_FNCN_PERFORMANCE_MODE; |
| buf.subn = GB_SUBN_PERFORMANCE_MODE_LIST; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_PERFORMANCE_MODE, |
| &buf, GB_SAWB_LEN_PERFORMANCE_MODE); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to get supported performance modes, error %d\n", err); |
| return err; |
| } |
| |
| /* set initial default profile performance mode values */ |
| perfmodes[PLATFORM_PROFILE_LOW_POWER] = GB_PERFORMANCE_MODE_FANOFF; |
| perfmodes[PLATFORM_PROFILE_QUIET] = GB_PERFORMANCE_MODE_LOWNOISE; |
| perfmodes[PLATFORM_PROFILE_BALANCED] = GB_PERFORMANCE_MODE_OPTIMIZED; |
| perfmodes[PLATFORM_PROFILE_PERFORMANCE] = GB_PERFORMANCE_MODE_PERFORMANCE; |
| |
| /* |
| * Value returned in iob0 will have the number of supported performance |
| * modes per device. The performance mode values will then be given as a |
| * list after this (iob1-iobX). Loop through the supported values and |
| * enable their mapped platform_profile choice, overriding "legacy" |
| * values along the way if a non-legacy value exists. |
| */ |
| for (i = 1; i <= buf.iob0; i++) { |
| err = get_performance_mode_profile(galaxybook, buf.iobs[i], &profile); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "ignoring unmapped performance mode 0x%x\n", buf.iobs[i]); |
| continue; |
| } |
| switch (buf.iobs[i]) { |
| case GB_PERFORMANCE_MODE_OPTIMIZED_V2: |
| perfmodes[profile] = GB_PERFORMANCE_MODE_OPTIMIZED_V2; |
| break; |
| case GB_PERFORMANCE_MODE_PERFORMANCE_V2: |
| /* only update if not already overwritten by Ultra */ |
| if (perfmodes[profile] != GB_PERFORMANCE_MODE_ULTRA) |
| perfmodes[profile] = GB_PERFORMANCE_MODE_PERFORMANCE_V2; |
| break; |
| case GB_PERFORMANCE_MODE_ULTRA: |
| perfmodes[profile] = GB_PERFORMANCE_MODE_ULTRA; |
| break; |
| default: |
| break; |
| } |
| set_bit(profile, choices); |
| dev_dbg(&galaxybook->platform->dev, |
| "setting platform profile %d to use performance mode 0x%x\n", |
| profile, perfmodes[profile]); |
| } |
| |
| /* initialize performance_mode using balanced's mapped value */ |
| if (test_bit(PLATFORM_PROFILE_BALANCED, choices)) |
| return performance_mode_acpi_set(galaxybook, perfmodes[PLATFORM_PROFILE_BALANCED]); |
| |
| return 0; |
| } |
| |
| static const struct platform_profile_ops galaxybook_platform_profile_ops = { |
| .probe = galaxybook_platform_profile_probe, |
| .profile_get = galaxybook_platform_profile_get, |
| .profile_set = galaxybook_platform_profile_set, |
| }; |
| |
| static int galaxybook_platform_profile_init(struct samsung_galaxybook *galaxybook) |
| { |
| struct device *platform_profile_dev; |
| u8 performance_mode; |
| int err; |
| |
| err = performance_mode_acpi_get(galaxybook, &performance_mode); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to get initial performance mode, error %d\n", err); |
| return GB_NOT_SUPPORTED; |
| } |
| |
| platform_profile_dev = devm_platform_profile_register(&galaxybook->platform->dev, |
| DRIVER_NAME, galaxybook, |
| &galaxybook_platform_profile_ops); |
| |
| return PTR_ERR_OR_ZERO(platform_profile_dev); |
| } |
| |
| /* |
| * Firmware Attributes |
| */ |
| |
| /* Power on lid open (device should power on when lid is opened) */ |
| |
| static int power_on_lid_open_acpi_get(struct samsung_galaxybook *galaxybook, bool *value) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_POWER_MANAGEMENT; |
| buf.gunm = GB_GUNM_POWER_MANAGEMENT; |
| buf.guds[0] = GB_GUDS_POWER_ON_LID_OPEN; |
| buf.guds[1] = GB_GUDS_POWER_ON_LID_OPEN_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| *value = buf.guds[1]; |
| |
| return 0; |
| } |
| |
| static int power_on_lid_open_acpi_set(struct samsung_galaxybook *galaxybook, const bool value) |
| { |
| struct sawb buf = {}; |
| |
| lockdep_assert_held(&galaxybook->fw_attr_lock); |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_POWER_MANAGEMENT; |
| buf.gunm = GB_GUNM_POWER_MANAGEMENT; |
| buf.guds[0] = GB_GUDS_POWER_ON_LID_OPEN; |
| buf.guds[1] = GB_GUDS_POWER_ON_LID_OPEN_SET; |
| buf.guds[2] = value ? 1 : 0; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| } |
| |
| /* USB Charging (USB ports can provide power when device is powered off) */ |
| |
| static int usb_charging_acpi_get(struct samsung_galaxybook *galaxybook, bool *value) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_USB_CHARGING_GET; |
| buf.gunm = GB_GUNM_USB_CHARGING_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| *value = buf.gunm == 1; |
| |
| return 0; |
| } |
| |
| static int usb_charging_acpi_set(struct samsung_galaxybook *galaxybook, const bool value) |
| { |
| struct sawb buf = {}; |
| |
| lockdep_assert_held(&galaxybook->fw_attr_lock); |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_USB_CHARGING_SET; |
| buf.gunm = value ? GB_GUNM_USB_CHARGING_ON : GB_GUNM_USB_CHARGING_OFF; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| } |
| |
| /* Block recording (blocks access to camera and microphone) */ |
| |
| static int block_recording_acpi_get(struct samsung_galaxybook *galaxybook, bool *value) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_BLOCK_RECORDING; |
| buf.gunm = GB_GUNM_GET; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| *value = buf.gunm == GB_BLOCK_RECORDING_ON; |
| |
| return 0; |
| } |
| |
| static int block_recording_acpi_set(struct samsung_galaxybook *galaxybook, const bool value) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| lockdep_assert_held(&galaxybook->fw_attr_lock); |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_BLOCK_RECORDING; |
| buf.gunm = GB_GUNM_SET; |
| buf.guds[0] = value ? GB_BLOCK_RECORDING_ON : GB_BLOCK_RECORDING_OFF; |
| |
| err = galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| if (err) |
| return err; |
| |
| input_report_switch(galaxybook->camera_lens_cover_switch, |
| SW_CAMERA_LENS_COVER, value ? 1 : 0); |
| input_sync(galaxybook->camera_lens_cover_switch); |
| |
| return 0; |
| } |
| |
| static int galaxybook_block_recording_init(struct samsung_galaxybook *galaxybook) |
| { |
| bool value; |
| int err; |
| |
| err = galaxybook_enable_acpi_feature(galaxybook, GB_SASB_BLOCK_RECORDING); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to initialize block_recording, error %d\n", err); |
| return GB_NOT_SUPPORTED; |
| } |
| |
| guard(mutex)(&galaxybook->fw_attr_lock); |
| |
| err = block_recording_acpi_get(galaxybook, &value); |
| if (err) { |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to get initial block_recording state, error %d\n", err); |
| return GB_NOT_SUPPORTED; |
| } |
| |
| galaxybook->camera_lens_cover_switch = |
| devm_input_allocate_device(&galaxybook->platform->dev); |
| if (!galaxybook->camera_lens_cover_switch) |
| return -ENOMEM; |
| |
| galaxybook->camera_lens_cover_switch->name = "Samsung Galaxy Book Camera Lens Cover"; |
| galaxybook->camera_lens_cover_switch->phys = DRIVER_NAME "/input0"; |
| galaxybook->camera_lens_cover_switch->id.bustype = BUS_HOST; |
| |
| input_set_capability(galaxybook->camera_lens_cover_switch, EV_SW, SW_CAMERA_LENS_COVER); |
| |
| err = input_register_device(galaxybook->camera_lens_cover_switch); |
| if (err) |
| return err; |
| |
| input_report_switch(galaxybook->camera_lens_cover_switch, |
| SW_CAMERA_LENS_COVER, value ? 1 : 0); |
| input_sync(galaxybook->camera_lens_cover_switch); |
| |
| return 0; |
| } |
| |
| /* Firmware Attributes setup */ |
| |
| static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| return sysfs_emit(buf, "enumeration\n"); |
| } |
| |
| static struct kobj_attribute fw_attr_type = __ATTR_RO(type); |
| |
| static ssize_t default_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| return sysfs_emit(buf, "0\n"); |
| } |
| |
| static struct kobj_attribute fw_attr_default_value = __ATTR_RO(default_value); |
| |
| static ssize_t possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| return sysfs_emit(buf, "0;1\n"); |
| } |
| |
| static struct kobj_attribute fw_attr_possible_values = __ATTR_RO(possible_values); |
| |
| static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr, |
| char *buf) |
| { |
| return sysfs_emit(buf, "%s\n", GB_ATTR_LANGUAGE_CODE); |
| } |
| |
| static struct kobj_attribute fw_attr_display_name_language_code = |
| __ATTR_RO(display_name_language_code); |
| |
| static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| struct galaxybook_fw_attr *fw_attr = |
| container_of(attr, struct galaxybook_fw_attr, display_name); |
| |
| return sysfs_emit(buf, "%s\n", galaxybook_fw_attr_desc[fw_attr->fw_attr_id]); |
| } |
| |
| static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) |
| { |
| struct galaxybook_fw_attr *fw_attr = |
| container_of(attr, struct galaxybook_fw_attr, current_value); |
| bool value; |
| int err; |
| |
| err = fw_attr->get_value(fw_attr->galaxybook, &value); |
| if (err) |
| return err; |
| |
| return sysfs_emit(buf, "%u\n", value); |
| } |
| |
| static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct galaxybook_fw_attr *fw_attr = |
| container_of(attr, struct galaxybook_fw_attr, current_value); |
| struct samsung_galaxybook *galaxybook = fw_attr->galaxybook; |
| bool value; |
| int err; |
| |
| if (!count) |
| return -EINVAL; |
| |
| err = kstrtobool(buf, &value); |
| if (err) |
| return err; |
| |
| guard(mutex)(&galaxybook->fw_attr_lock); |
| |
| err = fw_attr->set_value(galaxybook, value); |
| if (err) |
| return err; |
| |
| return count; |
| } |
| |
| #define NUM_FW_ATTR_ENUM_ATTRS 6 |
| |
| static int galaxybook_fw_attr_init(struct samsung_galaxybook *galaxybook, |
| const enum galaxybook_fw_attr_id fw_attr_id, |
| int (*get_value)(struct samsung_galaxybook *galaxybook, |
| bool *value), |
| int (*set_value)(struct samsung_galaxybook *galaxybook, |
| const bool value)) |
| { |
| struct galaxybook_fw_attr *fw_attr; |
| struct attribute **attrs; |
| |
| fw_attr = devm_kzalloc(&galaxybook->platform->dev, sizeof(*fw_attr), GFP_KERNEL); |
| if (!fw_attr) |
| return -ENOMEM; |
| |
| attrs = devm_kcalloc(&galaxybook->platform->dev, NUM_FW_ATTR_ENUM_ATTRS + 1, |
| sizeof(*attrs), GFP_KERNEL); |
| if (!attrs) |
| return -ENOMEM; |
| |
| attrs[0] = &fw_attr_type.attr; |
| attrs[1] = &fw_attr_default_value.attr; |
| attrs[2] = &fw_attr_possible_values.attr; |
| attrs[3] = &fw_attr_display_name_language_code.attr; |
| |
| sysfs_attr_init(&fw_attr->display_name.attr); |
| fw_attr->display_name.attr.name = "display_name"; |
| fw_attr->display_name.attr.mode = 0444; |
| fw_attr->display_name.show = display_name_show; |
| attrs[4] = &fw_attr->display_name.attr; |
| |
| sysfs_attr_init(&fw_attr->current_value.attr); |
| fw_attr->current_value.attr.name = "current_value"; |
| fw_attr->current_value.attr.mode = 0644; |
| fw_attr->current_value.show = current_value_show; |
| fw_attr->current_value.store = current_value_store; |
| attrs[5] = &fw_attr->current_value.attr; |
| |
| attrs[6] = NULL; |
| |
| fw_attr->galaxybook = galaxybook; |
| fw_attr->fw_attr_id = fw_attr_id; |
| fw_attr->attr_group.name = galaxybook_fw_attr_name[fw_attr_id]; |
| fw_attr->attr_group.attrs = attrs; |
| fw_attr->get_value = get_value; |
| fw_attr->set_value = set_value; |
| |
| return sysfs_create_group(&galaxybook->fw_attrs_kset->kobj, &fw_attr->attr_group); |
| } |
| |
| static void galaxybook_kset_unregister(void *data) |
| { |
| struct kset *kset = data; |
| |
| kset_unregister(kset); |
| } |
| |
| static void galaxybook_fw_attrs_dev_unregister(void *data) |
| { |
| struct device *fw_attrs_dev = data; |
| |
| device_unregister(fw_attrs_dev); |
| } |
| |
| static int galaxybook_fw_attrs_init(struct samsung_galaxybook *galaxybook) |
| { |
| bool value; |
| int err; |
| |
| err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->fw_attr_lock); |
| if (err) |
| return err; |
| |
| galaxybook->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), |
| NULL, "%s", DRIVER_NAME); |
| if (IS_ERR(galaxybook->fw_attrs_dev)) |
| return PTR_ERR(galaxybook->fw_attrs_dev); |
| |
| err = devm_add_action_or_reset(&galaxybook->platform->dev, |
| galaxybook_fw_attrs_dev_unregister, |
| galaxybook->fw_attrs_dev); |
| if (err) |
| return err; |
| |
| galaxybook->fw_attrs_kset = kset_create_and_add("attributes", NULL, |
| &galaxybook->fw_attrs_dev->kobj); |
| if (!galaxybook->fw_attrs_kset) |
| return -ENOMEM; |
| err = devm_add_action_or_reset(&galaxybook->platform->dev, |
| galaxybook_kset_unregister, galaxybook->fw_attrs_kset); |
| if (err) |
| return err; |
| |
| err = power_on_lid_open_acpi_get(galaxybook, &value); |
| if (!err) { |
| err = galaxybook_fw_attr_init(galaxybook, |
| GB_ATTR_POWER_ON_LID_OPEN, |
| &power_on_lid_open_acpi_get, |
| &power_on_lid_open_acpi_set); |
| if (err) |
| return err; |
| } |
| |
| err = usb_charging_acpi_get(galaxybook, &value); |
| if (!err) { |
| err = galaxybook_fw_attr_init(galaxybook, |
| GB_ATTR_USB_CHARGING, |
| &usb_charging_acpi_get, |
| &usb_charging_acpi_set); |
| if (err) |
| return err; |
| } |
| |
| err = galaxybook_block_recording_init(galaxybook); |
| if (err == GB_NOT_SUPPORTED) |
| return 0; |
| else if (err) |
| return err; |
| |
| galaxybook->has_block_recording = true; |
| |
| return galaxybook_fw_attr_init(galaxybook, |
| GB_ATTR_BLOCK_RECORDING, |
| &block_recording_acpi_get, |
| &block_recording_acpi_set); |
| } |
| |
| /* |
| * Hotkeys and notifications |
| */ |
| |
| static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work) |
| { |
| struct samsung_galaxybook *galaxybook = |
| from_work(galaxybook, work, kbd_backlight_hotkey_work); |
| int brightness; |
| int err; |
| |
| guard(mutex)(&galaxybook->kbd_backlight_lock); |
| |
| brightness = galaxybook->kbd_backlight.brightness; |
| if (brightness < galaxybook->kbd_backlight.max_brightness) |
| brightness++; |
| else |
| brightness = 0; |
| |
| err = led_set_brightness_sync(&galaxybook->kbd_backlight, brightness); |
| if (err) { |
| dev_err(&galaxybook->platform->dev, |
| "failed to set kbd_backlight brightness, error %d\n", err); |
| return; |
| } |
| |
| led_classdev_notify_brightness_hw_changed(&galaxybook->kbd_backlight, brightness); |
| } |
| |
| static void galaxybook_block_recording_hotkey_work(struct work_struct *work) |
| { |
| struct samsung_galaxybook *galaxybook = |
| from_work(galaxybook, work, block_recording_hotkey_work); |
| bool value; |
| int err; |
| |
| guard(mutex)(&galaxybook->fw_attr_lock); |
| |
| err = block_recording_acpi_get(galaxybook, &value); |
| if (err) { |
| dev_err(&galaxybook->platform->dev, |
| "failed to get block_recording, error %d\n", err); |
| return; |
| } |
| |
| err = block_recording_acpi_set(galaxybook, !value); |
| if (err) |
| dev_err(&galaxybook->platform->dev, |
| "failed to set block_recording, error %d\n", err); |
| } |
| |
| static bool galaxybook_i8042_filter(unsigned char data, unsigned char str, struct serio *port, |
| void *context) |
| { |
| struct samsung_galaxybook *galaxybook = context; |
| static bool extended; |
| |
| if (str & I8042_STR_AUXDATA) |
| return false; |
| |
| if (data == 0xe0) { |
| extended = true; |
| return true; |
| } else if (extended) { |
| extended = false; |
| switch (data) { |
| case GB_KEY_KBD_BACKLIGHT_KEYDOWN: |
| return true; |
| case GB_KEY_KBD_BACKLIGHT_KEYUP: |
| if (galaxybook->has_kbd_backlight) |
| schedule_work(&galaxybook->kbd_backlight_hotkey_work); |
| return true; |
| |
| case GB_KEY_BLOCK_RECORDING_KEYDOWN: |
| return true; |
| case GB_KEY_BLOCK_RECORDING_KEYUP: |
| if (galaxybook->has_block_recording) |
| schedule_work(&galaxybook->block_recording_hotkey_work); |
| return true; |
| |
| /* battery notification already sent to battery + SCAI device */ |
| case GB_KEY_BATTERY_NOTIFY_KEYUP: |
| case GB_KEY_BATTERY_NOTIFY_KEYDOWN: |
| return true; |
| |
| default: |
| /* |
| * Report the previously filtered e0 before continuing |
| * with the next non-filtered byte. |
| */ |
| serio_interrupt(port, 0xe0, 0); |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void galaxybook_i8042_filter_remove(void *data) |
| { |
| struct samsung_galaxybook *galaxybook = data; |
| |
| i8042_remove_filter(galaxybook_i8042_filter); |
| cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work); |
| cancel_work_sync(&galaxybook->block_recording_hotkey_work); |
| } |
| |
| static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook) |
| { |
| int err; |
| |
| if (!galaxybook->has_kbd_backlight && !galaxybook->has_block_recording) |
| return 0; |
| |
| INIT_WORK(&galaxybook->kbd_backlight_hotkey_work, |
| galaxybook_kbd_backlight_hotkey_work); |
| INIT_WORK(&galaxybook->block_recording_hotkey_work, |
| galaxybook_block_recording_hotkey_work); |
| |
| err = i8042_install_filter(galaxybook_i8042_filter, galaxybook); |
| if (err) |
| return err; |
| |
| return devm_add_action_or_reset(&galaxybook->platform->dev, |
| galaxybook_i8042_filter_remove, galaxybook); |
| } |
| |
| /* |
| * ACPI device setup |
| */ |
| |
| static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data) |
| { |
| struct samsung_galaxybook *galaxybook = data; |
| |
| switch (event) { |
| case GB_ACPI_NOTIFY_BATTERY_STATE_CHANGED: |
| case GB_ACPI_NOTIFY_DEVICE_ON_TABLE: |
| case GB_ACPI_NOTIFY_DEVICE_OFF_TABLE: |
| break; |
| case GB_ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE: |
| if (galaxybook->has_performance_mode) |
| platform_profile_cycle(); |
| break; |
| default: |
| dev_warn(&galaxybook->platform->dev, |
| "unknown ACPI notification event: 0x%x\n", event); |
| } |
| |
| acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(&galaxybook->platform->dev), |
| event, 1); |
| } |
| |
| static int galaxybook_enable_acpi_notify(struct samsung_galaxybook *galaxybook) |
| { |
| struct sawb buf = {}; |
| int err; |
| |
| err = galaxybook_enable_acpi_feature(galaxybook, GB_SASB_NOTIFICATIONS); |
| if (err) |
| return err; |
| |
| buf.safn = GB_SAFN; |
| buf.sasb = GB_SASB_NOTIFICATIONS; |
| buf.gunm = GB_GUNM_ACPI_NOTIFY_ENABLE; |
| buf.guds[0] = GB_GUDS_ACPI_NOTIFY_ENABLE; |
| |
| return galaxybook_acpi_method(galaxybook, GB_ACPI_METHOD_SETTINGS, |
| &buf, GB_SAWB_LEN_SETTINGS); |
| } |
| |
| static void galaxybook_acpi_remove_notify_handler(void *data) |
| { |
| struct samsung_galaxybook *galaxybook = data; |
| |
| acpi_remove_notify_handler(galaxybook->acpi->handle, ACPI_ALL_NOTIFY, |
| galaxybook_acpi_notify); |
| } |
| |
| static void galaxybook_acpi_disable(void *data) |
| { |
| struct samsung_galaxybook *galaxybook = data; |
| |
| acpi_execute_simple_method(galaxybook->acpi->handle, |
| GB_ACPI_METHOD_ENABLE, GB_ACPI_METHOD_ENABLE_OFF); |
| } |
| |
| static int galaxybook_acpi_init(struct samsung_galaxybook *galaxybook) |
| { |
| acpi_status status; |
| int err; |
| |
| status = acpi_execute_simple_method(galaxybook->acpi->handle, GB_ACPI_METHOD_ENABLE, |
| GB_ACPI_METHOD_ENABLE_ON); |
| if (ACPI_FAILURE(status)) |
| return -EIO; |
| err = devm_add_action_or_reset(&galaxybook->platform->dev, |
| galaxybook_acpi_disable, galaxybook); |
| if (err) |
| return err; |
| |
| status = acpi_install_notify_handler(galaxybook->acpi->handle, ACPI_ALL_NOTIFY, |
| galaxybook_acpi_notify, galaxybook); |
| if (ACPI_FAILURE(status)) |
| return -EIO; |
| err = devm_add_action_or_reset(&galaxybook->platform->dev, |
| galaxybook_acpi_remove_notify_handler, galaxybook); |
| if (err) |
| return err; |
| |
| err = galaxybook_enable_acpi_notify(galaxybook); |
| if (err) |
| dev_dbg(&galaxybook->platform->dev, "failed to enable ACPI notifications; " |
| "some hotkeys will not be supported\n"); |
| |
| err = galaxybook_enable_acpi_feature(galaxybook, GB_SASB_POWER_MANAGEMENT); |
| if (err) |
| dev_dbg(&galaxybook->platform->dev, |
| "failed to initialize ACPI power management features; " |
| "many features of this driver will not be available\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * Platform driver |
| */ |
| |
| static int galaxybook_probe(struct platform_device *pdev) |
| { |
| struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
| struct samsung_galaxybook *galaxybook; |
| int err; |
| |
| if (!adev) |
| return -ENODEV; |
| |
| galaxybook = devm_kzalloc(&pdev->dev, sizeof(*galaxybook), GFP_KERNEL); |
| if (!galaxybook) |
| return -ENOMEM; |
| |
| galaxybook->platform = pdev; |
| galaxybook->acpi = adev; |
| |
| /* |
| * Features must be enabled and initialized in the following order to |
| * avoid failures seen on certain devices: |
| * - GB_SASB_POWER_MANAGEMENT (including performance mode) |
| * - GB_SASB_KBD_BACKLIGHT |
| * - GB_SASB_BLOCK_RECORDING (as part of fw_attrs init) |
| */ |
| |
| err = galaxybook_acpi_init(galaxybook); |
| if (err) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize ACPI device\n"); |
| |
| err = galaxybook_platform_profile_init(galaxybook); |
| if (!err) |
| galaxybook->has_performance_mode = true; |
| else if (err != GB_NOT_SUPPORTED) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize platform profile\n"); |
| |
| err = galaxybook_battery_threshold_init(galaxybook); |
| if (err) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize battery threshold\n"); |
| |
| err = galaxybook_kbd_backlight_init(galaxybook); |
| if (!err) |
| galaxybook->has_kbd_backlight = true; |
| else if (err != GB_NOT_SUPPORTED) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize kbd_backlight\n"); |
| |
| err = galaxybook_fw_attrs_init(galaxybook); |
| if (err) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize firmware-attributes\n"); |
| |
| err = galaxybook_i8042_filter_install(galaxybook); |
| if (err) |
| return dev_err_probe(&galaxybook->platform->dev, err, |
| "failed to initialize i8042_filter\n"); |
| |
| return 0; |
| } |
| |
| static const struct acpi_device_id galaxybook_device_ids[] = { |
| { "SAM0427" }, |
| { "SAM0428" }, |
| { "SAM0429" }, |
| { "SAM0430" }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); |
| |
| static struct platform_driver galaxybook_platform_driver = { |
| .driver = { |
| .name = DRIVER_NAME, |
| .acpi_match_table = galaxybook_device_ids, |
| }, |
| .probe = galaxybook_probe, |
| }; |
| module_platform_driver(galaxybook_platform_driver); |
| |
| MODULE_AUTHOR("Joshua Grisham <josh@joshuagrisham.com>"); |
| MODULE_DESCRIPTION("Samsung Galaxy Book driver"); |
| MODULE_LICENSE("GPL"); |