diff mbox series

[v8] platform/x86: samsung-galaxybook: Add samsung-galaxybook driver

Message ID 20250118202632.8352-1-josh@joshuagrisham.com (mailing list archive)
State Changes Requested, archived
Headers show
Series [v8] platform/x86: samsung-galaxybook: Add samsung-galaxybook driver | expand

Commit Message

Joshua Grisham Jan. 18, 2025, 8:26 p.m. UTC
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@joshuagrisham.com>

---

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@gmx.de/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

v6->v7:
- Rebase to latest for-next and implement updates to firmware-attributes,
  platform_profile, and i8042 filter (including removal of global pointer).

v7->v8:
- Simplification and logic correction to platform_profile mapping and
  probe implementation, including renaming the internal performance modes
  to more closely match the internal names used by Samsung in Windows and
  align the logic in this driver to how it works with their Windows
  services.
---
 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     | 1430 +++++++++++++++++
 6 files changed, 1628 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/admin-guide/laptops/samsung-galaxybook.rst
 create mode 100644 drivers/platform/x86/samsung-galaxybook.c

Comments

Thomas Weißschuh Jan. 23, 2025, 11:42 p.m. UTC | #1
Hi Joshua,

looks good to me.
I have some nitpicks inline, but even for the current state:

Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>

On 2025-01-18 21:26:30+0100, 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@joshuagrisham.com>
> 
> ---
> 
> 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@gmx.de/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
> 
> v6->v7:
> - Rebase to latest for-next and implement updates to firmware-attributes,
>   platform_profile, and i8042 filter (including removal of global pointer).
> 
> v7->v8:
> - Simplification and logic correction to platform_profile mapping and
>   probe implementation, including renaming the internal performance modes
>   to more closely match the internal names used by Samsung in Windows and
>   align the logic in this driver to how it works with their Windows
>   services.
> ---
>  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     | 1430 +++++++++++++++++
>  6 files changed, 1628 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@joshuagrisham.com>
> +
> +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@vger.kernel.org
>  S:	Maintained
>  F:	drivers/video/fbdev/s3c-fb.c
>  
> +SAMSUNG GALAXY BOOK DRIVER
> +M:	Joshua Grisham <josh@joshuagrisham.com>
> +L:	platform-driver-x86@vger.kernel.org
> +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@samsung.com>
>  M:	Artur Świgoń <a.swigon@samsung.com>
> 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..c70024691656
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-galaxybook.c
> @@ -0,0 +1,1430 @@
> +// 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;
> +	struct device_attribute charge_control_end_threshold_attr;
> +
> +	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 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_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
> +
> +/*
> + * 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_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);
> +}

For the next revision you should be able to use the power supply
extension framework.

> +
> +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_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_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_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);
> +}
> +
> +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]

The usage sites of this macro don't look like regular C syntax.
This is iffy and can confuse some code parsers.
Any chance it could be reworked to look more regular?

> +
> +static int galaxybook_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> +	struct samsung_galaxybook *galaxybook = drvdata;
> +	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 */
> +	gb_pfmode(PLATFORM_PROFILE_LOW_POWER) = GB_PERFORMANCE_MODE_FANOFF;
> +	gb_pfmode(PLATFORM_PROFILE_QUIET) = GB_PERFORMANCE_MODE_LOWNOISE;
> +	gb_pfmode(PLATFORM_PROFILE_BALANCED) = GB_PERFORMANCE_MODE_OPTIMIZED;
> +	gb_pfmode(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.iob_values[i], &profile);
> +		if (err) {
> +			dev_dbg(&galaxybook->platform->dev,
> +				"ignoring unmapped performance mode 0x%x\n", buf.iob_values[i]);
> +			continue;
> +		}
> +		switch (buf.iob_values[i]) {
> +		case GB_PERFORMANCE_MODE_OPTIMIZED_V2:
> +			gb_pfmode(profile) = GB_PERFORMANCE_MODE_OPTIMIZED_V2;
> +			break;
> +		case GB_PERFORMANCE_MODE_PERFORMANCE_V2:
> +			/* only update if not already overwritten by Ultra */
> +			if (gb_pfmode(profile) != GB_PERFORMANCE_MODE_ULTRA)
> +				gb_pfmode(profile) = GB_PERFORMANCE_MODE_PERFORMANCE_V2;
> +			break;
> +		case GB_PERFORMANCE_MODE_ULTRA:
> +			gb_pfmode(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, gb_pfmode(profile));
> +	}
> +
> +	/* initialize performance_mode using balanced's mapped value */
> +	if (test_bit(PLATFORM_PROFILE_BALANCED, choices))
> +		return performance_mode_acpi_set(galaxybook, gb_pfmode(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;
> +
> +	/* check that performance mode appears to be supported on this device */
> +	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 0;
> +	}
> +
> +	galaxybook->has_performance_mode = true;

This should be set *after* devm_platform_profile_register() succeeded, no?
I would prefer it slightly if the flags where set by galaxybook_probe()
instead of the _init() functions.

> +
> +	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_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 provide power 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);
> +}

_show() is normally defined before _store().

> +
> +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);

I think it is unnecessary to manually clean up the group.
When the kset gets unregistered all its children and their attributes
should be cleaned up automatically.

