On Sun, 12 Jan 2025, Joshua Grisham wrote: > Add a new driver for Samsung Galaxy Book series notebook devices with the > following features: > > - Keyboard backlight control > - Battery extension with charge control end threshold > - Controller for Samsung's performance modes using the platform profile > interface > - Adds firmware-attributes to control various system features > - Handles various hotkeys and notifications > > Signed-off-by: Joshua Grisham <josh@xxxxxxxxxxxxxxxxx> > > --- > > v1->v2: > - Attempt to resolve all review comments from v1 as written here: > https://lore.kernel.org/platform-driver-x86/53c5075b-1967-45d0-937f-463912dd966d@xxxxxx/T/#mbcbd8d5d9bc4496bac5486636c7d3b32bc3e5cd0 > > v2->v3: > - Tweak to battery attribute to closer match pattern in dell-wmi-ddv > - implement platform_profile_remove() change from > 9b3bb37b44a317626464e79da8b39989b421963f > - Small tweak to Documentation page > > v3->v4: > - Remove custom tracepoint (can trace via existing mechanisms) > - Remove module parameters > - Move sysfs attributes from device to firmware-attributes > - Refactor "allow_recording" to "camera_lens_cover" plus other small > renames in aim to have more standardized naming that are cross-vendor > - Attempt to improve locking mechanisms > - Tweak logic for setting and getting led brightness > - More fixes for aiming to use devres/devm pattern > - Change battery charge end threshold to use 1 to 100 instead of 0 to 99 > - Add swtich input event for camera_lens_cover remove all others (they will > be generated as ACPI netlink events instead) > - Various other small tweaks and features as requested from feedback > > v4-v5: > - Prefix all locally defined symbols with "GB_" as a namespace > - Remove extra unused out_buf from galaxybook_acpi_method > - Tighten up logic flow for setting and unsetting global pointer (now it > is done directly in association with the i8042 filter init and exit) > - Rename "camera_lens_cover" to "block_recording" > - Change input device to only apply for "Camera Lens Cover", remove sparse > keymap and set capabilities manually as part of block_recording init, > then notify using input_report_switch when setting block_recording > - Correct firmware-attributes enumeration implementation (adding all > attributes) and remove erroneous ABI fw attrs docs update > - Few small tweaks to how locks are used > - Use device_unregister instead of device_destroy for firmware attributes > device > - Tighten up and clean up performance mode to profile mapping logic; now > the mapping is largely "fixed" apart from "Ultra" that will map to > performance while also re-mapping "Performance" to balanced-performance > - Tighten up error handling so probe will fail in more cases where it > should fail > - Replace platform_profile_register with devm_platform_profile_register > > v5->v6: > - A few small clean-up/fixes as requested from feedback > - Revamp performance mode profile mapping so it is more static and relies > more on the facilities already built in to platform_profile, including: > - setting and using bits on profile handler choices to drive most of the > behavior instead of having totally separate logic > - get_performance_mode_profile() is now a mostly static mapping that > drives both setting initial profile choices and retrieving the right > profile for a given performance_mode during runtime > - during init most of the mappings are hard-coded and the only things > that are changed are the exception cases (override legacy values and > downgrade of performance when Ultra mode is present) > - new function galaxybook_performance_mode_init() now handles initial > startup performance mode in a much more simple and straight-forward way > --- > Documentation/admin-guide/laptops/index.rst | 1 + > .../laptops/samsung-galaxybook.rst | 170 ++ > MAINTAINERS | 7 + > drivers/platform/x86/Kconfig | 17 + > drivers/platform/x86/Makefile | 5 +- > drivers/platform/x86/samsung-galaxybook.c | 1485 +++++++++++++++++ > 6 files changed, 1683 insertions(+), 2 deletions(-) > create mode 100644 Documentation/admin-guide/laptops/samsung-galaxybook.rst > create mode 100644 drivers/platform/x86/samsung-galaxybook.c > > diff --git a/Documentation/admin-guide/laptops/index.rst b/Documentation/admin-guide/laptops/index.rst > index cd9a1c2695fd..e71c8984c23e 100644 > --- a/Documentation/admin-guide/laptops/index.rst > +++ b/Documentation/admin-guide/laptops/index.rst > @@ -11,6 +11,7 @@ Laptop Drivers > disk-shock-protection > laptop-mode > lg-laptop > + samsung-galaxybook > sony-laptop > sonypi > thinkpad-acpi > diff --git a/Documentation/admin-guide/laptops/samsung-galaxybook.rst b/Documentation/admin-guide/laptops/samsung-galaxybook.rst > new file mode 100644 > index 000000000000..f6af0c84de2c > --- /dev/null > +++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst > @@ -0,0 +1,170 @@ > +.. SPDX-License-Identifier: GPL-2.0-or-later > + > +=================== > +Samsung Galaxy Book > +=================== > + > +Joshua Grisham <josh@xxxxxxxxxxxxxxxxx> > + > +This is a Linux x86 platform driver for Samsung Galaxy Book series notebook > +devices which utilizes Samsung's ``SCAI`` ACPI device in order to control > +extra features and receive various notifications. > + > +Supported devices > +================= > + > +Any device with one of the supported ACPI device IDs should be supported. This > +covers most of the "Samsung Galaxy Book" series notebooks that are currently > +available as of this writing, and could include other Samsung notebook devices > +as well. > + > +Status > +====== > + > +The following features are currently supported: > + > +- :ref:`Keyboard backlight <keyboard-backlight>` control > +- :ref:`Performance mode <performance-mode>` control implemented using the > + platform profile interface > +- :ref:`Battery charge control end threshold > + <battery-charge-control-end-threshold>` (stop charging battery at given > + percentage value) implemented as a battery hook > +- :ref:`Firmware Attributes <firmware-attributes>` to allow control of various > + device settings > +- :ref:`Handling of Fn hotkeys <keyboard-hotkey-actions>` for various actions > +- :ref:`Handling of ACPI notifications and hotkeys > + <acpi-notifications-and-hotkey-actions>` > + > +Because different models of these devices can vary in their features, there is > +logic built within the driver which attempts to test each implemented feature > +for a valid response before enabling its support (registering additional devices > +or extensions, adding sysfs attributes, etc). Therefore, it can be important to > +note that not all features may be supported for your particular device. > + > +The following features might be possible to implement but will require > +additional investigation and are therefore not supported at this time: > + > +- "Dolby Atmos" mode for the speakers > +- "Outdoor Mode" for increasing screen brightness on models with ``SAM0427`` > +- "Silent Mode" on models with ``SAM0427`` > + > +.. _keyboard-backlight: > + > +Keyboard backlight > +================== > + > +A new LED class named ``samsung-galaxybook::kbd_backlight`` is created which > +will then expose the device using the standard sysfs-based LED interface at > +``/sys/class/leds/samsung-galaxybook::kbd_backlight``. Brightness can be > +controlled by writing the desired value to the ``brightness`` sysfs attribute or > +with any other desired userspace utility. > + > +.. note:: > + Most of these devices have an ambient light sensor which also turns > + off the keyboard backlight under well-lit conditions. This behavior does not > + seem possible to control at this time, but can be good to be aware of. > + > +.. _performance-mode: > + > +Performance mode > +================ > + > +This driver implements the > +Documentation/userspace-api/sysfs-platform_profile.rst interface for working > +with the "performance mode" function of the Samsung ACPI device. > + > +Mapping of each Samsung "performance mode" to its respective platform profile is > +performed dynamically by the driver, as not all models support all of the same > +performance modes. Your device might have one or more of the following mappings: > + > +- "Silent" maps to ``low-power`` > +- "Quiet" maps to ``quiet`` > +- "Optimized" maps to ``balanced`` > +- "Performance" maps to ``performance`` > +- For devices which support "Ultra", "Ultra" will map to ``performance`` and > + "Performance" will be re-mapped to ``balanced-performance``. > + > +The result of the mapping can be printed in the kernel log when the module is > +loaded. Supported profiles can also be retrieved from > +``/sys/firmware/acpi/platform_profile_choices``, while > +``/sys/firmware/acpi/platform_profile`` can be used to read or write the > +currently selected profile. > + > +The ``balanced`` platform profile will be set during module load if no profile > +has been previously set. > + > +.. _battery-charge-control-end-threshold: > + > +Battery charge control end threshold > +==================================== > + > +This platform driver will add the ability to set the battery's charge control > +end threshold, but does not have the ability to set a start threshold. > + > +This feature is typically called "Battery Saver" by the various Samsung > +applications in Windows, but in Linux we have implemented the standardized > +"charge control threshold" sysfs interface on the battery device to allow for > +controlling this functionality from the userspace. > + > +The sysfs attribute > +``/sys/class/power_supply/BAT1/charge_control_end_threshold`` can be used to > +read or set the desired charge end threshold. > + > +If you wish to maintain interoperability with Windows, then you should set the > +value to 80 to represent "on", or 100 to represent "off", as these are the > +values currently recognized by the various Windows-based Samsung applications > +and services as "on" or "off". Otherwise, the device will accept any value > +between 1 and 100 as the percentage that you wish the battery to stop charging > +at. > + > +.. _firmware-attributes: > + > +Firmware Attributes > +=================== > + > +The following enumeration-typed firmware attributes are set up by this driver > +and should be accessible under > +``/sys/class/firmware-attributes/samsung-galaxybook/attributes/`` if your device > +supports them: > + > +- ``power_on_lid_open`` (device should power on when the lid is opened) > +- ``usb_charging`` (USB ports can deliver power to connected devices even when > + the device is powered off or in a low sleep state) > +- ``block_recording`` (blocks access to camera and microphone) > + > +All of these attributes are simple boolean-like enumeration values which use 0 > +to represent "off" and 1 to represent "on". Use the ``current_value`` attribute > +to get or change the setting on the device. > + > +Note that when ``block_recording`` is updated, the input device "Samsung Galaxy > +Book Lens Cover" will receive a ``SW_CAMERA_LENS_COVER`` switch event which > +reflects the current state. > + > +.. _keyboard-hotkey-actions: > + > +Keyboard hotkey actions (i8042 filter) > +====================================== > + > +The i8042 filter will swallow the keyboard events for the Fn+F9 hotkey (Multi- > +level keyboard backlight toggle) and Fn+F10 hotkey (Block recording toggle) > +and instead execute their actions within the driver itself. > + > +Fn+F9 will cycle through the brightness levels of the keyboard backlight. A > +notification will be sent using ``led_classdev_notify_brightness_hw_changed`` > +so that the userspace can be aware of the change. This mimics the behavior of > +other existing devices where the brightness level is cycled internally by the > +embedded controller and then reported via a notification. > + > +Fn+F10 will toggle the value of the "block recording" setting, which blocks > +or allows usage of the built-in camera and microphone. > + > +.. _acpi-notifications-and-hotkey-actions: > + > +ACPI notifications and hotkey actions > +===================================== > + > +ACPI notifications will generate ACPI netlink events and can be received using > +userspace tools such as ``acpi_listen`` and ``acpid``. > + > +The Fn+F11 Performance mode hotkey will be handled by the driver; each keypress > +will cycle to the next available platform profile. > diff --git a/MAINTAINERS b/MAINTAINERS > index 3809931b9240..6448e931728c 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -20733,6 +20733,13 @@ L: linux-fbdev@xxxxxxxxxxxxxxx > S: Maintained > F: drivers/video/fbdev/s3c-fb.c > > +SAMSUNG GALAXY BOOK DRIVER > +M: Joshua Grisham <josh@xxxxxxxxxxxxxxxxx> > +L: platform-driver-x86@xxxxxxxxxxxxxxx > +S: Maintained > +F: Documentation/admin-guide/laptops/samsung-galaxybook.rst > +F: drivers/platform/x86/samsung-galaxybook.c > + > SAMSUNG INTERCONNECT DRIVERS > M: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> > M: Artur Świgoń <a.swigon@xxxxxxxxxxx> > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 0258dd879d64..c77178e2640b 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -778,6 +778,23 @@ config BARCO_P50_GPIO > To compile this driver as a module, choose M here: the module > will be called barco-p50-gpio. > > +config SAMSUNG_GALAXYBOOK > + tristate "Samsung Galaxy Book driver" > + depends on ACPI > + depends on ACPI_BATTERY > + depends on INPUT > + depends on LEDS_CLASS > + depends on SERIO_I8042 > + select ACPI_PLATFORM_PROFILE > + select FW_ATTR_CLASS > + help > + This is a driver for Samsung Galaxy Book series notebooks. It adds > + support for the keyboard backlight control, performance mode control, > + function keys, and various firmware attributes. > + > + For more information about this driver, see > + <file:Documentation/admin-guide/laptops/samsung-galaxybook.rst>. > + > config SAMSUNG_LAPTOP > tristate "Samsung Laptop driver" > depends on RFKILL || RFKILL = n > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index e1b142947067..32ec4cb9d902 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -95,8 +95,9 @@ obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o > obj-$(CONFIG_BARCO_P50_GPIO) += barco-p50-gpio.o > > # Samsung > -obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o > -obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o > +obj-$(CONFIG_SAMSUNG_GALAXYBOOK) += samsung-galaxybook.o > +obj-$(CONFIG_SAMSUNG_LAPTOP) += samsung-laptop.o > +obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o > > # Toshiba > obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o > diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x86/samsung-galaxybook.c > new file mode 100644 > index 000000000000..bbdf4b9282ac > --- /dev/null > +++ b/drivers/platform/x86/samsung-galaxybook.c > @@ -0,0 +1,1485 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Samsung Galaxy Book driver > + * > + * Copyright (c) 2025 Joshua Grisham <josh@xxxxxxxxxxxxxxxxx> > + * > + * With contributions to the SCAI ACPI device interface: > + * Copyright (c) 2024 Giulio Girardi <giulio.girardi@xxxxxxxxxxxxxxx> > + * > + * Implementation inspired by existing x86 platform drivers. > + * Thank you to the authors! > + */ > + > +#include <linux/acpi.h> > +#include <linux/bitops.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; > + > + const struct class *fw_attrs_class; > + struct device *fw_attrs_dev; > + struct kset *fw_attrs_kset; > + /* block out of sync condition if firmware attributes are updated in multiple threads */ Please use multiple line comment as this exceeds 80 chars. > + 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 out of sync condition in hotkey action if brightness updated in another thread */ Ditto. > + 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; > + struct device_attribute charge_control_end_threshold_attr; > + > + u8 profile_performance_modes[PLATFORM_PROFILE_LAST + 1]; > + struct platform_profile_handler profile_handler; > +}; > + > +static struct samsung_galaxybook *galaxybook_ptr; > + > +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", Please align values in both arrays. > +}; > + > +#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 iob_values[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_ULTRA 0x16 > +#define GB_PERFORMANCE_MODE_PERFORMANCE 0x15 > +#define GB_PERFORMANCE_MODE_SILENT 0xb > +#define GB_PERFORMANCE_MODE_QUIET 0xa > +#define GB_PERFORMANCE_MODE_OPTIMIZED 0x2 > +#define GB_PERFORMANCE_MODE_PERFORMANCE_LEGACY 0x1 > +#define GB_PERFORMANCE_MODE_OPTIMIZED_LEGACY 0x0 > +#define GB_PERFORMANCE_MODE_IGNORE1 0x14 > +#define GB_PERFORMANCE_MODE_IGNORE2 0xc > +#define GB_PERFORMANCE_MODE_INVALID 0xff > + > +#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 > + > +/* > + * 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 method %s; " > + "response length mismatch\n", method); Please put the entire error message on the same line (to make sure grep will capture it). > + 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 method %s; " > + "device did not respond with success code 0x%x\n", Ditto. > + 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 method %s; device responded with failure code 0x%x\n", Ditto. > + 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_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 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_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 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 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 0; > + } > + > + 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 0; > + } > + > + 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; > + > + err = devm_led_classdev_register_ext(&galaxybook->platform->dev, > + &galaxybook->kbd_backlight, &init_data); > + if (err) > + return err; > + > + galaxybook->has_kbd_backlight = true; > + > + return 0; > +} > + > +/* > + * Battery Extension (adds charge_control_end_threshold to the battery device) > + */ > + > +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 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 ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct samsung_galaxybook *galaxybook = > + container_of(attr, struct samsung_galaxybook, charge_control_end_threshold_attr); > + u8 value; > + int err; > + > + if (!count) > + return -EINVAL; > + > + err = kstrtou8(buf, 0, &value); > + if (err) > + return err; > + > + 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; > + > + err = charge_control_end_threshold_acpi_set(galaxybook, value); > + if (err) > + return err; > + > + return count; > +} > + > +static ssize_t charge_control_end_threshold_show(struct device *dev, struct device_attribute *attr, > + char *buf) > +{ > + struct samsung_galaxybook *galaxybook = > + container_of(attr, struct samsung_galaxybook, charge_control_end_threshold_attr); > + u8 value; > + int err; > + > + err = charge_control_end_threshold_acpi_get(galaxybook, &value); > + if (err) > + return err; > + > + /* device stores "no end threshold" as 0 instead of 100; if device has 0, report 100 */ > + if (value == 0) > + value = 100; > + > + return sysfs_emit(buf, "%u\n", value); > +} > + > +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 device_create_file(&battery->dev, &galaxybook->charge_control_end_threshold_attr); > +} > + > +static int galaxybook_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) > +{ > + struct samsung_galaxybook *galaxybook = > + container_of(hook, struct samsung_galaxybook, battery_hook); > + > + device_remove_file(&battery->dev, &galaxybook->charge_control_end_threshold_attr); > + return 0; > +} > + > +static int galaxybook_battery_threshold_init(struct samsung_galaxybook *galaxybook) > +{ > + struct acpi_battery_hook *hook; > + struct device_attribute *attr; > + 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; > + } > + > + hook = &galaxybook->battery_hook; > + hook->add_battery = galaxybook_battery_add; > + hook->remove_battery = galaxybook_battery_remove; > + hook->name = "Samsung Galaxy Book Battery Extension"; > + > + attr = &galaxybook->charge_control_end_threshold_attr; > + sysfs_attr_init(&attr->attr); > + attr->attr.name = "charge_control_end_threshold"; > + attr->attr.mode = 0644; > + attr->show = charge_control_end_threshold_show; > + attr->store = charge_control_end_threshold_store; > + > + return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook); > +} > + > +/* > + * Platform Profile / Performance mode > + */ > + > +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 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 get_performance_mode_profile(struct samsung_galaxybook *galaxybook, > + const u8 performance_mode, > + enum platform_profile_option *profile) > +{ > + switch (performance_mode) { > + case GB_PERFORMANCE_MODE_SILENT: > + *profile = PLATFORM_PROFILE_LOW_POWER; > + break; > + case GB_PERFORMANCE_MODE_QUIET: > + *profile = PLATFORM_PROFILE_QUIET; > + break; > + case GB_PERFORMANCE_MODE_OPTIMIZED: > + case GB_PERFORMANCE_MODE_OPTIMIZED_LEGACY: > + *profile = PLATFORM_PROFILE_BALANCED; > + break; > + case GB_PERFORMANCE_MODE_PERFORMANCE: > + case GB_PERFORMANCE_MODE_PERFORMANCE_LEGACY: > + /* balanced-performance profile is used for Performance when Ultra is present */ > + if (test_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, > + galaxybook->profile_handler.choices)) > + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; > + else > + *profile = PLATFORM_PROFILE_PERFORMANCE; > + break; > + 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_set(struct platform_profile_handler *pprof, > + enum platform_profile_option profile) > +{ > + struct samsung_galaxybook *galaxybook = > + container_of(pprof, struct samsung_galaxybook, profile_handler); > + > + return performance_mode_acpi_set(galaxybook, > + galaxybook->profile_performance_modes[profile]); > +} > + > +static int galaxybook_platform_profile_get(struct platform_profile_handler *pprof, > + enum platform_profile_option *profile) > +{ > + struct samsung_galaxybook *galaxybook = > + container_of(pprof, struct samsung_galaxybook, profile_handler); > + 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_performance_mode_init(struct samsung_galaxybook *galaxybook) > +{ > + enum platform_profile_option profile = PLATFORM_PROFILE_LAST; > + u8 performance_mode; > + int err; > + int i; > + > + err = performance_mode_acpi_get(galaxybook, &performance_mode); > + if (err) > + return err; > + > + err = get_performance_mode_profile(galaxybook, performance_mode, &profile); > + if (err) > + dev_warn(&galaxybook->platform->dev, > + "initial startup performance mode 0x%x is not mapped\n", > + performance_mode); > + > + for_each_set_bit(i, galaxybook->profile_handler.choices, PLATFORM_PROFILE_LAST) > + dev_dbg(&galaxybook->platform->dev, > + "enabled platform profile %d using performance mode 0x%x\n", > + i, galaxybook->profile_performance_modes[i]); > + > + /* ensure that performance_mode at startup matches what should be mapped for its profile */ > + if (galaxybook->profile_performance_modes[profile] == performance_mode) > + return 0; > + > + /* otherwise, if balanced is enabled, use it as the default startup profile */ > + if (test_bit(PLATFORM_PROFILE_BALANCED, galaxybook->profile_handler.choices)) > + return galaxybook_platform_profile_set(&galaxybook->profile_handler, > + PLATFORM_PROFILE_BALANCED); > + > + /* otherwise, find the first enabled profile and use that instead */ > + profile = find_next_bit_wrap(galaxybook->profile_handler.choices, > + PLATFORM_PROFILE_LAST, > + 0); > + > + if (profile == PLATFORM_PROFILE_LAST) { > + dev_err(&galaxybook->platform->dev, > + "no platform profiles have been enabled\n"); > + return -EOPNOTSUPP; > + } > + > + return galaxybook_platform_profile_set(&galaxybook->profile_handler, profile); > +} > + > +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile] > + > +static int galaxybook_profile_init(struct samsung_galaxybook *galaxybook) > +{ > + enum platform_profile_option profile; > + struct sawb buf = {}; > + unsigned int i; > + int err; > + > + /* fetch supported performance mode values from ACPI method */ > + 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 0; > + } > + > + galaxybook->profile_handler.name = DRIVER_NAME; > + galaxybook->profile_handler.dev = &galaxybook->platform->dev; > + galaxybook->profile_handler.profile_get = galaxybook_platform_profile_get; > + galaxybook->profile_handler.profile_set = galaxybook_platform_profile_set; > + > + /* set initial default profile performance mode mappings */ > + gb_pfmode(PLATFORM_PROFILE_LOW_POWER) = GB_PERFORMANCE_MODE_SILENT; > + gb_pfmode(PLATFORM_PROFILE_QUIET) = GB_PERFORMANCE_MODE_QUIET; > + gb_pfmode(PLATFORM_PROFILE_BALANCED) = GB_PERFORMANCE_MODE_OPTIMIZED_LEGACY; > + gb_pfmode(PLATFORM_PROFILE_PERFORMANCE) = GB_PERFORMANCE_MODE_PERFORMANCE_LEGACY; > + gb_pfmode(PLATFORM_PROFILE_LAST) = GB_PERFORMANCE_MODE_INVALID; > + /* > + * 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++) { > + dev_dbg(&galaxybook->platform->dev, > + "device supports performance mode 0x%x\n", buf.iob_values[i]); > + err = get_performance_mode_profile(galaxybook, buf.iob_values[i], &profile); > + if (err) > + continue; > + switch (buf.iob_values[i]) { > + case GB_PERFORMANCE_MODE_OPTIMIZED: > + /* override legacy Optimized value */ > + gb_pfmode(profile) = GB_PERFORMANCE_MODE_OPTIMIZED; > + break; > + case GB_PERFORMANCE_MODE_PERFORMANCE: > + /* override legacy Performance value */ > + gb_pfmode(profile) = GB_PERFORMANCE_MODE_PERFORMANCE; > + break; > + case GB_PERFORMANCE_MODE_ULTRA: > + /* if Ultra is supported, downgrade performance to balanced-performance */ > + if (test_bit(PLATFORM_PROFILE_PERFORMANCE, > + galaxybook->profile_handler.choices)) { > + gb_pfmode(PLATFORM_PROFILE_BALANCED_PERFORMANCE) = > + gb_pfmode(PLATFORM_PROFILE_PERFORMANCE); > + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, > + galaxybook->profile_handler.choices); > + } > + /* override performance profile to use Ultra's value */ > + gb_pfmode(profile) = GB_PERFORMANCE_MODE_ULTRA; > + break; > + default: > + break; > + } > + set_bit(profile, galaxybook->profile_handler.choices); > + } > + > + err = galaxybook_performance_mode_init(galaxybook); > + if (err) { > + dev_dbg(&galaxybook->platform->dev, > + "failed to initialize performance mode, error %d\n", err); > + return 0; > + } > + > + err = devm_platform_profile_register(&galaxybook->profile_handler); > + if (err) > + return err; As you might already know, I've in the meantime merged the Kurt's big platform_profile series so these need to the be rebased on top of that. -- i. > + galaxybook->has_performance_mode = true; > + > + return 0; > +} > + > +/* > + * Firmware Attributes > + */ > + > +/* Power on lid open (device should power on when lid is opened) */ > + > +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); > +} > + > +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; > +} > + > +/* USB Charging (USB ports can charge other devices even when device is powered off) */ > + > +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); > +} > + > +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; > +} > + > +/* Block recording (blocks access to camera and microphone) */ > + > +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 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 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 0; > + } > + > + 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 0; > + } > + > + 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); > + > + galaxybook->has_block_recording = true; > + > + 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_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; > +} > + > +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 void galaxybook_fw_attr_remove(void *data) > +{ > + struct galaxybook_fw_attr *fw_attr = data; > + struct samsung_galaxybook *galaxybook = fw_attr->galaxybook; > + > + sysfs_remove_group(&galaxybook->fw_attrs_kset->kobj, &fw_attr->attr_group); > +} > + > +#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; > + int err; > + > + 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); > + 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); > + 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; > + > + err = sysfs_create_group(&galaxybook->fw_attrs_kset->kobj, &fw_attr->attr_group); > + if (err) > + return err; > + > + return devm_add_action_or_reset(&galaxybook->platform->dev, > + galaxybook_fw_attr_remove, fw_attr); > +} > + > +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); > + fw_attributes_class_put(); > +} > + > +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; > + > + err = fw_attributes_class_get(&galaxybook->fw_attrs_class); > + if (err) > + return err; > + > + galaxybook->fw_attrs_dev = device_create(galaxybook->fw_attrs_class, NULL, MKDEV(0, 0), > + NULL, "%s", DRIVER_NAME); > + if (IS_ERR(galaxybook->fw_attrs_dev)) { > + fw_attributes_class_put(); > + err = PTR_ERR(galaxybook->fw_attrs_dev); > + return err; > + } > + 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; > + } > + > + /* block_recording requires an additional init before it can be used */ > + err = galaxybook_block_recording_init(galaxybook); > + if (err) > + return err; > + if (!galaxybook->has_block_recording) > + return 0; > + > + err = block_recording_acpi_get(galaxybook, &value); > + if (err) { > + galaxybook->has_block_recording = false; > + return 0; > + } > + > + 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) > +{ > + 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_ptr->has_kbd_backlight) > + schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work); > + return true; > + > + case GB_KEY_BLOCK_RECORDING_KEYDOWN: > + return true; > + case GB_KEY_BLOCK_RECORDING_KEYUP: > + if (galaxybook_ptr->has_block_recording) > + schedule_work(&galaxybook_ptr->block_recording_hotkey_work); > + return true; > + > + /* battery notification already sent to battery and ACPI device; ignore */ > + 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); > + > + if (galaxybook_ptr) > + galaxybook_ptr = NULL; > +} > + > +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook) > +{ > + int err; > + > + /* global pointer is currently needed for i8042 filter */ > + if (galaxybook_ptr) > + return -EBUSY; > + galaxybook_ptr = galaxybook; > + > + 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); > + 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; > + galaxybook->has_kbd_backlight = false; > + galaxybook->has_block_recording = false; > + galaxybook->has_performance_mode = false; > + > + err = galaxybook_acpi_init(galaxybook); > + if (err) > + return dev_err_probe(&galaxybook->platform->dev, err, > + "failed to initialize ACPI device\n"); > + > + err = galaxybook_profile_init(galaxybook); > + if (err) > + 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) > + 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@xxxxxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Samsung Galaxy Book driver"); > +MODULE_LICENSE("GPL");