diff options
Diffstat (limited to 'drivers/platform/x86/samsung-galaxybook.c')
-rw-r--r-- | drivers/platform/x86/samsung-galaxybook.c | 1425 |
1 files changed, 1425 insertions, 0 deletions
diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c new file mode 100644 index 000000000000..5878a351993e --- /dev/null +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -0,0 +1,1425 @@ +// 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"); |