> +}
> +
> +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)) {
> +		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,
> +				    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;
> +	galaxybook->has_kbd_backlight = false;
> +	galaxybook->has_block_recording = false;
> +	galaxybook->has_performance_mode = false;

Nit: These are already initialized to false due to kzalloc() above.

> +
> +	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)
> +		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@joshuagrisham.com>");
> +MODULE_DESCRIPTION("Samsung Galaxy Book driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.45.2
>
Joshua Grisham Jan. 25, 2025, 11:45 a.m. UTC | #2
Hi Thomas, thank you for the review and taking the time to go through it again!

Den fre 24 jan. 2025 kl 00:42 skrev Thomas Weißschuh <linux@weissschuh.net>:
>
> Hi Joshua,
>
> looks good to me.
> I have some nitpicks inline, but even for the current state:
>
> Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
>
> > +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);
> > +}
>
> For the next revision you should be able to use the power supply
> extension framework.
>

I looked around a bit in the mailing lists and saw some of the
proposed patches now which add power_supply_sysfs_add_extension() and
similar functions, but do not see them yet in for-next of the pdx86
repository. Do you think it makes more sense to wait on
samsung-galaxybook and then add these changes from the start, or go
ahead with samsung-galaxybook and then update it after with using the
new framework?

> > +
> > +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]
>
> The usage sites of this macro don't look like regular C syntax.
> This is iffy and can confuse some code parsers.
> Any chance it could be reworked to look more regular?
>

Good point, and to be honest the only reason for this was to give me a
way to keep all of the lines below 100 characters :) Now I have just
made it a local pointer within galaxybook_platform_profile_probe in
order to achieve the same effect, so hopefully it looks and feels more
"standard" now, but please take a look when I eventually send this
later as v9 !

> > +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;
> > +
> > +     /* check that performance mode appears to be supported on this device */
> > +     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 0;
> > +     }
> > +
> > +     galaxybook->has_performance_mode = true;
>
> This should be set *after* devm_platform_profile_register() succeeded, no?
> I would prefer it slightly if the flags where set by galaxybook_probe()
> instead of the _init() functions.
>

Here it gets a bit tricky. Originally, I had much of the logic from
galaxybook_platform_profile_probe in this
galaxybook_platform_profile_init function, as I really wanted to
evaluate if all of the ACPI methods were working and it was possible
to map at least one Samsung "performance mode" to a profile, but
feedback from Kurt (which I agree with) is that it is within the probe
that should really be handling this kind of logic.

At that point I decided that it was ONLY success of
performance_mode_acpi_get that I am now using to determine
has_performance_mode, so I set it immediately after more from a
"self-documenting" perspective.

Now the code works so that if galaxybook_platform_profile_probe fails,
then that failure will bubble up to galaxybook_probe which will then
cause the entire driver to unload ... so it will not matter anyway if
or where the value was set, the module will no longer even be loaded
:)

Regarding setting all of these "feature flags" in galaxybook_probe, I
think this will be even more tricky now that I have refactored to
actually fail the galaxybook_probe for "valid failures" (e.g. I have
detected that the device does seem to support kbd_backlight, for
example (the ACPI method to get the brightness gave an expected
result) but there was some kind of failure when registering the LED
class... which, yes, in this case I would guess we DO want that it
should fail and the driver should be unloaded, because something that
definitely SHOULD work has failed ---- that is the thinking, now,
anyway)

These flags are mostly being used to control behavior after the driver
has probed and the user is interacting with various things from the
userspace (e.g. pressing hotkeys) -- we don't want the driver to try
and use features that we detected during the probe are not supported
on the particular device the driver is running on.

So essentially I want that the various init() functions called from
galaxybook_probe will return 0 (success) even if the feature is not
supported, but internally within that respective init() function I may
have disabled the feature (has_kbd_backlight=false for example).
Because of this then I think it would be a bit tricky to try and
implement setting the flags back in galaxybook_probe (e.g. need to
create some kind of custom return facility that indicates if there was
a "real error" vs if the feature is not supported but the probe should
continue?). To me it sounds a bit more complicated and potentially
"hacky" but if you have a good suggestion on how this could be done in
a better way then I would most definitely welcome it!

The only thing that comes immediately to mind is that I could pass a
pointer to the flags in the init method (e.g. "err =
galaxybook_kbd_backlight_init(galaxybook,
&galaxybook->has_kdb_backlight);" within galaxybook_probe) but it
feels more confusing and kind of same-same result compared to what
there is now (that the logic for setting the value would still be
within the init functions anyway....)

Past ALL of that I do not have any strong opinions on if setting
has_performance_mode should come before or after
devm_platform_profile_register and am fine to change it if it should
be changed. As I mentioned before, I did it this way as it felt more
"self-documenting" and also keeping in mind that if
devm_platform_profile_register returns nonzero then the whole driver
will be unloaded anyway so it would not matter :)

> > +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);
> > +}
>
> _show() is normally defined before _store().
>

Thanks for catching this! I have now gone through the entire driver
and tried to match this pattern ("get" before "set" for the ACPI
method functions, and "show" before "store" for all of the sysfs attr
functions) -- this update will come in v9 of the patch.

> > +     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);
>
> I think it is unnecessary to manually clean up the group.
> When the kset gets unregistered all its children and their attributes
> should be cleaned up automatically.
>

Thank you, good catch! I tested this and you are absolutely right, it
works exactly as you say. So I have removed this for the coming v9 :)

> > +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;
>
> Nit: These are already initialized to false due to kzalloc() above.
>

Thank you, good catch again. I think I got stuck in my head when I
fixed an uninitialized local variable somewhere else and was thinking
more in terms of malloc here.. I have removed these :)


I will wait another day or two in case there are any other comments
(or answer to the above question on timing for implementing the power
supply extension framework); if I don't hear anything by then then I
will go ahead and send what I have now (per comments above) as v9.

Thank you again!

Best regards,
Joshua
Thomas Weißschuh Jan. 25, 2025, 12:26 p.m. UTC | #3
On 2025-01-25 12:45:02+0100, Joshua Grisham wrote:
> Hi Thomas, thank you for the review and taking the time to go through it again!
> 
> Den fre 24 jan. 2025 kl 00:42 skrev Thomas Weißschuh <linux@weissschuh.net>:
> >
> > Hi Joshua,
> >
> > looks good to me.
> > I have some nitpicks inline, but even for the current state:
> >
> > Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
> >
> > > +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);
> > > +}
> >
> > For the next revision you should be able to use the power supply
> > extension framework.
> >
> 
> I looked around a bit in the mailing lists and saw some of the
> proposed patches now which add power_supply_sysfs_add_extension() and
> similar functions, but do not see them yet in for-next of the pdx86
> repository. Do you think it makes more sense to wait on
> samsung-galaxybook and then add these changes from the start, or go
> ahead with samsung-galaxybook and then update it after with using the
> new framework?

The API is power_supply_register_extension().
Indeed this is not yet part of the pdx86/for-next branch, as that is
still based upon v6.13-rc1 while power_supply_register_extension() will
only be available with v6.14-rc1 (to be released next Sunday).
However we are currently in the middle of the merge window for v6.14.
So your driver won't make it into v6.14 in any case and by the time it
can be applied to pdx86/for-next that branch will be based on v6.14-rc1.

So either wait a week or merge in power-supply/for-next manually.

Or you decide that I am annoying to push you to adopt my own feature and
just go without it :-)

> > > +
> > > +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]
> >
> > The usage sites of this macro don't look like regular C syntax.
> > This is iffy and can confuse some code parsers.
> > Any chance it could be reworked to look more regular?
> >
> 
> Good point, and to be honest the only reason for this was to give me a
> way to keep all of the lines below 100 characters :) Now I have just
> made it a local pointer within galaxybook_platform_profile_probe in
> order to achieve the same effect, so hopefully it looks and feels more
> "standard" now, but please take a look when I eventually send this
> later as v9 !

Sounds good.

> > > +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;
> > > +
> > > +     /* check that performance mode appears to be supported on this device */
> > > +     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 0;
> > > +     }
> > > +
> > > +     galaxybook->has_performance_mode = true;
> >
> > This should be set *after* devm_platform_profile_register() succeeded, no?
> > I would prefer it slightly if the flags where set by galaxybook_probe()
> > instead of the _init() functions.
> >
> 
> Here it gets a bit tricky. Originally, I had much of the logic from
> galaxybook_platform_profile_probe in this
> galaxybook_platform_profile_init function, as I really wanted to
> evaluate if all of the ACPI methods were working and it was possible
> to map at least one Samsung "performance mode" to a profile, but
> feedback from Kurt (which I agree with) is that it is within the probe
> that should really be handling this kind of logic.
> 
> At that point I decided that it was ONLY success of
> performance_mode_acpi_get that I am now using to determine
> has_performance_mode, so I set it immediately after more from a
> "self-documenting" perspective.
> 
> Now the code works so that if galaxybook_platform_profile_probe fails,
> then that failure will bubble up to galaxybook_probe which will then
> cause the entire driver to unload ... so it will not matter anyway if
> or where the value was set, the module will no longer even be loaded
> :)
> 
> Regarding setting all of these "feature flags" in galaxybook_probe, I
> think this will be even more tricky now that I have refactored to
> actually fail the galaxybook_probe for "valid failures" (e.g. I have
> detected that the device does seem to support kbd_backlight, for
> example (the ACPI method to get the brightness gave an expected
> result) but there was some kind of failure when registering the LED
> class... which, yes, in this case I would guess we DO want that it
> should fail and the driver should be unloaded, because something that
> definitely SHOULD work has failed ---- that is the thinking, now,
> anyway)
> 
> These flags are mostly being used to control behavior after the driver
> has probed and the user is interacting with various things from the
> userspace (e.g. pressing hotkeys) -- we don't want the driver to try
> and use features that we detected during the probe are not supported
> on the particular device the driver is running on.
> 
> So essentially I want that the various init() functions called from
> galaxybook_probe will return 0 (success) even if the feature is not
> supported, but internally within that respective init() function I may
> have disabled the feature (has_kbd_backlight=false for example).
> Because of this then I think it would be a bit tricky to try and
> implement setting the flags back in galaxybook_probe (e.g. need to
> create some kind of custom return facility that indicates if there was
> a "real error" vs if the feature is not supported but the probe should
> continue?). To me it sounds a bit more complicated and potentially
> "hacky" but if you have a good suggestion on how this could be done in
> a better way then I would most definitely welcome it!
> 
> The only thing that comes immediately to mind is that I could pass a
> pointer to the flags in the init method (e.g. "err =
> galaxybook_kbd_backlight_init(galaxybook,
> &galaxybook->has_kdb_backlight);" within galaxybook_probe) but it
> feels more confusing and kind of same-same result compared to what
> there is now (that the logic for setting the value would still be
> within the init functions anyway....)
> 
> Past ALL of that I do not have any strong opinions on if setting
> has_performance_mode should come before or after
> devm_platform_profile_register and am fine to change it if it should
> be changed. As I mentioned before, I did it this way as it felt more
> "self-documenting" and also keeping in mind that if
> devm_platform_profile_register returns nonzero then the whole driver
> will be unloaded anyway so it would not matter :)

You could designate a special error code to mean:
"This feature is not supported, but that's fine and continue probing".

For example EOPNOTSUPP:

ret = init_foo();
if (ret == 0)
	priv->have_foo;
elif (ret != EOPNOTSUPP)
	return ret;

ret = init_bar();
...

[snip]
Joshua Grisham Jan. 25, 2025, 1:42 p.m. UTC | #4
Den lör 25 jan. 2025 kl 13:26 skrev Thomas Weißschuh <linux@weissschuh.net>:
>
>
> You could designate a special error code to mean:
> "This feature is not supported, but that's fine and continue probing".
>
> For example EOPNOTSUPP:
>
> ret = init_foo();
> if (ret == 0)
>         priv->have_foo;
> elif (ret != EOPNOTSUPP)
>         return ret;
>
> ret = init_bar();
> ...
>
> [snip]

Hi Thomas! This is an good suggestion and might make it seem more
"native," which I like. One thing I am worried about, though, do you
think it is possible that any of the other functions called by these
various inits (registering various devices etc) could or might be
updated in the future to legitimately return EOPNOTSUPP, in which case
when I just pass their return code along there would be some
unexpected behavior? (that the driver does not unload but in fact
continues)

Or are you saying to return a positive EOPNOTSUPP instead of a
negative -EOPNOTSUPP to help ensure that this problem would be less
likely?

Thanks again!

Joshua
Kurt Borja Jan. 25, 2025, 3:06 p.m. UTC | #5
On Sat Jan 25, 2025 at 6:45 AM -05, Joshua Grisham wrote:
> Hi Thomas, thank you for the review and taking the time to go through it again!
>
> Den fre 24 jan. 2025 kl 00:42 skrev Thomas Weißschuh <linux@weissschuh.net>:
>>
>> Hi Joshua,
>>
>> looks good to me.
>> I have some nitpicks inline, but even for the current state:
>>
>> Reviewed-by: Thomas Weißschuh <linux@weissschuh.net>
>>
>> > +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);
>> > +}
>>
>> For the next revision you should be able to use the power supply
>> extension framework.
>>
>
> I looked around a bit in the mailing lists and saw some of the
> proposed patches now which add power_supply_sysfs_add_extension() and
> similar functions, but do not see them yet in for-next of the pdx86
> repository. Do you think it makes more sense to wait on
> samsung-galaxybook and then add these changes from the start, or go
> ahead with samsung-galaxybook and then update it after with using the
> new framework?
>
>> > +
>> > +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]
>>
>> The usage sites of this macro don't look like regular C syntax.
>> This is iffy and can confuse some code parsers.
>> Any chance it could be reworked to look more regular?
>>
>
> Good point, and to be honest the only reason for this was to give me a
> way to keep all of the lines below 100 characters :) Now I have just
> made it a local pointer within galaxybook_platform_profile_probe in
> order to achieve the same effect, so hopefully it looks and feels more
> "standard" now, but please take a look when I eventually send this
> later as v9 !
>
>> > +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;
>> > +
>> > +     /* check that performance mode appears to be supported on this device */
>> > +     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 0;
>> > +     }
>> > +
>> > +     galaxybook->has_performance_mode = true;
>>
>> This should be set *after* devm_platform_profile_register() succeeded, no?
>> I would prefer it slightly if the flags where set by galaxybook_probe()
>> instead of the _init() functions.
>>
>
> Here it gets a bit tricky. Originally, I had much of the logic from
> galaxybook_platform_profile_probe in this
> galaxybook_platform_profile_init function, as I really wanted to
> evaluate if all of the ACPI methods were working and it was possible
> to map at least one Samsung "performance mode" to a profile, but
> feedback from Kurt (which I agree with) is that it is within the probe
> that should really be handling this kind of logic.
>
> At that point I decided that it was ONLY success of
> performance_mode_acpi_get that I am now using to determine
> has_performance_mode, so I set it immediately after more from a
> "self-documenting" perspective.
>
> Now the code works so that if galaxybook_platform_profile_probe fails,
> then that failure will bubble up to galaxybook_probe which will then
> cause the entire driver to unload ... so it will not matter anyway if
> or where the value was set, the module will no longer even be loaded
> :)

Now I understand the original problem better. I didn't consider this
possibility when designing the callback.

While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
the way to go. I think positive err value would be the safest but you
should wait for the advice of someone with more experience.

Aside from that I really like how the whole platform profile sections
works now. Good design choices :)

~ Kurt

> <snip>
Armin Wolf Jan. 26, 2025, 2:34 p.m. UTC | #6
Am 18.01.25 um 21:26 schrieb Joshua Grisham:

> 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

Reviewed-by: Armin Wolf <W_Armin@gmx.de>

> Signed-off-by: Joshua Grisham <josh@joshuagrisham.com>
>
> ---
>
> 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@gmx.de/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
>
> v6->v7:
> - Rebase to latest for-next and implement updates to firmware-attributes,
>    platform_profile, and i8042 filter (including removal of global pointer).
>
> v7->v8:
> - Simplification and logic correction to platform_profile mapping and
>    probe implementation, including renaming the internal performance modes
>    to more closely match the internal names used by Samsung in Windows and
>    align the logic in this driver to how it works with their Windows
>    services.
> ---
>   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     | 1430 +++++++++++++++++
>   6 files changed, 1628 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@joshuagrisham.com>
> +
> +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@vger.kernel.org
>   S:	Maintained
>   F:	drivers/video/fbdev/s3c-fb.c
>
> +SAMSUNG GALAXY BOOK DRIVER
> +M:	Joshua Grisham <josh@joshuagrisham.com>
> +L:	platform-driver-x86@vger.kernel.org
> +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@samsung.com>
>   M:	Artur Świgoń <a.swigon@samsung.com>
> 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..c70024691656
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-galaxybook.c
> @@ -0,0 +1,1430 @@
> +// 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;
> +	struct device_attribute charge_control_end_threshold_attr;
> +
> +	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 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_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
> +
> +/*
> + * 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_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_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_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_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);
> +}
> +
> +#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]
> +
> +static int galaxybook_platform_profile_probe(void *drvdata, unsigned long *choices)
> +{
> +	struct samsung_galaxybook *galaxybook = drvdata;
> +	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 */
> +	gb_pfmode(PLATFORM_PROFILE_LOW_POWER) = GB_PERFORMANCE_MODE_FANOFF;
> +	gb_pfmode(PLATFORM_PROFILE_QUIET) = GB_PERFORMANCE_MODE_LOWNOISE;
> +	gb_pfmode(PLATFORM_PROFILE_BALANCED) = GB_PERFORMANCE_MODE_OPTIMIZED;
> +	gb_pfmode(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.iob_values[i], &profile);
> +		if (err) {
> +			dev_dbg(&galaxybook->platform->dev,
> +				"ignoring unmapped performance mode 0x%x\n", buf.iob_values[i]);
> +			continue;
> +		}
> +		switch (buf.iob_values[i]) {
> +		case GB_PERFORMANCE_MODE_OPTIMIZED_V2:
> +			gb_pfmode(profile) = GB_PERFORMANCE_MODE_OPTIMIZED_V2;
> +			break;
> +		case GB_PERFORMANCE_MODE_PERFORMANCE_V2:
> +			/* only update if not already overwritten by Ultra */
> +			if (gb_pfmode(profile) != GB_PERFORMANCE_MODE_ULTRA)
> +				gb_pfmode(profile) = GB_PERFORMANCE_MODE_PERFORMANCE_V2;
> +			break;
> +		case GB_PERFORMANCE_MODE_ULTRA:
> +			gb_pfmode(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, gb_pfmode(profile));
> +	}
> +
> +	/* initialize performance_mode using balanced's mapped value */
> +	if (test_bit(PLATFORM_PROFILE_BALANCED, choices))
> +		return performance_mode_acpi_set(galaxybook, gb_pfmode(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;
> +
> +	/* check that performance mode appears to be supported on this device */
> +	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 0;
> +	}
> +
> +	galaxybook->has_performance_mode = true;
> +
> +	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_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 provide power 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);
> +}
> +
> +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)) {
> +		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,
> +				    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;
> +	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_platform_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@joshuagrisham.com>");
> +MODULE_DESCRIPTION("Samsung Galaxy Book driver");
> +MODULE_LICENSE("GPL");
Joshua Grisham Jan. 28, 2025, 7:17 p.m. UTC | #7
Thank you Kurt!

Den lör 25 jan. 2025 kl 16:06 skrev Kurt Borja <kuurtb@gmail.com>:
>
> Now I understand the original problem better. I didn't consider this
> possibility when designing the callback.
>
> While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
> the way to go. I think positive err value would be the safest but you
> should wait for the advice of someone with more experience.
>
> Aside from that I really like how the whole platform profile sections
> works now. Good design choices :)
>
> ~ Kurt
>
> > <snip>

Regarding using this positive error code internally within the module,
I thought about maybe adding a comment to galaxybook_probe() before
all of the inits which describe this a bit -- do you all think this
will be helpful or is it clear enough / does not matter and can be
skipped?

I also realized that maybe it is worth to describe that a specific
sequence is needed for doing these "enable feature" + init calls to
the ACPI methods otherwise some devices were reported as starting to
reject the payloads if the sequence was not followed.

Based on these two then I have drafted a comment sort of like this to
put in galaxybook_probe() before the init() calls:

/*
* 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)
*
* The init function for features which are not supported on all devices
* will return EOPNOTSUPP (positive to differentiate it from upstream
* error codes) if the feature is not working and should be ignored.
*/

Does adding something like this seem like it would help make
everything more clear (especially thinking when new refactoring comes
by other maintainers in X months/years/decades, it would probably help
them to know these subtleties, right?)?

If this comment (you all are welcome to suggest wording tweaks as
well, of course!) plus the few other small tweaks make sense then I
can prep this to send as a new version. But I am holding a bit in
hopes that the 6.14 stuff gets merged to pdx86 for-next so that I can
go ahead with implementing Thomas's new power supply extension
interface at the same time.

Because there are multiple variations to these devices, and there were
some small issues that users with other devices found, I was
thinking/hoping once all looks good for all reviewers, including
implementing the power supply extension, that this could be merged in
to for-next and then I can ask a few people with other supported
devices to test this revamped (and in some ways completely refactored)
driver directly from the branch so that we can try to catch any other
issues that I did not see on my device before it is proposed as a
candidate for mainline -- does that sound reasonable?

Thanks again!

Best regards,
Joshua
Thomas Weißschuh Jan. 28, 2025, 9:17 p.m. UTC | #8
Hi Joshua,

sorry for the late reply.

On 2025-01-28 20:17:53+0100, Joshua Grisham wrote:
> Thank you Kurt!
> 
> Den lör 25 jan. 2025 kl 16:06 skrev Kurt Borja <kuurtb@gmail.com>:
> >
> > Now I understand the original problem better. I didn't consider this
> > possibility when designing the callback.
> >
> > While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
> > the way to go. I think positive err value would be the safest but you
> > should wait for the advice of someone with more experience.
> >
> > Aside from that I really like how the whole platform profile sections
> > works now. Good design choices :)
> >
> > ~ Kurt
> >
> > > <snip>
> 
> Regarding using this positive error code internally within the module,
> I thought about maybe adding a comment to galaxybook_probe() before
> all of the inits which describe this a bit -- do you all think this
> will be helpful or is it clear enough / does not matter and can be
> skipped?

To me that sounds reasonable.

<snip>

> If this comment (you all are welcome to suggest wording tweaks as
> well, of course!) plus the few other small tweaks make sense then I
> can prep this to send as a new version. But I am holding a bit in
> hopes that the 6.14 stuff gets merged to pdx86 for-next so that I can
> go ahead with implementing Thomas's new power supply extension
> interface at the same time.

Nice :-)

<snip>
Ilpo Järvinen Jan. 29, 2025, 1:47 p.m. UTC | #9
On Tue, 28 Jan 2025, Joshua Grisham wrote:

> Thank you Kurt!
> 
> Den lör 25 jan. 2025 kl 16:06 skrev Kurt Borja <kuurtb@gmail.com>:
> >
> > Now I understand the original problem better. I didn't consider this
> > possibility when designing the callback.
> >
> > While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
> > the way to go. I think positive err value would be the safest but you
> > should wait for the advice of someone with more experience.
> >
> > Aside from that I really like how the whole platform profile sections
> > works now. Good design choices :)
> >
> > ~ Kurt
> >
> > > <snip>
> 
> Regarding using this positive error code internally within the module,
> I thought about maybe adding a comment to galaxybook_probe() before
> all of the inits which describe this a bit -- do you all think this
> will be helpful or is it clear enough / does not matter and can be
> skipped?
> 
> I also realized that maybe it is worth to describe that a specific
> sequence is needed for doing these "enable feature" + init calls to
> the ACPI methods otherwise some devices were reported as starting to
> reject the payloads if the sequence was not followed.
> 
> Based on these two then I have drafted a comment sort of like this to
> put in galaxybook_probe() before the init() calls:
> 
> /*
> * 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)
> *
> * The init function for features which are not supported on all devices
> * will return EOPNOTSUPP (positive to differentiate it from upstream
> * error codes) if the feature is not working and should be ignored.
> */
> 
> Does adding something like this seem like it would help make
> everything more clear (especially thinking when new refactoring comes
> by other maintainers in X months/years/decades, it would probably help
> them to know these subtleties, right?)?
> 
> If this comment (you all are welcome to suggest wording tweaks as
> well, of course!) plus the few other small tweaks make sense then I
> can prep this to send as a new version. But I am holding a bit in
> hopes that the 6.14 stuff gets merged to pdx86 for-next so that I can
> go ahead with implementing Thomas's new power supply extension
> interface at the same time.

Hi Joshua,

In general, you don't need to wait for me to act because any commit in 
Linus' tree during merge window will be fast-forwardable to 6.14-rc1 which 
is the commit I'll be basing for-next for 6.15. So you can just pick the 
latest commit on Linus' tree and rebase on top of it.

If there ever are some commits applied already during the merge window 
into for-next, those would get rebased on top of rc1 once it's released.

> Because there are multiple variations to these devices, and there were
> some small issues that users with other devices found, I was
> thinking/hoping once all looks good for all reviewers, including
> implementing the power supply extension, that this could be merged in
> to for-next and then I can ask a few people with other supported
> devices to test this revamped (and in some ways completely refactored)
> driver directly from the branch so that we can try to catch any other
> issues that I did not see on my device before it is proposed as a
> candidate for mainline -- does that sound reasonable?
> 
> Thanks again!
> 
> Best regards,
> Joshua
>
Joshua Grisham Jan. 30, 2025, 5:17 p.m. UTC | #10
Den tis 28 jan. 2025 kl 22:17 skrev Thomas Weißschuh <linux@weissschuh.net>:
>
> Hi Joshua,
>
> sorry for the late reply.
>
> On 2025-01-28 20:17:53+0100, Joshua Grisham wrote:
> > Thank you Kurt!
> >
> > Den lör 25 jan. 2025 kl 16:06 skrev Kurt Borja <kuurtb@gmail.com>:
> > >
> > > Now I understand the original problem better. I didn't consider this
> > > possibility when designing the callback.
> > >
> > > While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
> > > the way to go. I think positive err value would be the safest but you
> > > should wait for the advice of someone with more experience.
> > >
> > > Aside from that I really like how the whole platform profile sections
> > > works now. Good design choices :)
> > >
> > > ~ Kurt
> > >
> > > > <snip>
> >
> > Regarding using this positive error code internally within the module,
> > I thought about maybe adding a comment to galaxybook_probe() before
> > all of the inits which describe this a bit -- do you all think this
> > will be helpful or is it clear enough / does not matter and can be
> > skipped?
>
> To me that sounds reasonable.
>
> <snip>
>
> > If this comment (you all are welcome to suggest wording tweaks as
> > well, of course!) plus the few other small tweaks make sense then I
> > can prep this to send as a new version. But I am holding a bit in
> > hopes that the 6.14 stuff gets merged to pdx86 for-next so that I can
> > go ahead with implementing Thomas's new power supply extension
> > interface at the same time.
>
> Nice :-)
>
> <snip>

Hi Thomas! I have been looking into this now and it seems I have
gotten it working using the new power_supply_register_extension
without too much fuss. It seems like a nice API but especially helpful
when there are multiple attributes (in this case there is only 1, but
still feels "nicer" than manually creating sysfs files!).

One thing I noticed was that, as I still needed a pointer to the
battery's struct power_supply, then it seemed to still work best if I
left in the existing battery hook (devm_battery_hook_register) and
then within the add_battery callback I could take the pointer to the
struct power_supply to hang my new power_supply_ext onto.  Essentially
much of the code for the "init" + using a battery hook that I had from
before is still the same, I have just replaced manually creating the
sysfs file (and its show/store callbacks) with the extension and then
implemented the callbacks to get/set the value from power_supply_ext
instead. Does this sound basically as you would have expected?

Regarding the possibility for the module to be loaded multiple times
and/or if one of these devices suddenly had multiple battery devices,
is there anything within power supply extension framework that will
handle multiple instances (e.g. auto-appending a number to the name or
something like with LED classes) or would this case need to be somehow
covered in each individual implementation (e.g. samsung-galaxybook)?

I had not really covered this so well either when just manually
creating the sysfs attribute (working assumption is that
device_create_file() would have failed if trying to create the same
file more than once under the same battery device, which would have
lead to a probe fail and the module unloaded for this "second"
instance ? ) and not sure what exactly the best approach would be
without giving it a bit more thought.. but maybe you can think about
this a bit when I send v9 of the patch up in just a few minutes :)
This is again a super "corner case" and it would certainly be very
weird if one of these devices suddenly had multiple instances of one
of the supporting ACPI device IDs so I am not sure if it would ever
really be a reasonable possibility. More than one battery in the same
device seems more likely, but not super reasonable either given that
one of the hallmarks of these devices is "thin and light" then I would
guess it is not likely there would ever be one with multiple
batteries?

Thanks again!

Joshua
Thomas Weißschuh Jan. 30, 2025, 6:51 p.m. UTC | #11
On 2025-01-30 18:17:47+0100, Joshua Grisham wrote:
> Den tis 28 jan. 2025 kl 22:17 skrev Thomas Weißschuh <linux@weissschuh.net>:
> >
> > Hi Joshua,
> >
> > sorry for the late reply.
> >
> > On 2025-01-28 20:17:53+0100, Joshua Grisham wrote:
> > > Thank you Kurt!
> > >
> > > Den lör 25 jan. 2025 kl 16:06 skrev Kurt Borja <kuurtb@gmail.com>:
> > > >
> > > > Now I understand the original problem better. I didn't consider this
> > > > possibility when designing the callback.
> > > >
> > > > While this is a fine solution I believe Thomas' EOPNOTSUPP solution is
> > > > the way to go. I think positive err value would be the safest but you
> > > > should wait for the advice of someone with more experience.
> > > >
> > > > Aside from that I really like how the whole platform profile sections
> > > > works now. Good design choices :)
> > > >
> > > > ~ Kurt
> > > >
> > > > > <snip>
> > >
> > > Regarding using this positive error code internally within the module,
> > > I thought about maybe adding a comment to galaxybook_probe() before
> > > all of the inits which describe this a bit -- do you all think this
> > > will be helpful or is it clear enough / does not matter and can be
> > > skipped?
> >
> > To me that sounds reasonable.
> >
> > <snip>
> >
> > > If this comment (you all are welcome to suggest wording tweaks as
> > > well, of course!) plus the few other small tweaks make sense then I
> > > can prep this to send as a new version. But I am holding a bit in
> > > hopes that the 6.14 stuff gets merged to pdx86 for-next so that I can
> > > go ahead with implementing Thomas's new power supply extension
> > > interface at the same time.
> >
> > Nice :-)
> >
> > <snip>
> 
> Hi Thomas! I have been looking into this now and it seems I have
> gotten it working using the new power_supply_register_extension
> without too much fuss. It seems like a nice API but especially helpful
> when there are multiple attributes (in this case there is only 1, but
> still feels "nicer" than manually creating sysfs files!).

There are also some functional improvements :-)

> One thing I noticed was that, as I still needed a pointer to the
> battery's struct power_supply, then it seemed to still work best if I
> left in the existing battery hook (devm_battery_hook_register) and
> then within the add_battery callback I could take the pointer to the
> struct power_supply to hang my new power_supply_ext onto.  Essentially
> much of the code for the "init" + using a battery hook that I had from
> before is still the same, I have just replaced manually creating the
> sysfs file (and its show/store callbacks) with the extension and then
> implemented the callbacks to get/set the value from power_supply_ext
> instead. Does this sound basically as you would have expected?

Yes.

> Regarding the possibility for the module to be loaded multiple times
> and/or if one of these devices suddenly had multiple battery devices,
> is there anything within power supply extension framework that will
> handle multiple instances (e.g. auto-appending a number to the name or
> something like with LED classes) or would this case need to be somehow
> covered in each individual implementation (e.g. samsung-galaxybook)?

First some nitpicking about the wording:
A module can only ever be loaded once at the same time.
However the driver from the module can be probed and bound multiple
times.

The extensions are hanging off the original struct power_supply.
The battery hook will be called for each battery and then it can add a
dedicated extension instance for each one.

> I had not really covered this so well either when just manually
> creating the sysfs attribute (working assumption is that
> device_create_file() would have failed if trying to create the same
> file more than once under the same battery device, which would have
> lead to a probe fail and the module unloaded for this "second"
> instance ? ) and not sure what exactly the best approach would be
> without giving it a bit more thought.. but maybe you can think about
> this a bit when I send v9 of the patch up in just a few minutes :)
>
> This is again a super "corner case" and it would certainly be very
> weird if one of these devices suddenly had multiple instances of one
> of the supporting ACPI device IDs so I am not sure if it would ever
> really be a reasonable possibility. More than one battery in the same
> device seems more likely, but not super reasonable either given that
> one of the hallmarks of these devices is "thin and light" then I would
> guess it is not likely there would ever be one with multiple
> batteries?

More than one battery should work fine. Your hook gets called for each.
If you think that doesn't make sense just restrict the hook to a single
instance. For an example see drivers/power/supply/cros_charge-control.c
The same can be done for ACPI device, but I think many drivers share
this issue and don't have any handling for that.
diff mbox series

Patch

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@joshuagrisham.com>
+
+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@vger.kernel.org
 S:	Maintained
 F:	drivers/video/fbdev/s3c-fb.c
 
+SAMSUNG GALAXY BOOK DRIVER
+M:	Joshua Grisham <josh@joshuagrisham.com>
+L:	platform-driver-x86@vger.kernel.org
+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@samsung.com>
 M:	Artur Świgoń <a.swigon@samsung.com>
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..c70024691656
--- /dev/null
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -0,0 +1,1430 @@ 
+// 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;
+	struct device_attribute charge_control_end_threshold_attr;
+
+	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 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_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
+
+/*
+ * 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_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_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_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_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);
+}
+
+#define gb_pfmode(profile) galaxybook->profile_performance_modes[profile]
+
+static int galaxybook_platform_profile_probe(void *drvdata, unsigned long *choices)
+{
+	struct samsung_galaxybook *galaxybook = drvdata;
+	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 */
+	gb_pfmode(PLATFORM_PROFILE_LOW_POWER) = GB_PERFORMANCE_MODE_FANOFF;
+	gb_pfmode(PLATFORM_PROFILE_QUIET) = GB_PERFORMANCE_MODE_LOWNOISE;
+	gb_pfmode(PLATFORM_PROFILE_BALANCED) = GB_PERFORMANCE_MODE_OPTIMIZED;
+	gb_pfmode(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.iob_values[i], &profile);
+		if (err) {
+			dev_dbg(&galaxybook->platform->dev,
+				"ignoring unmapped performance mode 0x%x\n", buf.iob_values[i]);
+			continue;
+		}
+		switch (buf.iob_values[i]) {
+		case GB_PERFORMANCE_MODE_OPTIMIZED_V2:
+			gb_pfmode(profile) = GB_PERFORMANCE_MODE_OPTIMIZED_V2;
+			break;
+		case GB_PERFORMANCE_MODE_PERFORMANCE_V2:
+			/* only update if not already overwritten by Ultra */
+			if (gb_pfmode(profile) != GB_PERFORMANCE_MODE_ULTRA)
+				gb_pfmode(profile) = GB_PERFORMANCE_MODE_PERFORMANCE_V2;
+			break;
+		case GB_PERFORMANCE_MODE_ULTRA:
+			gb_pfmode(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, gb_pfmode(profile));
+	}
+
+	/* initialize performance_mode using balanced's mapped value */
+	if (test_bit(PLATFORM_PROFILE_BALANCED, choices))
+		return performance_mode_acpi_set(galaxybook, gb_pfmode(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;
+
+	/* check that performance mode appears to be supported on this device */
+	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 0;
+	}
+
+	galaxybook->has_performance_mode = true;
+
+	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_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 provide power 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);
+}
+
+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)) {
+		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,
+				    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;
+	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_platform_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@joshuagrisham.com>");
+MODULE_DESCRIPTION("Samsung Galaxy Book driver");
+MODULE_LICENSE("GPL");