diff mbox series

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

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

Commit Message

Joshua Grisham Dec. 26, 2024, 3:30 p.m. UTC
Adds 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
---
 .../testing/sysfs-class-firmware-attributes   |   28 +
 Documentation/admin-guide/laptops/index.rst   |    1 +
 .../laptops/samsung-galaxybook.rst            |  165 ++
 MAINTAINERS                                   |    7 +
 drivers/platform/x86/Kconfig                  |   18 +
 drivers/platform/x86/Makefile                 |    5 +-
 drivers/platform/x86/samsung-galaxybook.c     | 1493 +++++++++++++++++
 7 files changed, 1715 insertions(+), 2 deletions(-)
 create mode 100644 Documentation/admin-guide/laptops/samsung-galaxybook.rst
 create mode 100644 drivers/platform/x86/samsung-galaxybook.c

Comments

Kurt Borja Dec. 30, 2024, 5:50 p.m. UTC | #1
On Thu, Dec 26, 2024 at 04:30:22PM +0100, Joshua Grisham wrote:
> Adds 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

Hi Joshua!

A couple of comments:

> 
> 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
> ---
>  .../testing/sysfs-class-firmware-attributes   |   28 +
>  Documentation/admin-guide/laptops/index.rst   |    1 +
>  .../laptops/samsung-galaxybook.rst            |  165 ++
>  MAINTAINERS                                   |    7 +
>  drivers/platform/x86/Kconfig                  |   18 +
>  drivers/platform/x86/Makefile                 |    5 +-
>  drivers/platform/x86/samsung-galaxybook.c     | 1493 +++++++++++++++++
>  7 files changed, 1715 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/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> index 2713efa509b4..dd36577b68f2 100644
> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> @@ -326,6 +326,17 @@ Description:
>  					This role is specific to Secure Platform Management (SPM) attribute.
>  					It requires configuring an endorsement (kek) and signing certificate (sk).
>  
> +What:		/sys/class/firmware-attributes/*/attributes/camera_lens_cover
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control the behavior of a software-based camera lens
> +		cover. The value is a boolean represented by 0 for false (camera is not blocked)
> +		and 1 for true (camera is blocked).
> +
> +		On Samsung Galaxy Book systems, this attribute will also control a software-based
> +		"cover" of the microphone in addition to the camera.
>  
>  What:		/sys/class/firmware-attributes/*/attributes/pending_reboot
>  Date:		February 2021
> @@ -356,6 +367,14 @@ Description:
>  		Drivers may emit a CHANGE uevent when this value changes and userspace
>  		may check it again.
>  
> +What:		/sys/class/firmware-attributes/*/attributes/power_on_lid_open
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control powering on a device when the lid is opened.
> +		The value is a boolean represented by 0 for false and 1 for true.
> +
>  What:		/sys/class/firmware-attributes/*/attributes/reset_bios
>  Date:		February 2021
>  KernelVersion:	5.11
> @@ -429,6 +448,15 @@ Description:
>  		HP specific class extensions - Secure Platform Manager (SPM)
>  		--------------------------------
>  
> +What:		/sys/class/firmware-attributes/*/attributes/usb_charging
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control if USB ports can continue to deliver power to
> +		connected devices when the device is powered off or in a low sleep state. The value
> +		is a boolean represented by 0 for false and 1 for true.
> +
>  What:		/sys/class/firmware-attributes/*/authentication/SPM/kek
>  Date:		March 2023
>  KernelVersion:	5.18
> 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..65da7cd84c01
> --- /dev/null
> +++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst
> @@ -0,0 +1,165 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +==========================
> +Samsung Galaxy Book Extras
> +==========================
> +
> +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 device extension
> +- :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
> +done dynamically based on a list of the supported modes reported by the device
> +itself. Preference is given to always try and map ``low-power``, ``balanced``,
> +and ``performance`` profiles, as these seem to be the most common profiles
> +utilized (and sometimes even required) by various userspace tools.
> +
> +The result of the mapping will 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 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:
> +
> +- ``camera_lens_cover``
> +- ``power_on_lid_open``
> +- ``usb_charging``
> +
> +These attributes are documented in more detail under
> +Documentation/admin-guide/abi.rst.
> +
> +.. _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 "camera lens cover" setting, which blocks
> +or allows usage of the built-in camera and microphone.
> +
> +There is a new "Samsung Galaxy Book Extra Buttons" input device created which
> +will send input events for the following notifications:
> +
> +- Switch ``SW_CAMERA_LENS_COVER`` (on or off) when the camera and microphone are
> +  "blocked" or "allowed" when toggling the Camera Lens Cover setting.
> +
> +.. _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..e74873a1e74b 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 EXTRAS 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..ecc509f5df55 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -778,6 +778,24 @@ 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 extras 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
> +	select INPUT_SPARSEKMAP
> +	help
> +	  This is a driver for Samsung Galaxy Book series notebooks. It adds
> +	  support for the keyboard backlight control, performance mode control, fan
> +	  speed reporting, function keys, and various other device controls.
> +
> +	  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..c656471dd1c7
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-galaxybook.c
> @@ -0,0 +1,1493 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Samsung Galaxy Book series extras driver
> + *
> + * Copyright (c) 2024 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/err.h>
> +#include <linux/i8042.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.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"
> +
> +static const struct acpi_device_id galaxybook_device_ids[] = {
> +	{ "SAM0427" },
> +	{ "SAM0428" },
> +	{ "SAM0429" },
> +	{ "SAM0430" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
> +
> +struct samsung_galaxybook {
> +	struct platform_device *platform;
> +	struct acpi_device *acpi;
> +
> +	struct device *fw_attrs_dev;
> +	struct kset *fw_attrs_kset;
> +	struct kobj_attribute power_on_lid_open_attr;
> +	struct kobj_attribute usb_charging_attr;
> +	struct kobj_attribute camera_lens_cover_attr;
> +
> +	bool has_kbd_backlight;
> +	bool has_camera_lens_cover;
> +	bool has_performance_mode;
> +
> +	struct led_classdev kbd_backlight;
> +	/* block out of sync condition in hotkey action if brightness updated in another thread */
> +	struct mutex kbd_backlight_lock;
> +	struct work_struct kbd_backlight_hotkey_work;
> +
> +	struct input_dev *input;
> +	/* protect sparse keymap event reporting getting out of sync from multiple threads */
> +	struct mutex input_lock;
> +	void *i8042_filter_ptr;
> +
> +	/* block out of sync condition in hotkey action if value updated in another thread */
> +	struct mutex camera_lens_cover_lock;
> +	struct work_struct camera_lens_cover_hotkey_work;
> +
> +	struct acpi_battery_hook battery_hook;
> +	struct device_attribute charge_control_end_threshold_attr;
> +
> +	u8 profile_performance_modes[PLATFORM_PROFILE_LAST];
> +	struct platform_profile_handler profile_handler;
> +};
> +
> +static struct samsung_galaxybook *galaxybook_ptr;
> +static const struct class *fw_attr_class;
> +
> +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 SAWB_LEN_SETTINGS         0x15
> +#define SAWB_LEN_PERFORMANCE_MODE 0x100
> +
> +#define SAFN  0x5843
> +
> +#define SASB_KBD_BACKLIGHT      0x78
> +#define SASB_POWER_MANAGEMENT   0x7a
> +#define SASB_USB_CHARGING_GET   0x67
> +#define SASB_USB_CHARGING_SET   0x68
> +#define SASB_NOTIFICATIONS      0x86
> +#define SASB_CAMERA_LENS_COVER  0x8a
> +#define SASB_PERFORMANCE_MODE   0x91
> +
> +#define SAWB_RFLG_POS  4
> +#define SAWB_GUNM_POS  5
> +
> +#define RFLG_SUCCESS  0xaa
> +#define GUNM_FAIL     0xff
> +
> +#define GUNM_FEATURE_ENABLE          0xbb
> +#define GUNM_FEATURE_ENABLE_SUCCESS  0xdd
> +#define GUDS_FEATURE_ENABLE          0xaa
> +#define GUDS_FEATURE_ENABLE_SUCCESS  0xcc
> +
> +#define GUNM_GET  0x81
> +#define GUNM_SET  0x82
> +
> +#define GUNM_POWER_MANAGEMENT  0x82
> +
> +#define GUNM_USB_CHARGING_GET            0x80
> +#define GUNM_USB_CHARGING_ON             0x81
> +#define GUNM_USB_CHARGING_OFF            0x80
> +#define GUDS_POWER_ON_LID_OPEN           0xa3
> +#define GUDS_POWER_ON_LID_OPEN_GET       0x81
> +#define GUDS_POWER_ON_LID_OPEN_SET       0x80
> +#define GUDS_BATTERY_CHARGE_CONTROL      0xe9
> +#define GUDS_BATTERY_CHARGE_CONTROL_GET  0x91
> +#define GUDS_BATTERY_CHARGE_CONTROL_SET  0x90
> +#define GUNM_ACPI_NOTIFY_ENABLE          0x80
> +#define GUDS_ACPI_NOTIFY_ENABLE          0x02
> +
> +#define GB_CAMERA_LENS_COVER_ON   0x0
> +#define GB_CAMERA_LENS_COVER_OFF  0x1
> +
> +#define FNCN_PERFORMANCE_MODE       0x51
> +#define SUBN_PERFORMANCE_MODE_LIST  0x01
> +#define SUBN_PERFORMANCE_MODE_GET   0x02
> +#define SUBN_PERFORMANCE_MODE_SET   0x03
> +
> +/* guid 8246028d-8bca-4a55-ba0f-6f1e6b921b8f */
> +static const guid_t performance_mode_guid_value =
> +	GUID_INIT(0x8246028d, 0x8bca, 0x4a55, 0xba, 0x0f, 0x6f, 0x1e, 0x6b, 0x92, 0x1b, 0x8f);
> +#define PERFORMANCE_MODE_GUID performance_mode_guid_value
> +
> +#define PERFORMANCE_MODE_ULTRA               0x16
> +#define PERFORMANCE_MODE_PERFORMANCE         0x15
> +#define PERFORMANCE_MODE_SILENT              0xb
> +#define PERFORMANCE_MODE_QUIET               0xa
> +#define PERFORMANCE_MODE_OPTIMIZED           0x2
> +#define PERFORMANCE_MODE_PERFORMANCE_LEGACY  0x1
> +#define PERFORMANCE_MODE_OPTIMIZED_LEGACY    0x0
> +#define PERFORMANCE_MODE_UNKNOWN             0xff
> +
> +#define DEFAULT_PLATFORM_PROFILE PLATFORM_PROFILE_BALANCED
> +
> +#define ACPI_METHOD_ENABLE            "SDLS"
> +#define ACPI_METHOD_ENABLE_ON         1
> +#define ACPI_METHOD_ENABLE_OFF        0
> +#define ACPI_METHOD_SETTINGS          "CSFI"
> +#define ACPI_METHOD_PERFORMANCE_MODE  "CSXI"
> +
> +#define KBD_BACKLIGHT_MAX_BRIGHTNESS  3
> +
> +#define ACPI_NOTIFY_BATTERY_STATE_CHANGED    0x61
> +#define ACPI_NOTIFY_DEVICE_ON_TABLE          0x6c
> +#define ACPI_NOTIFY_DEVICE_OFF_TABLE         0x6d
> +#define ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE  0x70
> +
> +#define GB_KEY_KBD_BACKLIGHT_KEYDOWN      0x2c
> +#define GB_KEY_KBD_BACKLIGHT_KEYUP        0xac
> +#define GB_KEY_CAMERA_LENS_COVER_KEYDOWN  0x1f
> +#define GB_KEY_CAMERA_LENS_COVER_KEYUP    0x9f
> +#define GB_KEY_BATTERY_NOTIFY_KEYUP       0xf
> +#define GB_KEY_BATTERY_NOTIFY_KEYDOWN     0x8f
> +
> +#define INPUT_CAMERA_LENS_COVER_ON   0x01
> +#define INPUT_CAMERA_LENS_COVER_OFF  0x02
> +
> +static const struct key_entry galaxybook_acpi_keymap[] = {
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_ON,  { .sw = { SW_CAMERA_LENS_COVER, 1 } } },
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_OFF, { .sw = { SW_CAMERA_LENS_COVER, 0 } } },
> +	{ KE_END, 0 },
> +};
> +
> +/*
> + * ACPI method handling
> + */
> +
> +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> +				  struct sawb *in_buf, size_t len, struct sawb *out_buf)
> +{
> +	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 *)in_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 < SAWB_GUNM_POS + 1) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"response length mismatch\n", method);
> +		err = -EPROTO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"device did not respond with success code 0x%x\n",
> +			method, RFLG_SUCCESS);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> +		dev_err(&galaxybook->acpi->dev,
> +			"failed to execute method %s; device responded with failure code 0x%x\n",
> +			method, GUNM_FAIL);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +
> +	memcpy(out_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 = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = sasb;
> +	buf.gunm = GUNM_FEATURE_ENABLE;
> +	buf.guds[0] = GUDS_FEATURE_ENABLE;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	if (buf.gunm != GUNM_FEATURE_ENABLE_SUCCESS && buf.guds[0] != 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_SET;
> +
> +	buf.guds[0] = brightness;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook,
> +				  enum led_brightness *brightness)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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, SASB_KBD_BACKLIGHT);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* verify we can read the value, otherwise stop without setting has_kbd_backlight */
> +	err = kbd_backlight_acpi_get(galaxybook, &brightness);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	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 = KBD_BACKLIGHT_MAX_BRIGHTNESS;
> +
> +	err = devm_led_classdev_register_ext(&galaxybook->platform->dev,
> +					     &galaxybook->kbd_backlight, &init_data);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_kbd_backlight = true;
> +
> +	return 0;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize kbd_backlight, error %d\n", err);
> +	return 0;

Return `err` here.

> +}
> +
> +/*
> + * Platform device attributes (configuration properties which can be controlled via userspace)
> + */
> +
> +/* 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> +	buf.guds[2] = value ? 1 : 0;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int power_on_lid_open_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.guds[1];
> +
> +	return 0;
> +}
> +
> +static ssize_t power_on_lid_open_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t power_on_lid_open_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +	bool value;
> +	int err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +/* USB Charging (USB ports can charge other devices even when device is powered off) */
> +
> +static int usb_charging_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_SET;
> +	buf.gunm = value ? GUNM_USB_CHARGING_ON : GUNM_USB_CHARGING_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int usb_charging_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_GET;
> +	buf.gunm = GUNM_USB_CHARGING_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == 1;
> +
> +	return 0;
> +}
> +
> +static ssize_t usb_charging_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				  const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = usb_charging_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t usb_charging_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +/* Camera lens cover (blocks access to camera and microphone) */
> +
> +static int camera_lens_cover_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_SET;
> +	buf.guds[0] = value ? GB_CAMERA_LENS_COVER_ON : GB_CAMERA_LENS_COVER_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int camera_lens_cover_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == GB_CAMERA_LENS_COVER_ON;
> +
> +	return 0;
> +}
> +
> +static ssize_t camera_lens_cover_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	mutex_lock(&galaxybook->camera_lens_cover_lock);
> +	err = camera_lens_cover_acpi_set(galaxybook, value);
> +	mutex_unlock(&galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t camera_lens_cover_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +static int galaxybook_camera_lens_cover_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_CAMERA_LENS_COVER);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"failed to initialize camera lens cover feature, error %d\n", err);
> +		return 0;
> +	}
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->has_camera_lens_cover = true;
> +
> +	return 0;
> +}
> +
> +/* Attribute setup */
> +
> +static void galaxybook_power_on_lid_open_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->power_on_lid_open_attr.attr);
> +}
> +
> +static void galaxybook_usb_charging_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->usb_charging_attr.attr);
> +}
> +
> +static void galaxybook_camera_lens_cover_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->camera_lens_cover_attr.attr);
> +}
> +
> +static void galaxybook_fw_attrs_kset_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	kset_unregister(galaxybook->fw_attrs_kset);
> +}
> +
> +static void galaxybook_fw_attr_class_remove(void *data)
> +{
> +	device_destroy(fw_attr_class, MKDEV(0, 0));
> +	fw_attributes_class_put();
> +}
> +
> +static int galaxybook_fw_attrs_init(struct samsung_galaxybook *galaxybook)
> +{
> +	bool value;
> +	int err;
> +
> +	err = fw_attributes_class_get(&fw_attr_class);
> +	if (err)
> +		return err;
> +
> +	galaxybook->fw_attrs_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> +						 NULL, "%s", DRIVER_NAME);
> +	if (IS_ERR(galaxybook->fw_attrs_dev)) {
> +		fw_attributes_class_put();
> +		err = PTR_ERR(galaxybook->fw_attrs_dev);
> +		return err;
> +	}
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_fw_attr_class_remove, NULL);
> +	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_fw_attrs_kset_remove, galaxybook);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->power_on_lid_open_attr);
> +		galaxybook->power_on_lid_open_attr.attr.name = "power_on_lid_open";
> +		galaxybook->power_on_lid_open_attr.attr.mode = 0644;
> +		galaxybook->power_on_lid_open_attr.show = power_on_lid_open_show;
> +		galaxybook->power_on_lid_open_attr.store = power_on_lid_open_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->power_on_lid_open_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_power_on_lid_open_attr_remove,
> +					       galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->usb_charging_attr);
> +		galaxybook->usb_charging_attr.attr.name = "usb_charging";
> +		galaxybook->usb_charging_attr.attr.mode = 0644;
> +		galaxybook->usb_charging_attr.show = usb_charging_show;
> +		galaxybook->usb_charging_attr.store = usb_charging_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->usb_charging_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_usb_charging_attr_remove, galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	if (!galaxybook->has_camera_lens_cover)
> +		return 0;
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		galaxybook->has_camera_lens_cover = false;
> +		return 0;
> +	}
> +
> +	sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
> +	galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
> +	galaxybook->camera_lens_cover_attr.attr.mode = 0644;
> +	galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
> +	galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
> +	err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +				&galaxybook->camera_lens_cover_attr.attr);
> +	if (err)
> +		return err;
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_camera_lens_cover_attr_remove, galaxybook);
> +}
> +
> +/*
> + * 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_SET;
> +	buf.guds[2] = value;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook *galaxybook, u8 *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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 *buffer, 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(buffer, 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 *buffer)
> +{
> +	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(buffer, "%d\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 void 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)
> +		goto return_with_dbg;
> +
> +	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;
> +
> +	err = devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize battery charge threshold, error %d\n", err);
> +}
> +
> +/*
> + * Platform Profile / Performance mode
> + */
> +
> +static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook,
> +				     const u8 performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_SET;
> +	buf.iob0 = performance_mode;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				      &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +}
> +
> +static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook, u8 *performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	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)
> +{
> +	for (int i = 0; i < PLATFORM_PROFILE_LAST; i++) {
> +		if (galaxybook->profile_performance_modes[i] == performance_mode) {
> +			if (profile)
> +				*profile = i;
> +			return 0;
> +		}
> +	}
> +
> +	return -ENODATA;
> +}
> +
> +static int galaxybook_platform_profile_set(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +
> +	return performance_mode_acpi_set(galaxybook,
> +					 galaxybook->profile_performance_modes[profile]);
> +}
> +
> +static int galaxybook_platform_profile_get(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option *profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +	u8 performance_mode;
> +	int err;
> +
> +	err = performance_mode_acpi_get(galaxybook, &performance_mode);
> +	if (err)
> +		return err;
> +
> +	return get_performance_mode_profile(galaxybook, performance_mode, profile);
> +}
> +
> +static void galaxybook_profile_exit(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	platform_profile_remove(&galaxybook->profile_handler);
> +}
> +
> +#define IGNORE_PERFORMANCE_MODE_MAPPING  -1
> +
> +static void galaxybook_profile_init(struct samsung_galaxybook *galaxybook)
> +{
> +	u8 current_performance_mode;
> +	u8 init_performance_mode;
> +	struct sawb buf = { 0 };
> +	int mapped_profiles;
> +	int mode_profile;
> +	int err;
> +	int i;
> +
> +	galaxybook->profile_handler.name = DRIVER_NAME;
> +	galaxybook->profile_handler.dev = &galaxybook->platform->dev;
> +	galaxybook->profile_handler.profile_get = galaxybook_platform_profile_get;
> +	galaxybook->profile_handler.profile_set = galaxybook_platform_profile_set;
> +
> +	/* fetch supported performance mode values from ACPI method */
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_LIST;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* set up profile_performance_modes with "unknown" as init value */
> +	for (i = 0; i < PLATFORM_PROFILE_LAST; i++)
> +		galaxybook->profile_performance_modes[i] = PERFORMANCE_MODE_UNKNOWN;
> +
> +	/*
> +	 * Value returned in iob0 will have the number of supported performance modes.
> +	 * The performance mode values will then be given as a list after this (iob1-iobX).
> +	 * Loop backwards from last value to first value (to handle fallback cases which come with
> +	 * smaller values) and map each supported value to its correct platform_profile_option.
> +	 */
> +	for (i = buf.iob0; i > 0; i--) {
> +		/*
> +		 * Prefer mapping to at least performance, balanced, and low-power profiles, as they
> +		 * are the profiles which are typically supported by userspace tools
> +		 * (power-profiles-daemon, etc).
> +		 * - performance = "ultra", otherwise "performance"
> +		 * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
> +		 * - low-power   = "silent", otherwise "quiet"
> +		 * Different models support different modes. Additional supported modes will be
> +		 * mapped to profiles that fall in between these 3.
> +		 */
> +		switch (buf.iob_values[i]) {
> +		case PERFORMANCE_MODE_ULTRA:
> +			/* ultra always maps to performance */
> +			mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE:
> +			/* if ultra exists, map performance to balanced-performance */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			else /* otherwise map it to performance instead */
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_SILENT:
> +			/* silent always maps to low-power */
> +			mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_QUIET:
> +			/* if silent exists, map quiet to quiet */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_LOW_POWER] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_QUIET;
> +			else /* otherwise map it to low-power for better userspace tool support */
> +				mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED:
> +			/* optimized always maps to balanced */
> +			mode_profile = PLATFORM_PROFILE_BALANCED;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE_LEGACY:
> +			/* map to performance if performance is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED_LEGACY:
> +			/* map to balanced if balanced is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_BALANCED] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		default: /* any other value is not supported */
> +			mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +		}
> +
> +		/* if current mode value mapped to a supported platform_profile_option, set it up */
> +		if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
> +			mapped_profiles++;

mapped_profiles is uninitialized!!

> +			galaxybook->profile_performance_modes[mode_profile] = buf.iob_values[i];
> +			set_bit(mode_profile, galaxybook->profile_handler.choices);
> +			if (mode_profile == DEFAULT_PLATFORM_PROFILE)
> +				init_performance_mode = buf.iob_values[i];
> +			dev_dbg(&galaxybook->platform->dev,
> +				"will support platform profile %d (performance mode 0x%x)\n",
> +				mode_profile, buf.iob_values[i]);
> +		} else {
> +			dev_dbg(&galaxybook->platform->dev,
> +				"unmapped performance mode 0x%x will be ignored\n",
> +				buf.iob_values[i]);
> +		}
> +	}
> +
> +	if (mapped_profiles == 0) {
> +		err = -ENODEV;
> +		goto return_with_dbg;
> +	}
> +
> +	/* now check currently set performance mode; if not supported then set default mode */
> +	err = performance_mode_acpi_get(galaxybook, &current_performance_mode);
> +	if (err)
> +		goto return_with_dbg;
> +	err = get_performance_mode_profile(galaxybook, current_performance_mode, NULL);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"initial performance mode value is not supported by device; "
> +			"setting to default\n");
> +		err = performance_mode_acpi_set(galaxybook, init_performance_mode);
> +		if (err)
> +			goto return_with_dbg;
> +	}
> +
> +	err = platform_profile_register(&galaxybook->profile_handler);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_profile_exit, galaxybook);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_performance_mode = true;
> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize platform profile, error %d\n", err);
> +}
> +
> +/*
> + * Hotkeys and notifications
> + */
> +
> +static void galaxybook_input_notify(struct samsung_galaxybook *galaxybook, int event)
> +{
> +	if (!galaxybook->input)
> +		return;
> +	mutex_lock(&galaxybook->input_lock);
> +	if (!sparse_keymap_report_event(galaxybook->input, event, 1, true))
> +		dev_warn(&galaxybook->acpi->dev, "unknown input notification event: 0x%x\n", event);
> +	mutex_unlock(&galaxybook->input_lock);
> +}
> +
> +static int galaxybook_input_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->input_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev);
> +	if (!galaxybook->input)
> +		return -ENOMEM;
> +
> +	galaxybook->input->name = "Samsung Galaxy Book Extra Buttons";
> +	galaxybook->input->phys = DRIVER_NAME "/input0";
> +	galaxybook->input->id.bustype = BUS_HOST;
> +	galaxybook->input->dev.parent = &galaxybook->platform->dev;
> +
> +	err = sparse_keymap_setup(galaxybook->input, galaxybook_acpi_keymap, NULL);
> +	if (err)
> +		return err;
> +
> +	return input_register_device(galaxybook->input);
> +}
> +
> +static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, kbd_backlight_hotkey_work);
> +	int new_brightness;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->kbd_backlight_lock);
> +
> +	if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)
> +		new_brightness = galaxybook->kbd_backlight.brightness + 1;
> +	else
> +		new_brightness = 0;
> +
> +	err = led_set_brightness_sync(&galaxybook->kbd_backlight, new_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, new_brightness);
> +}
> +
> +static void galaxybook_camera_lens_cover_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, camera_lens_cover_hotkey_work);
> +	bool value;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->camera_lens_cover_lock);
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to get camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	err = camera_lens_cover_acpi_set(galaxybook, !value);
> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to set camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	galaxybook_input_notify(galaxybook,
> +				!value ? INPUT_CAMERA_LENS_COVER_ON : INPUT_CAMERA_LENS_COVER_OFF);
> +}
> +
> +static bool galaxybook_i8042_filter(unsigned char data, unsigned char str, struct serio *port)
> +{
> +	static bool extended;
> +
> +	if (str & I8042_STR_AUXDATA)
> +		return false;
> +
> +	if (data == 0xe0) {
> +		extended = true;
> +		return true;
> +	} else if (extended) {
> +		extended = false;
> +		switch (data) {
> +		case GB_KEY_KBD_BACKLIGHT_KEYDOWN:
> +			return true;
> +		case GB_KEY_KBD_BACKLIGHT_KEYUP:
> +			if (galaxybook_ptr->has_kbd_backlight)
> +				schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work);
> +			return true;
> +
> +		case GB_KEY_CAMERA_LENS_COVER_KEYDOWN:
> +			return true;
> +		case GB_KEY_CAMERA_LENS_COVER_KEYUP:
> +			if (galaxybook_ptr->has_camera_lens_cover)
> +				schedule_work(&galaxybook_ptr->camera_lens_cover_hotkey_work);
> +			return true;
> +
> +		/* battery notification already sent to battery and ACPI device; ignore */
> +		case GB_KEY_BATTERY_NOTIFY_KEYUP:
> +		case GB_KEY_BATTERY_NOTIFY_KEYDOWN:
> +			return true;
> +
> +		default:
> +			/*
> +			 * Report the previously filtered e0 before continuing
> +			 * with the next non-filtered byte.
> +			 */
> +			serio_interrupt(port, 0xe0, 0);
> +			return false;
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +static void galaxybook_i8042_filter_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	i8042_remove_filter(galaxybook_i8042_filter);
> +	if (galaxybook->has_kbd_backlight)
> +		cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
> +	if (galaxybook->has_camera_lens_cover)
> +		cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
> +}
> +
> +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
> +		return 0;
> +
> +	if (galaxybook->has_kbd_backlight)
> +		INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
> +			  galaxybook_kbd_backlight_hotkey_work);
> +
> +	if (galaxybook->has_camera_lens_cover)
> +		INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
> +			  galaxybook_camera_lens_cover_hotkey_work);
> +
> +	err = i8042_install_filter(galaxybook_i8042_filter);
> +	if (err)
> +		return err;
> +
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_i8042_filter_remove, galaxybook);
> +}
> +
> +/*
> + * ACPI device setup
> + */
> +
> +static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	switch (event) {
> +	case ACPI_NOTIFY_BATTERY_STATE_CHANGED:
> +	case ACPI_NOTIFY_DEVICE_ON_TABLE:
> +	case ACPI_NOTIFY_DEVICE_OFF_TABLE:
> +		break;
> +	case ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE:
> +		if (galaxybook->has_performance_mode)
> +			platform_profile_cycle();
> +		break;
> +	default:
> +		dev_warn(&galaxybook->acpi->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 = { 0 };
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_NOTIFICATIONS);
> +	if (err)
> +		return err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_NOTIFICATIONS;
> +	buf.gunm = GUNM_ACPI_NOTIFY_ENABLE;
> +	buf.guds[0] = GUDS_ACPI_NOTIFY_ENABLE;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +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,
> +				   ACPI_METHOD_ENABLE, 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, ACPI_METHOD_ENABLE,
> +					    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_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
> +			 "some hotkeys will not be supported\n");
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_POWER_MANAGEMENT);
> +	if (err)
> +		dev_warn(&galaxybook->acpi->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;
> +
> +	/* set static pointer here so it can be used in i8042 filter */
> +	if (galaxybook_ptr)
> +		return -EBUSY;
> +	galaxybook_ptr = galaxybook;
> +
> +	galaxybook->platform = pdev;
> +	galaxybook->acpi = adev;
> +
> +	dev_set_drvdata(&galaxybook->platform->dev, galaxybook);
> +
> +	err = galaxybook_input_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->platform->dev, err,
> +				     "failed to initialize input device\n");
> +
> +	err = galaxybook_acpi_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize ACPI device\n");
> +
> +	galaxybook_profile_init(galaxybook);
> +	galaxybook_battery_threshold_init(galaxybook);
> +
> +	err = galaxybook_camera_lens_cover_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize camera_lens_cover\n");
> +
> +	err = galaxybook_kbd_backlight_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->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 void galaxybook_remove(struct platform_device *pdev)
> +{
> +	if (galaxybook_ptr)
> +		galaxybook_ptr = NULL;

Please someone correct me if I'm wrong.

Device resources get released after calling the .remove callback,
therefore there is a small window in which the i8042 filter is *still*
installed after this point, which means you could dereference a NULL
pointer.

I suggest not using devres for the i8042 filter.

~ Kurt

> +}
> +
> +static struct platform_driver galaxybook_platform_driver = {
> +	.driver = {
> +		.name = DRIVER_NAME,
> +		.acpi_match_table = galaxybook_device_ids,
> +	},
> +	.probe = galaxybook_probe,
> +	.remove = galaxybook_remove,
> +};
> +module_platform_driver(galaxybook_platform_driver);
> +
> +MODULE_AUTHOR("Joshua Grisham <josh@joshuagrisham.com>");
> +MODULE_DESCRIPTION("Samsung Galaxy Book Extras");
> +MODULE_LICENSE("GPL");
Armin Wolf Jan. 2, 2025, 7:13 p.m. UTC | #2
Am 26.12.24 um 16:30 schrieb Joshua Grisham:

> Adds 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
> ---
>   .../testing/sysfs-class-firmware-attributes   |   28 +
>   Documentation/admin-guide/laptops/index.rst   |    1 +
>   .../laptops/samsung-galaxybook.rst            |  165 ++
>   MAINTAINERS                                   |    7 +
>   drivers/platform/x86/Kconfig                  |   18 +
>   drivers/platform/x86/Makefile                 |    5 +-
>   drivers/platform/x86/samsung-galaxybook.c     | 1493 +++++++++++++++++
>   7 files changed, 1715 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/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> index 2713efa509b4..dd36577b68f2 100644
> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> @@ -326,6 +326,17 @@ Description:
>   					This role is specific to Secure Platform Management (SPM) attribute.
>   					It requires configuring an endorsement (kek) and signing certificate (sk).
>
> +What:		/sys/class/firmware-attributes/*/attributes/camera_lens_cover
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control the behavior of a software-based camera lens
> +		cover. The value is a boolean represented by 0 for false (camera is not blocked)
> +		and 1 for true (camera is blocked).
> +
> +		On Samsung Galaxy Book systems, this attribute will also control a software-based
> +		"cover" of the microphone in addition to the camera.
>
>   What:		/sys/class/firmware-attributes/*/attributes/pending_reboot
>   Date:		February 2021
> @@ -356,6 +367,14 @@ Description:
>   		Drivers may emit a CHANGE uevent when this value changes and userspace
>   		may check it again.
>
> +What:		/sys/class/firmware-attributes/*/attributes/power_on_lid_open
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control powering on a device when the lid is opened.
> +		The value is a boolean represented by 0 for false and 1 for true.
> +
>   What:		/sys/class/firmware-attributes/*/attributes/reset_bios
>   Date:		February 2021
>   KernelVersion:	5.11
> @@ -429,6 +448,15 @@ Description:
>   		HP specific class extensions - Secure Platform Manager (SPM)
>   		--------------------------------
>
> +What:		/sys/class/firmware-attributes/*/attributes/usb_charging
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control if USB ports can continue to deliver power to
> +		connected devices when the device is powered off or in a low sleep state. The value
> +		is a boolean represented by 0 for false and 1 for true.

Hi,

please move the documentation of the firmware attributes to samsung-galaxybook.rst to avoid cluttering
the subsystem docs with too much driver-specific entries.

> +
>   What:		/sys/class/firmware-attributes/*/authentication/SPM/kek
>   Date:		March 2023
>   KernelVersion:	5.18
> 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..65da7cd84c01
> --- /dev/null
> +++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst
> @@ -0,0 +1,165 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +==========================
> +Samsung Galaxy Book Extras
> +==========================
> +
> +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 device extension
> +- :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
> +done dynamically based on a list of the supported modes reported by the device
> +itself. Preference is given to always try and map ``low-power``, ``balanced``,
> +and ``performance`` profiles, as these seem to be the most common profiles
> +utilized (and sometimes even required) by various userspace tools.
> +
> +The result of the mapping will 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 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:
> +
> +- ``camera_lens_cover``
> +- ``power_on_lid_open``
> +- ``usb_charging``
> +
> +These attributes are documented in more detail under
> +Documentation/admin-guide/abi.rst.
> +
> +.. _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 "camera lens cover" setting, which blocks
> +or allows usage of the built-in camera and microphone.
> +
> +There is a new "Samsung Galaxy Book Extra Buttons" input device created which
> +will send input events for the following notifications:
> +
> +- Switch ``SW_CAMERA_LENS_COVER`` (on or off) when the camera and microphone are
> +  "blocked" or "allowed" when toggling the Camera Lens Cover setting.
> +
> +.. _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..e74873a1e74b 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 EXTRAS 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..ecc509f5df55 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -778,6 +778,24 @@ 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 extras 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
> +	select INPUT_SPARSEKMAP
> +	help
> +	  This is a driver for Samsung Galaxy Book series notebooks. It adds
> +	  support for the keyboard backlight control, performance mode control, fan
> +	  speed reporting, function keys, and various other device controls.
> +
> +	  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..c656471dd1c7
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-galaxybook.c
> @@ -0,0 +1,1493 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Samsung Galaxy Book series extras driver
> + *
> + * Copyright (c) 2024 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/err.h>
> +#include <linux/i8042.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.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"
> +
> +static const struct acpi_device_id galaxybook_device_ids[] = {
> +	{ "SAM0427" },
> +	{ "SAM0428" },
> +	{ "SAM0429" },
> +	{ "SAM0430" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);

Can you move this device ID table closer to the platform driver?

> +
> +struct samsung_galaxybook {
> +	struct platform_device *platform;
> +	struct acpi_device *acpi;
> +
> +	struct device *fw_attrs_dev;
> +	struct kset *fw_attrs_kset;
> +	struct kobj_attribute power_on_lid_open_attr;
> +	struct kobj_attribute usb_charging_attr;
> +	struct kobj_attribute camera_lens_cover_attr;
> +
> +	bool has_kbd_backlight;
> +	bool has_camera_lens_cover;
> +	bool has_performance_mode;
> +
> +	struct led_classdev kbd_backlight;
> +	/* block out of sync condition in hotkey action if brightness updated in another thread */
> +	struct mutex kbd_backlight_lock;
> +	struct work_struct kbd_backlight_hotkey_work;
> +
> +	struct input_dev *input;
> +	/* protect sparse keymap event reporting getting out of sync from multiple threads */
> +	struct mutex input_lock;
> +	void *i8042_filter_ptr;
> +
> +	/* block out of sync condition in hotkey action if value updated in another thread */
> +	struct mutex camera_lens_cover_lock;
> +	struct work_struct camera_lens_cover_hotkey_work;
> +
> +	struct acpi_battery_hook battery_hook;
> +	struct device_attribute charge_control_end_threshold_attr;
> +
> +	u8 profile_performance_modes[PLATFORM_PROFILE_LAST];
> +	struct platform_profile_handler profile_handler;
> +};
> +
> +static struct samsung_galaxybook *galaxybook_ptr;
> +static const struct class *fw_attr_class;
> +
> +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 SAWB_LEN_SETTINGS         0x15
> +#define SAWB_LEN_PERFORMANCE_MODE 0x100
> +
> +#define SAFN  0x5843
> +
> +#define SASB_KBD_BACKLIGHT      0x78
> +#define SASB_POWER_MANAGEMENT   0x7a
> +#define SASB_USB_CHARGING_GET   0x67
> +#define SASB_USB_CHARGING_SET   0x68
> +#define SASB_NOTIFICATIONS      0x86
> +#define SASB_CAMERA_LENS_COVER  0x8a
> +#define SASB_PERFORMANCE_MODE   0x91
> +
> +#define SAWB_RFLG_POS  4
> +#define SAWB_GUNM_POS  5
> +
> +#define RFLG_SUCCESS  0xaa
> +#define GUNM_FAIL     0xff
> +
> +#define GUNM_FEATURE_ENABLE          0xbb
> +#define GUNM_FEATURE_ENABLE_SUCCESS  0xdd
> +#define GUDS_FEATURE_ENABLE          0xaa
> +#define GUDS_FEATURE_ENABLE_SUCCESS  0xcc
> +
> +#define GUNM_GET  0x81
> +#define GUNM_SET  0x82
> +
> +#define GUNM_POWER_MANAGEMENT  0x82
> +
> +#define GUNM_USB_CHARGING_GET            0x80
> +#define GUNM_USB_CHARGING_ON             0x81
> +#define GUNM_USB_CHARGING_OFF            0x80
> +#define GUDS_POWER_ON_LID_OPEN           0xa3
> +#define GUDS_POWER_ON_LID_OPEN_GET       0x81
> +#define GUDS_POWER_ON_LID_OPEN_SET       0x80
> +#define GUDS_BATTERY_CHARGE_CONTROL      0xe9
> +#define GUDS_BATTERY_CHARGE_CONTROL_GET  0x91
> +#define GUDS_BATTERY_CHARGE_CONTROL_SET  0x90
> +#define GUNM_ACPI_NOTIFY_ENABLE          0x80
> +#define GUDS_ACPI_NOTIFY_ENABLE          0x02
> +
> +#define GB_CAMERA_LENS_COVER_ON   0x0
> +#define GB_CAMERA_LENS_COVER_OFF  0x1
> +
> +#define FNCN_PERFORMANCE_MODE       0x51
> +#define SUBN_PERFORMANCE_MODE_LIST  0x01
> +#define SUBN_PERFORMANCE_MODE_GET   0x02
> +#define SUBN_PERFORMANCE_MODE_SET   0x03
> +
> +/* guid 8246028d-8bca-4a55-ba0f-6f1e6b921b8f */
> +static const guid_t performance_mode_guid_value =
> +	GUID_INIT(0x8246028d, 0x8bca, 0x4a55, 0xba, 0x0f, 0x6f, 0x1e, 0x6b, 0x92, 0x1b, 0x8f);
> +#define PERFORMANCE_MODE_GUID performance_mode_guid_value
> +
> +#define PERFORMANCE_MODE_ULTRA               0x16
> +#define PERFORMANCE_MODE_PERFORMANCE         0x15
> +#define PERFORMANCE_MODE_SILENT              0xb
> +#define PERFORMANCE_MODE_QUIET               0xa
> +#define PERFORMANCE_MODE_OPTIMIZED           0x2
> +#define PERFORMANCE_MODE_PERFORMANCE_LEGACY  0x1
> +#define PERFORMANCE_MODE_OPTIMIZED_LEGACY    0x0
> +#define PERFORMANCE_MODE_UNKNOWN             0xff
> +
> +#define DEFAULT_PLATFORM_PROFILE PLATFORM_PROFILE_BALANCED
> +
> +#define ACPI_METHOD_ENABLE            "SDLS"
> +#define ACPI_METHOD_ENABLE_ON         1
> +#define ACPI_METHOD_ENABLE_OFF        0
> +#define ACPI_METHOD_SETTINGS          "CSFI"
> +#define ACPI_METHOD_PERFORMANCE_MODE  "CSXI"
> +
> +#define KBD_BACKLIGHT_MAX_BRIGHTNESS  3
> +
> +#define ACPI_NOTIFY_BATTERY_STATE_CHANGED    0x61
> +#define ACPI_NOTIFY_DEVICE_ON_TABLE          0x6c
> +#define ACPI_NOTIFY_DEVICE_OFF_TABLE         0x6d
> +#define ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE  0x70
> +
> +#define GB_KEY_KBD_BACKLIGHT_KEYDOWN      0x2c
> +#define GB_KEY_KBD_BACKLIGHT_KEYUP        0xac
> +#define GB_KEY_CAMERA_LENS_COVER_KEYDOWN  0x1f
> +#define GB_KEY_CAMERA_LENS_COVER_KEYUP    0x9f
> +#define GB_KEY_BATTERY_NOTIFY_KEYUP       0xf
> +#define GB_KEY_BATTERY_NOTIFY_KEYDOWN     0x8f
> +
> +#define INPUT_CAMERA_LENS_COVER_ON   0x01
> +#define INPUT_CAMERA_LENS_COVER_OFF  0x02
> +
> +static const struct key_entry galaxybook_acpi_keymap[] = {
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_ON,  { .sw = { SW_CAMERA_LENS_COVER, 1 } } },
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_OFF, { .sw = { SW_CAMERA_LENS_COVER, 0 } } },
> +	{ KE_END, 0 },
> +};
> +
> +/*
> + * ACPI method handling
> + */
> +
> +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> +				  struct sawb *in_buf, size_t len, struct sawb *out_buf)
> +{
> +	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 *)in_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 < SAWB_GUNM_POS + 1) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"response length mismatch\n", method);
> +		err = -EPROTO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"device did not respond with success code 0x%x\n",
> +			method, RFLG_SUCCESS);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> +		dev_err(&galaxybook->acpi->dev,
> +			"failed to execute method %s; device responded with failure code 0x%x\n",
> +			method, GUNM_FAIL);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +
> +	memcpy(out_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 = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = sasb;
> +	buf.gunm = GUNM_FEATURE_ENABLE;
> +	buf.guds[0] = GUDS_FEATURE_ENABLE;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	if (buf.gunm != GUNM_FEATURE_ENABLE_SUCCESS && buf.guds[0] != 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_SET;
> +
> +	buf.guds[0] = brightness;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook,
> +				  enum led_brightness *brightness)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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, SASB_KBD_BACKLIGHT);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* verify we can read the value, otherwise stop without setting has_kbd_backlight */
> +	err = kbd_backlight_acpi_get(galaxybook, &brightness);
> +	if (err)
> +		goto return_with_dbg;

Reusing the same debug message for multiple error sources seems useless to me, please user
different debug messages for each error source or remove some debug messages. You can also
print the debug message inside the caller of galaxybook_kbd_backlight_init().

> +
> +	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 = KBD_BACKLIGHT_MAX_BRIGHTNESS;
> +
> +	err = devm_led_classdev_register_ext(&galaxybook->platform->dev,
> +					     &galaxybook->kbd_backlight, &init_data);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_kbd_backlight = true;
> +
> +	return 0;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize kbd_backlight, error %d\n", err);
> +	return 0;
> +}
> +
> +/*
> + * Platform device attributes (configuration properties which can be controlled via userspace)
> + */
> +
> +/* 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> +	buf.guds[2] = value ? 1 : 0;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int power_on_lid_open_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.guds[1];
> +
> +	return 0;
> +}
> +
> +static ssize_t power_on_lid_open_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t power_on_lid_open_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +	bool value;
> +	int err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +/* USB Charging (USB ports can charge other devices even when device is powered off) */
> +
> +static int usb_charging_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_SET;
> +	buf.gunm = value ? GUNM_USB_CHARGING_ON : GUNM_USB_CHARGING_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int usb_charging_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_GET;
> +	buf.gunm = GUNM_USB_CHARGING_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == 1;
> +
> +	return 0;
> +}
> +
> +static ssize_t usb_charging_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				  const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = usb_charging_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t usb_charging_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);

Please use %d here.

> +}
> +
> +/* Camera lens cover (blocks access to camera and microphone) */
> +
> +static int camera_lens_cover_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_SET;
> +	buf.guds[0] = value ? GB_CAMERA_LENS_COVER_ON : GB_CAMERA_LENS_COVER_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int camera_lens_cover_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == GB_CAMERA_LENS_COVER_ON;
> +
> +	return 0;
> +}
> +
> +static ssize_t camera_lens_cover_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	mutex_lock(&galaxybook->camera_lens_cover_lock);
> +	err = camera_lens_cover_acpi_set(galaxybook, value);
> +	mutex_unlock(&galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t camera_lens_cover_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +static int galaxybook_camera_lens_cover_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_CAMERA_LENS_COVER);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"failed to initialize camera lens cover feature, error %d\n", err);
> +		return 0;
> +	}
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->has_camera_lens_cover = true;
> +
> +	return 0;
> +}
> +
> +/* Attribute setup */
> +
> +static void galaxybook_power_on_lid_open_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->power_on_lid_open_attr.attr);
> +}
> +
> +static void galaxybook_usb_charging_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->usb_charging_attr.attr);
> +}
> +
> +static void galaxybook_camera_lens_cover_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->camera_lens_cover_attr.attr);
> +}
> +
> +static void galaxybook_fw_attrs_kset_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	kset_unregister(galaxybook->fw_attrs_kset);
> +}
> +
> +static void galaxybook_fw_attr_class_remove(void *data)
> +{
> +	device_destroy(fw_attr_class, MKDEV(0, 0));

Please use device_unregister() instead since multiple devices might share the same devt of MKDEV(0, 0).
This would also allow you to remove the global variable "fw_attr_class".

> +	fw_attributes_class_put();
> +}
> +
> +static int galaxybook_fw_attrs_init(struct samsung_galaxybook *galaxybook)
> +{
> +	bool value;
> +	int err;
> +
> +	err = fw_attributes_class_get(&fw_attr_class);
> +	if (err)
> +		return err;
> +
> +	galaxybook->fw_attrs_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> +						 NULL, "%s", DRIVER_NAME);
> +	if (IS_ERR(galaxybook->fw_attrs_dev)) {
> +		fw_attributes_class_put();
> +		err = PTR_ERR(galaxybook->fw_attrs_dev);
> +		return err;
> +	}
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_fw_attr_class_remove, NULL);
> +	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_fw_attrs_kset_remove, galaxybook);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->power_on_lid_open_attr);
> +		galaxybook->power_on_lid_open_attr.attr.name = "power_on_lid_open";
> +		galaxybook->power_on_lid_open_attr.attr.mode = 0644;
> +		galaxybook->power_on_lid_open_attr.show = power_on_lid_open_show;
> +		galaxybook->power_on_lid_open_attr.store = power_on_lid_open_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->power_on_lid_open_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_power_on_lid_open_attr_remove,
> +					       galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->usb_charging_attr);
> +		galaxybook->usb_charging_attr.attr.name = "usb_charging";
> +		galaxybook->usb_charging_attr.attr.mode = 0644;
> +		galaxybook->usb_charging_attr.show = usb_charging_show;
> +		galaxybook->usb_charging_attr.store = usb_charging_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->usb_charging_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_usb_charging_attr_remove, galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	if (!galaxybook->has_camera_lens_cover)
> +		return 0;
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		galaxybook->has_camera_lens_cover = false;
> +		return 0;
> +	}
> +
> +	sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
> +	galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
> +	galaxybook->camera_lens_cover_attr.attr.mode = 0644;
> +	galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
> +	galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
> +	err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +				&galaxybook->camera_lens_cover_attr.attr);
> +	if (err)
> +		return err;
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_camera_lens_cover_attr_remove, galaxybook);

That is not how the firmware attribute interface is supposed to work. For each firmware attribute you need to
create an attribute group (with a unique name of course) with the following attributes:

- type: should return "enumeration"
- current_value: should return the current value of the firmware attribute
- default_value: should return the default value of the firmware attribute
- display_name: should contain a user friendly description of the firmware attribute
- display_name_language_code: should return "en"
- possible_values: should return "0;1" since this firmware attributes are boolean values

You can theoretically use sysfs_create_groups() to add all groups in one go to simplify error handling. Since each
attribute_group specifies a .is_visible callback you can handle the visibility of each group there.

Those groups then need to be added to the fw_attrs_kset.

Just a small question: is the value of the camera lens cover persistent across reboots?

> +}
> +
> +/*
> + * 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_SET;
> +	buf.guds[2] = value;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook *galaxybook, u8 *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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 *buffer, 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(buffer, 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 *buffer)
> +{
> +	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(buffer, "%d\n", value);

Please use %u here.

> +}
> +
> +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 void 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)
> +		goto return_with_dbg;
> +
> +	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;
> +
> +	err = devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook);
> +	if (err)
> +		goto return_with_dbg;

Please return and error here if the battery hook registration fails.

> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize battery charge threshold, error %d\n", err);

Again: using the same error message for multiple error sources makes little sense.

> +}
> +
> +/*
> + * Platform Profile / Performance mode
> + */
> +
> +static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook,
> +				     const u8 performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_SET;
> +	buf.iob0 = performance_mode;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				      &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +}
> +
> +static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook, u8 *performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	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)
> +{
> +	for (int i = 0; i < PLATFORM_PROFILE_LAST; i++) {
> +		if (galaxybook->profile_performance_modes[i] == performance_mode) {
> +			if (profile)
> +				*profile = i;
> +			return 0;
> +		}
> +	}
> +
> +	return -ENODATA;
> +}
> +
> +static int galaxybook_platform_profile_set(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +
> +	return performance_mode_acpi_set(galaxybook,
> +					 galaxybook->profile_performance_modes[profile]);
> +}
> +
> +static int galaxybook_platform_profile_get(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option *profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +	u8 performance_mode;
> +	int err;
> +
> +	err = performance_mode_acpi_get(galaxybook, &performance_mode);
> +	if (err)
> +		return err;
> +
> +	return get_performance_mode_profile(galaxybook, performance_mode, profile);
> +}
> +
> +static void galaxybook_profile_exit(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	platform_profile_remove(&galaxybook->profile_handler);
> +}
> +
> +#define IGNORE_PERFORMANCE_MODE_MAPPING  -1
> +
> +static void galaxybook_profile_init(struct samsung_galaxybook *galaxybook)
> +{
> +	u8 current_performance_mode;
> +	u8 init_performance_mode;
> +	struct sawb buf = { 0 };
> +	int mapped_profiles;
> +	int mode_profile;
> +	int err;
> +	int i;
> +
> +	galaxybook->profile_handler.name = DRIVER_NAME;
> +	galaxybook->profile_handler.dev = &galaxybook->platform->dev;
> +	galaxybook->profile_handler.profile_get = galaxybook_platform_profile_get;
> +	galaxybook->profile_handler.profile_set = galaxybook_platform_profile_set;
> +
> +	/* fetch supported performance mode values from ACPI method */
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_LIST;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* set up profile_performance_modes with "unknown" as init value */
> +	for (i = 0; i < PLATFORM_PROFILE_LAST; i++)
> +		galaxybook->profile_performance_modes[i] = PERFORMANCE_MODE_UNKNOWN;
> +
> +	/*
> +	 * Value returned in iob0 will have the number of supported performance modes.
> +	 * The performance mode values will then be given as a list after this (iob1-iobX).
> +	 * Loop backwards from last value to first value (to handle fallback cases which come with
> +	 * smaller values) and map each supported value to its correct platform_profile_option.
> +	 */
> +	for (i = buf.iob0; i > 0; i--) {
> +		/*
> +		 * Prefer mapping to at least performance, balanced, and low-power profiles, as they
> +		 * are the profiles which are typically supported by userspace tools
> +		 * (power-profiles-daemon, etc).
> +		 * - performance = "ultra", otherwise "performance"
> +		 * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
> +		 * - low-power   = "silent", otherwise "quiet"
> +		 * Different models support different modes. Additional supported modes will be
> +		 * mapped to profiles that fall in between these 3.
> +		 */

To be honest i would prefer if you remove this overly complicated mapping algorithm. I rather suggest that the
userspace utilities in question are updated to handle such situations themself (other drivers would also benefit
from this).

I think the following static mappings would make sense:

PERFORMANCE_MODE_ULTRA -> performance
PERFORMANCE_MODE_PERFORMANCE -> balanced-performance
PERFORMANCE_MODE_OPTIMIZED -> balanced
PERFORMANCE_MODE_QUIET -> quiet
PERFORMANCE_MODE_SILENT -> low-power

The legacy performance modes should not override other performance modes, i. e. PERFORMANCE_MODE_PERFORMANCE_LEGACY
should not override PERFORMANCE_MODE_PERFORMANCE. However non-legacy performance modes should override legacy
performance modes.

If you can be sure that legacy performance modes are not mixed with non-legacy performance modes then you can omit
the override mechanism.

> +		switch (buf.iob_values[i]) {
> +		case PERFORMANCE_MODE_ULTRA:
> +			/* ultra always maps to performance */
> +			mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE:
> +			/* if ultra exists, map performance to balanced-performance */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			else /* otherwise map it to performance instead */
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_SILENT:
> +			/* silent always maps to low-power */
> +			mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_QUIET:
> +			/* if silent exists, map quiet to quiet */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_LOW_POWER] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_QUIET;
> +			else /* otherwise map it to low-power for better userspace tool support */
> +				mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED:
> +			/* optimized always maps to balanced */
> +			mode_profile = PLATFORM_PROFILE_BALANCED;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE_LEGACY:
> +			/* map to performance if performance is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED_LEGACY:
> +			/* map to balanced if balanced is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_BALANCED] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		default: /* any other value is not supported */
> +			mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +		}
> +
> +		/* if current mode value mapped to a supported platform_profile_option, set it up */
> +		if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
> +			mapped_profiles++;
> +			galaxybook->profile_performance_modes[mode_profile] = buf.iob_values[i];
> +			set_bit(mode_profile, galaxybook->profile_handler.choices);
> +			if (mode_profile == DEFAULT_PLATFORM_PROFILE)
> +				init_performance_mode = buf.iob_values[i];
> +			dev_dbg(&galaxybook->platform->dev,
> +				"will support platform profile %d (performance mode 0x%x)\n",
> +				mode_profile, buf.iob_values[i]);
> +		} else {
> +			dev_dbg(&galaxybook->platform->dev,
> +				"unmapped performance mode 0x%x will be ignored\n",
> +				buf.iob_values[i]);
> +		}
> +	}
> +
> +	if (mapped_profiles == 0) {
> +		err = -ENODEV;
> +		goto return_with_dbg;
> +	}
> +
> +	/* now check currently set performance mode; if not supported then set default mode */
> +	err = performance_mode_acpi_get(galaxybook, &current_performance_mode);
> +	if (err)
> +		goto return_with_dbg;
> +	err = get_performance_mode_profile(galaxybook, current_performance_mode, NULL);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"initial performance mode value is not supported by device; "
> +			"setting to default\n");
> +		err = performance_mode_acpi_set(galaxybook, init_performance_mode);
> +		if (err)
> +			goto return_with_dbg;
> +	}
> +
> +	err = platform_profile_register(&galaxybook->profile_handler);

Since devm_platform_profile_register() has been added recently i suggest that you use this instead.

Also failing to register the platform profile should return an error back to the caller of this function.

> +	if (err)
> +		goto return_with_dbg;
> +
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_profile_exit, galaxybook);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_performance_mode = true;
> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize platform profile, error %d\n", err);

...

> +}
> +
> +/*
> + * Hotkeys and notifications
> + */
> +
> +static void galaxybook_input_notify(struct samsung_galaxybook *galaxybook, int event)
> +{
> +	if (!galaxybook->input)
> +		return;
> +	mutex_lock(&galaxybook->input_lock);
> +	if (!sparse_keymap_report_event(galaxybook->input, event, 1, true))
> +		dev_warn(&galaxybook->acpi->dev, "unknown input notification event: 0x%x\n", event);
> +	mutex_unlock(&galaxybook->input_lock);

Since the only two values are the states of the switch i suggest that you use input_report_switch() directly
and omit the sparse keymap.

> +}
> +
> +static int galaxybook_input_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->input_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev);
> +	if (!galaxybook->input)
> +		return -ENOMEM;
> +
> +	galaxybook->input->name = "Samsung Galaxy Book Extra Buttons";
> +	galaxybook->input->phys = DRIVER_NAME "/input0";
> +	galaxybook->input->id.bustype = BUS_HOST;
> +	galaxybook->input->dev.parent = &galaxybook->platform->dev;

Please call input_report_switch() with the current value of the camera lense cover here to seed
the intial state of the switch. Otherwise the switch might report an incorrect position after
initialization.

Since this input device is only used by the camera lense cover i suggest that you merge both initialization
functions.

> +
> +	err = sparse_keymap_setup(galaxybook->input, galaxybook_acpi_keymap, NULL);
> +	if (err)
> +		return err;
> +
> +	return input_register_device(galaxybook->input);
> +}
> +
> +static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, kbd_backlight_hotkey_work);
> +	int new_brightness;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->kbd_backlight_lock);
> +
> +	if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)

Please use led_get_brightness() here.

> +		new_brightness = galaxybook->kbd_backlight.brightness + 1;
> +	else
> +		new_brightness = 0;
> +
> +	err = led_set_brightness_sync(&galaxybook->kbd_backlight, new_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, new_brightness);
> +}
> +
> +static void galaxybook_camera_lens_cover_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, camera_lens_cover_hotkey_work);
> +	bool value;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->camera_lens_cover_lock);
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to get camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	err = camera_lens_cover_acpi_set(galaxybook, !value);
> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to set camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	galaxybook_input_notify(galaxybook,
> +				!value ? INPUT_CAMERA_LENS_COVER_ON : INPUT_CAMERA_LENS_COVER_OFF);
> +}
> +
> +static bool galaxybook_i8042_filter(unsigned char data, unsigned char str, struct serio *port)
> +{
> +	static bool extended;
> +
> +	if (str & I8042_STR_AUXDATA)
> +		return false;
> +
> +	if (data == 0xe0) {
> +		extended = true;
> +		return true;
> +	} else if (extended) {
> +		extended = false;
> +		switch (data) {
> +		case GB_KEY_KBD_BACKLIGHT_KEYDOWN:
> +			return true;
> +		case GB_KEY_KBD_BACKLIGHT_KEYUP:
> +			if (galaxybook_ptr->has_kbd_backlight)
> +				schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work);
> +			return true;
> +
> +		case GB_KEY_CAMERA_LENS_COVER_KEYDOWN:
> +			return true;
> +		case GB_KEY_CAMERA_LENS_COVER_KEYUP:
> +			if (galaxybook_ptr->has_camera_lens_cover)
> +				schedule_work(&galaxybook_ptr->camera_lens_cover_hotkey_work);
> +			return true;
> +
> +		/* battery notification already sent to battery and ACPI device; ignore */
> +		case GB_KEY_BATTERY_NOTIFY_KEYUP:
> +		case GB_KEY_BATTERY_NOTIFY_KEYDOWN:
> +			return true;
> +
> +		default:
> +			/*
> +			 * Report the previously filtered e0 before continuing
> +			 * with the next non-filtered byte.
> +			 */
> +			serio_interrupt(port, 0xe0, 0);
> +			return false;
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +static void galaxybook_i8042_filter_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	i8042_remove_filter(galaxybook_i8042_filter);
> +	if (galaxybook->has_kbd_backlight)
> +		cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
> +	if (galaxybook->has_camera_lens_cover)
> +		cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
> +}
> +
> +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
> +		return 0;
> +
> +	if (galaxybook->has_kbd_backlight)
> +		INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
> +			  galaxybook_kbd_backlight_hotkey_work);
> +
> +	if (galaxybook->has_camera_lens_cover)
> +		INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
> +			  galaxybook_camera_lens_cover_hotkey_work);
> +
> +	err = i8042_install_filter(galaxybook_i8042_filter);
> +	if (err)
> +		return err;
> +
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_i8042_filter_remove, galaxybook);
> +}
> +
> +/*
> + * ACPI device setup
> + */
> +
> +static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	switch (event) {
> +	case ACPI_NOTIFY_BATTERY_STATE_CHANGED:
> +	case ACPI_NOTIFY_DEVICE_ON_TABLE:
> +	case ACPI_NOTIFY_DEVICE_OFF_TABLE:
> +		break;
> +	case ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE:
> +		if (galaxybook->has_performance_mode)
> +			platform_profile_cycle();
> +		break;
> +	default:
> +		dev_warn(&galaxybook->acpi->dev, "unknown ACPI notification event: 0x%x\n", event);

Please only use the ACPI device for calling ACPI methods. Everything else should use the platform device.

> +	}
> +
> +	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 = { 0 };
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_NOTIFICATIONS);
> +	if (err)
> +		return err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_NOTIFICATIONS;
> +	buf.gunm = GUNM_ACPI_NOTIFY_ENABLE;
> +	buf.guds[0] = GUDS_ACPI_NOTIFY_ENABLE;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +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,
> +				   ACPI_METHOD_ENABLE, 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, ACPI_METHOD_ENABLE,
> +					    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_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
> +			 "some hotkeys will not be supported\n");
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_POWER_MANAGEMENT);
> +	if (err)
> +		dev_warn(&galaxybook->acpi->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;
> +
> +	/* set static pointer here so it can be used in i8042 filter */
> +	if (galaxybook_ptr)
> +		return -EBUSY;
> +	galaxybook_ptr = galaxybook;
> +
> +	galaxybook->platform = pdev;
> +	galaxybook->acpi = adev;
> +
> +	dev_set_drvdata(&galaxybook->platform->dev, galaxybook);
> +
> +	err = galaxybook_input_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->platform->dev, err,
> +				     "failed to initialize input device\n");
> +
> +	err = galaxybook_acpi_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize ACPI device\n");
> +
> +	galaxybook_profile_init(galaxybook);
> +	galaxybook_battery_threshold_init(galaxybook);
> +
> +	err = galaxybook_camera_lens_cover_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize camera_lens_cover\n");
> +
> +	err = galaxybook_kbd_backlight_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->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 void galaxybook_remove(struct platform_device *pdev)
> +{
> +	if (galaxybook_ptr)
> +		galaxybook_ptr = NULL;

As already being said, this will cause issues with the i8042 filter. I suggest you move the whole galaxybook_ptr
handling inside galaxybook_i8042_filter_install()/_remove().

All things considered the driver looks quite good, hoping for a v5 revision in the future :).

Thanks,
Armin Wolf

> +}
> +
> +static struct platform_driver galaxybook_platform_driver = {
> +	.driver = {
> +		.name = DRIVER_NAME,
> +		.acpi_match_table = galaxybook_device_ids,
> +	},
> +	.probe = galaxybook_probe,
> +	.remove = galaxybook_remove,
> +};
> +module_platform_driver(galaxybook_platform_driver);
> +
> +MODULE_AUTHOR("Joshua Grisham <josh@joshuagrisham.com>");
> +MODULE_DESCRIPTION("Samsung Galaxy Book Extras");
> +MODULE_LICENSE("GPL");
Joshua Grisham Jan. 3, 2025, 6:19 p.m. UTC | #3
Hi Kurt, thanks for the comments! Will respond inline below...

Den mån 30 dec. 2024 kl 18:50 skrev Kurt Borja <kuurtb@gmail.com>:
>
> > +     if (err)
> > +             goto return_with_dbg;
> > +
> > +     galaxybook->has_kbd_backlight = true;
> > +
> > +     return 0;
> > +
> > +return_with_dbg:
> > +     dev_dbg(&galaxybook->platform->dev,
> > +             "failed to initialize kbd_backlight, error %d\n", err);
> > +     return 0;
>
> Return `err` here.
>

I actually intentionally want to return 0 here -- the feature is "not
enabled" but other features of the driver can be (so probe should not
fail and unload the module). Not all devices that have these ACPI IDs
will have keyboard backlight (or various other features that are
supported by this module), but do have other features, so those
features that exist on the specific device should "work" ideally while
others are not made available. This logic matches the behavior from
before but just slightly refactored now to clean it up a bit. Per some
other comments from Armin I will change a bit of this so the debug
messages will be more clear at "point of use" so hopefully it will be
even more clear; does this seem ok or should there also be a comment
or clear text in the debug message that it will continue without
failing the probe?

> > +     int mapped_profiles;
> >  [...]
> > +             /* if current mode value mapped to a supported platform_profile_option, set it up */
> > +             if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
> > +                     mapped_profiles++;
>
> mapped_profiles is uninitialized!!
>

Thank you! A total miss on my part .. and feels like just random
chance that I have not had an issue so far (it seems like it has
always grabbed fresh memory / a value that was already 0) but I will
fix this :)

> > +     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 void galaxybook_remove(struct platform_device *pdev)
> > +{
> > +     if (galaxybook_ptr)
> > +             galaxybook_ptr = NULL;
>
> Please someone correct me if I'm wrong.
>
> Device resources get released after calling the .remove callback,
> therefore there is a small window in which the i8042 filter is *still*
> installed after this point, which means you could dereference a NULL
> pointer.
>
> I suggest not using devres for the i8042 filter.
>

I believe you are correct, and I checked some of the driver core code
and was able to pinpoint the exact sequence to confirm. This was also
mentioned by Armin in a comment. My intention is that I will actually
fold everything to do with this global pointer into the i8042 init /
remove functions since it is the only thing that uses it, so hopefully
all will work out ok. Also my intention further is if Armin's changes
to add a context pointer to the i8042 filter hook get accepted and
merged then I will move to that and remove this global pointer
entirely :)

Thanks again for looking into this, and please feel free to say if
there is anything else you find or something I responded with here
that does not sound good!

Joshua
Kurt Borja Jan. 3, 2025, 6:52 p.m. UTC | #4
On Fri, Jan 03, 2025 at 07:19:51PM +0100, Joshua Grisham wrote:
> Hi Kurt, thanks for the comments! Will respond inline below...
> 
> Den mån 30 dec. 2024 kl 18:50 skrev Kurt Borja <kuurtb@gmail.com>:
> >
> > > +     if (err)
> > > +             goto return_with_dbg;
> > > +
> > > +     galaxybook->has_kbd_backlight = true;
> > > +
> > > +     return 0;
> > > +
> > > +return_with_dbg:
> > > +     dev_dbg(&galaxybook->platform->dev,
> > > +             "failed to initialize kbd_backlight, error %d\n", err);
> > > +     return 0;
> >
> > Return `err` here.
> >
> 
> I actually intentionally want to return 0 here -- the feature is "not
> enabled" but other features of the driver can be (so probe should not
> fail and unload the module). Not all devices that have these ACPI IDs
> will have keyboard backlight (or various other features that are
> supported by this module), but do have other features, so those
> features that exist on the specific device should "work" ideally while
> others are not made available. This logic matches the behavior from
> before but just slightly refactored now to clean it up a bit. Per some
> other comments from Armin I will change a bit of this so the debug
> messages will be more clear at "point of use" so hopefully it will be
> even more clear; does this seem ok or should there also be a comment
> or clear text in the debug message that it will continue without
> failing the probe?

I thought this might have been the case, but you do propagate errors
from this method to the probe, even though it always returns 0, so it
seems that you wanted to return err instead.

To me it would be better to make this method void like 
galaxybook_profile_init() or galaxybook_battery_threshold_init(). But
I'd like to hear Armin's opinion.

> 
> > > +     int mapped_profiles;
> > >  [...]
> > > +             /* if current mode value mapped to a supported platform_profile_option, set it up */
> > > +             if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
> > > +                     mapped_profiles++;
> >
> > mapped_profiles is uninitialized!!
> >
> 
> Thank you! A total miss on my part .. and feels like just random
> chance that I have not had an issue so far (it seems like it has
> always grabbed fresh memory / a value that was already 0) but I will
> fix this :)

Thankfully, I think there are kernel configs to auto-initialize stack
variables to 0. That may be why you didn't encounter problems.

> 
> > > +     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 void galaxybook_remove(struct platform_device *pdev)
> > > +{
> > > +     if (galaxybook_ptr)
> > > +             galaxybook_ptr = NULL;
> >
> > Please someone correct me if I'm wrong.
> >
> > Device resources get released after calling the .remove callback,
> > therefore there is a small window in which the i8042 filter is *still*
> > installed after this point, which means you could dereference a NULL
> > pointer.
> >
> > I suggest not using devres for the i8042 filter.
> >
> 
> I believe you are correct, and I checked some of the driver core code
> and was able to pinpoint the exact sequence to confirm. This was also
> mentioned by Armin in a comment. My intention is that I will actually
> fold everything to do with this global pointer into the i8042 init /
> remove functions since it is the only thing that uses it, so hopefully
> all will work out ok. Also my intention further is if Armin's changes
> to add a context pointer to the i8042 filter hook get accepted and
> merged then I will move to that and remove this global pointer
> entirely :)

Yes, I'm also waiting for it to get merged. I want to implement a filter
in alienware-wmi.

> 
> Thanks again for looking into this, and please feel free to say if
> there is anything else you find or something I responded with here
> that does not sound good!

Sure :)

~ Kurt

> 
> Joshua
Thomas Weißschuh Jan. 3, 2025, 7:37 p.m. UTC | #5
Hi,

On 2024-12-26 16:30:22+0100, Joshua Grisham wrote:
> Adds a new driver for Samsung Galaxy Book series notebook devices with the
> following features:

Use imerpative language:

Adds -> Add

Also when sending new revisions explicitly Cc previous reviewers.

> - 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
> ---
>  .../testing/sysfs-class-firmware-attributes   |   28 +
>  Documentation/admin-guide/laptops/index.rst   |    1 +
>  .../laptops/samsung-galaxybook.rst            |  165 ++
>  MAINTAINERS                                   |    7 +
>  drivers/platform/x86/Kconfig                  |   18 +
>  drivers/platform/x86/Makefile                 |    5 +-
>  drivers/platform/x86/samsung-galaxybook.c     | 1493 +++++++++++++++++
>  7 files changed, 1715 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/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> index 2713efa509b4..dd36577b68f2 100644
> --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
> +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
> @@ -326,6 +326,17 @@ Description:
>  					This role is specific to Secure Platform Management (SPM) attribute.
>  					It requires configuring an endorsement (kek) and signing certificate (sk).
>  
> +What:		/sys/class/firmware-attributes/*/attributes/camera_lens_cover
> +Date:		December 2024
> +KernelVersion:	6.13

The Date and KernelVersion are out of date now.
(Yes, it's annoying)

> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control the behavior of a software-based camera lens
> +		cover. The value is a boolean represented by 0 for false (camera is not blocked)
> +		and 1 for true (camera is blocked).
> +
> +		On Samsung Galaxy Book systems, this attribute will also control a software-based
> +		"cover" of the microphone in addition to the camera.
>  
>  What:		/sys/class/firmware-attributes/*/attributes/pending_reboot
>  Date:		February 2021
> @@ -356,6 +367,14 @@ Description:
>  		Drivers may emit a CHANGE uevent when this value changes and userspace
>  		may check it again.
>  
> +What:		/sys/class/firmware-attributes/*/attributes/power_on_lid_open
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control powering on a device when the lid is opened.
> +		The value is a boolean represented by 0 for false and 1 for true.
> +
>  What:		/sys/class/firmware-attributes/*/attributes/reset_bios
>  Date:		February 2021
>  KernelVersion:	5.11
> @@ -429,6 +448,15 @@ Description:
>  		HP specific class extensions - Secure Platform Manager (SPM)
>  		--------------------------------
>  
> +What:		/sys/class/firmware-attributes/*/attributes/usb_charging
> +Date:		December 2024
> +KernelVersion:	6.13
> +Contact:	Joshua Grisham <josh@joshuagrisham.com>
> +Description:
> +		This attribute can be used to control if USB ports can continue to deliver power to
> +		connected devices when the device is powered off or in a low sleep state. The value
> +		is a boolean represented by 0 for false and 1 for true.
> +
>  What:		/sys/class/firmware-attributes/*/authentication/SPM/kek
>  Date:		March 2023
>  KernelVersion:	5.18
> 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..65da7cd84c01
> --- /dev/null
> +++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst
> @@ -0,0 +1,165 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +==========================
> +Samsung Galaxy Book Extras
> +==========================
> +
> +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 device extension

Please rename it to "battery hook". It can be confused with a power
supply extension which is something related but different. If you are
targetting 6.14 you could use the power supply extension framework to
get rid of the manual sysfs attribute handling.
(That framework is currently only in the power-supply/for-next tree)

> +- :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

You can make this real reST link which will be converted into a
hyperlink.

> +with the "performance mode" function of the Samsung ACPI device.
> +
> +Mapping of each Samsung "performance mode" to its respective platform profile is
> +done dynamically based on a list of the supported modes reported by the device
> +itself. Preference is given to always try and map ``low-power``, ``balanced``,
> +and ``performance`` profiles, as these seem to be the most common profiles
> +utilized (and sometimes even required) by various userspace tools.
> +
> +The result of the mapping will 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 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:
> +
> +- ``camera_lens_cover``
> +- ``power_on_lid_open``
> +- ``usb_charging``
> +
> +These attributes are documented in more detail under
> +Documentation/admin-guide/abi.rst.
> +
> +.. _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 "camera lens cover" setting, which blocks
> +or allows usage of the built-in camera and microphone.
> +
> +There is a new "Samsung Galaxy Book Extra Buttons" input device created which
> +will send input events for the following notifications:
> +
> +- Switch ``SW_CAMERA_LENS_COVER`` (on or off) when the camera and microphone are
> +  "blocked" or "allowed" when toggling the Camera Lens Cover setting.
> +
> +.. _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..e74873a1e74b 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 EXTRAS DRIVER
> +M:	Joshua Grisham <josh@joshuagrisham.com>
> +L:	platform-driver-x86@vger.kernel.org

Technically the list isn't needed as it is picked up automatically
already. Other pdx86 entries also include it, so for consistency it can
be kept.

> +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..ecc509f5df55 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -778,6 +778,24 @@ 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 extras 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
> +	select INPUT_SPARSEKMAP
> +	help
> +	  This is a driver for Samsung Galaxy Book series notebooks. It adds
> +	  support for the keyboard backlight control, performance mode control, fan
> +	  speed reporting, function keys, and various other device controls.
> +
> +	  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..c656471dd1c7
> --- /dev/null
> +++ b/drivers/platform/x86/samsung-galaxybook.c
> @@ -0,0 +1,1493 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Samsung Galaxy Book series extras driver
> + *
> + * Copyright (c) 2024 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/err.h>
> +#include <linux/i8042.h>
> +#include <linux/init.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.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"
> +
> +static const struct acpi_device_id galaxybook_device_ids[] = {
> +	{ "SAM0427" },
> +	{ "SAM0428" },
> +	{ "SAM0429" },
> +	{ "SAM0430" },
> +	{},

No trailing comma after sentinel values.

> +};
> +MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
> +
> +struct samsung_galaxybook {
> +	struct platform_device *platform;
> +	struct acpi_device *acpi;
> +
> +	struct device *fw_attrs_dev;
> +	struct kset *fw_attrs_kset;
> +	struct kobj_attribute power_on_lid_open_attr;
> +	struct kobj_attribute usb_charging_attr;
> +	struct kobj_attribute camera_lens_cover_attr;
> +
> +	bool has_kbd_backlight;
> +	bool has_camera_lens_cover;
> +	bool has_performance_mode;
> +
> +	struct led_classdev kbd_backlight;
> +	/* block out of sync condition in hotkey action if brightness updated in another thread */
> +	struct mutex kbd_backlight_lock;
> +	struct work_struct kbd_backlight_hotkey_work;
> +
> +	struct input_dev *input;
> +	/* protect sparse keymap event reporting getting out of sync from multiple threads */
> +	struct mutex input_lock;
> +	void *i8042_filter_ptr;
> +
> +	/* block out of sync condition in hotkey action if value updated in another thread */
> +	struct mutex camera_lens_cover_lock;
> +	struct work_struct camera_lens_cover_hotkey_work;
> +
> +	struct acpi_battery_hook battery_hook;
> +	struct device_attribute charge_control_end_threshold_attr;
> +
> +	u8 profile_performance_modes[PLATFORM_PROFILE_LAST];
> +	struct platform_profile_handler profile_handler;
> +};
> +
> +static struct samsung_galaxybook *galaxybook_ptr;
> +static const struct class *fw_attr_class;
> +
> +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 SAWB_LEN_SETTINGS         0x15
> +#define SAWB_LEN_PERFORMANCE_MODE 0x100
> +
> +#define SAFN  0x5843
> +
> +#define SASB_KBD_BACKLIGHT      0x78
> +#define SASB_POWER_MANAGEMENT   0x7a
> +#define SASB_USB_CHARGING_GET   0x67
> +#define SASB_USB_CHARGING_SET   0x68
> +#define SASB_NOTIFICATIONS      0x86
> +#define SASB_CAMERA_LENS_COVER  0x8a
> +#define SASB_PERFORMANCE_MODE   0x91
> +
> +#define SAWB_RFLG_POS  4
> +#define SAWB_GUNM_POS  5
> +
> +#define RFLG_SUCCESS  0xaa
> +#define GUNM_FAIL     0xff
> +
> +#define GUNM_FEATURE_ENABLE          0xbb
> +#define GUNM_FEATURE_ENABLE_SUCCESS  0xdd
> +#define GUDS_FEATURE_ENABLE          0xaa
> +#define GUDS_FEATURE_ENABLE_SUCCESS  0xcc
> +
> +#define GUNM_GET  0x81
> +#define GUNM_SET  0x82
> +
> +#define GUNM_POWER_MANAGEMENT  0x82
> +
> +#define GUNM_USB_CHARGING_GET            0x80
> +#define GUNM_USB_CHARGING_ON             0x81
> +#define GUNM_USB_CHARGING_OFF            0x80
> +#define GUDS_POWER_ON_LID_OPEN           0xa3
> +#define GUDS_POWER_ON_LID_OPEN_GET       0x81
> +#define GUDS_POWER_ON_LID_OPEN_SET       0x80
> +#define GUDS_BATTERY_CHARGE_CONTROL      0xe9
> +#define GUDS_BATTERY_CHARGE_CONTROL_GET  0x91
> +#define GUDS_BATTERY_CHARGE_CONTROL_SET  0x90
> +#define GUNM_ACPI_NOTIFY_ENABLE          0x80
> +#define GUDS_ACPI_NOTIFY_ENABLE          0x02
> +
> +#define GB_CAMERA_LENS_COVER_ON   0x0
> +#define GB_CAMERA_LENS_COVER_OFF  0x1
> +
> +#define FNCN_PERFORMANCE_MODE       0x51
> +#define SUBN_PERFORMANCE_MODE_LIST  0x01
> +#define SUBN_PERFORMANCE_MODE_GET   0x02
> +#define SUBN_PERFORMANCE_MODE_SET   0x03
> +
> +/* guid 8246028d-8bca-4a55-ba0f-6f1e6b921b8f */
> +static const guid_t performance_mode_guid_value =
> +	GUID_INIT(0x8246028d, 0x8bca, 0x4a55, 0xba, 0x0f, 0x6f, 0x1e, 0x6b, 0x92, 0x1b, 0x8f);
> +#define PERFORMANCE_MODE_GUID performance_mode_guid_value
> +
> +#define PERFORMANCE_MODE_ULTRA               0x16
> +#define PERFORMANCE_MODE_PERFORMANCE         0x15
> +#define PERFORMANCE_MODE_SILENT              0xb
> +#define PERFORMANCE_MODE_QUIET               0xa
> +#define PERFORMANCE_MODE_OPTIMIZED           0x2
> +#define PERFORMANCE_MODE_PERFORMANCE_LEGACY  0x1
> +#define PERFORMANCE_MODE_OPTIMIZED_LEGACY    0x0
> +#define PERFORMANCE_MODE_UNKNOWN             0xff
> +
> +#define DEFAULT_PLATFORM_PROFILE PLATFORM_PROFILE_BALANCED
> +
> +#define ACPI_METHOD_ENABLE            "SDLS"
> +#define ACPI_METHOD_ENABLE_ON         1
> +#define ACPI_METHOD_ENABLE_OFF        0
> +#define ACPI_METHOD_SETTINGS          "CSFI"
> +#define ACPI_METHOD_PERFORMANCE_MODE  "CSXI"
> +
> +#define KBD_BACKLIGHT_MAX_BRIGHTNESS  3

Try to namespace your custom symbols. This looks like it comes from some
core code.

> +
> +#define ACPI_NOTIFY_BATTERY_STATE_CHANGED    0x61
> +#define ACPI_NOTIFY_DEVICE_ON_TABLE          0x6c
> +#define ACPI_NOTIFY_DEVICE_OFF_TABLE         0x6d
> +#define ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE  0x70
> +
> +#define GB_KEY_KBD_BACKLIGHT_KEYDOWN      0x2c
> +#define GB_KEY_KBD_BACKLIGHT_KEYUP        0xac
> +#define GB_KEY_CAMERA_LENS_COVER_KEYDOWN  0x1f
> +#define GB_KEY_CAMERA_LENS_COVER_KEYUP    0x9f
> +#define GB_KEY_BATTERY_NOTIFY_KEYUP       0xf
> +#define GB_KEY_BATTERY_NOTIFY_KEYDOWN     0x8f
> +
> +#define INPUT_CAMERA_LENS_COVER_ON   0x01
> +#define INPUT_CAMERA_LENS_COVER_OFF  0x02
> +
> +static const struct key_entry galaxybook_acpi_keymap[] = {
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_ON,  { .sw = { SW_CAMERA_LENS_COVER, 1 } } },
> +	{ KE_SW,  INPUT_CAMERA_LENS_COVER_OFF, { .sw = { SW_CAMERA_LENS_COVER, 0 } } },
> +	{ KE_END, 0 },
> +};
> +
> +/*
> + * ACPI method handling
> + */
> +
> +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> +				  struct sawb *in_buf, size_t len, struct sawb *out_buf)

in_buf and out_buf are always the same.

> +{
> +	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 *)in_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 < SAWB_GUNM_POS + 1) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"response length mismatch\n", method);
> +		err = -EPROTO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> +		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> +			"device did not respond with success code 0x%x\n",
> +			method, RFLG_SUCCESS);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +	if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> +		dev_err(&galaxybook->acpi->dev,
> +			"failed to execute method %s; device responded with failure code 0x%x\n",
> +			method, GUNM_FAIL);
> +		err = -ENXIO;
> +		goto out_free;
> +	}
> +
> +	memcpy(out_buf, out_obj->buffer.pointer, len);

Nit: This memcpy() could be avoided by having the ACPI core write directly
into out_buf. It would also remove the allocation.

> +	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 = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = sasb;
> +	buf.gunm = GUNM_FEATURE_ENABLE;
> +	buf.guds[0] = GUDS_FEATURE_ENABLE;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	if (buf.gunm != GUNM_FEATURE_ENABLE_SUCCESS && buf.guds[0] != 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_SET;
> +
> +	buf.guds[0] = brightness;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook,
> +				  enum led_brightness *brightness)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_KBD_BACKLIGHT;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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, SASB_KBD_BACKLIGHT);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* verify we can read the value, otherwise stop without setting has_kbd_backlight */
> +	err = kbd_backlight_acpi_get(galaxybook, &brightness);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	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 = KBD_BACKLIGHT_MAX_BRIGHTNESS;
> +
> +	err = devm_led_classdev_register_ext(&galaxybook->platform->dev,
> +					     &galaxybook->kbd_backlight, &init_data);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_kbd_backlight = true;
> +
> +	return 0;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize kbd_backlight, error %d\n", err);
> +	return 0;
> +}
> +
> +/*
> + * Platform device attributes (configuration properties which can be controlled via userspace)
> + */
> +
> +/* 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> +	buf.guds[2] = value ? 1 : 0;

No need for the ternary.

> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int power_on_lid_open_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> +	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.guds[1];
> +
> +	return 0;
> +}
> +
> +static ssize_t power_on_lid_open_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t power_on_lid_open_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
> +	bool value;
> +	int err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +/* USB Charging (USB ports can charge other devices even when device is powered off) */
> +
> +static int usb_charging_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_SET;
> +	buf.gunm = value ? GUNM_USB_CHARGING_ON : GUNM_USB_CHARGING_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int usb_charging_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_USB_CHARGING_GET;
> +	buf.gunm = GUNM_USB_CHARGING_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == 1;
> +
> +	return 0;
> +}
> +
> +static ssize_t usb_charging_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				  const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	err = usb_charging_acpi_set(galaxybook, value);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t usb_charging_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
> +	bool value;
> +	int err;
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +/* Camera lens cover (blocks access to camera and microphone) */
> +
> +static int camera_lens_cover_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> +{
> +	struct sawb buf = { 0 };

Add a lockdep_assert_held(&galaxybook->camera_lens_cover_lock) here to
document and enforce locking requirements.

> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_SET;
> +	buf.guds[0] = value ? GB_CAMERA_LENS_COVER_ON : GB_CAMERA_LENS_COVER_OFF;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int camera_lens_cover_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_CAMERA_LENS_COVER;
> +	buf.gunm = GUNM_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	if (err)
> +		return err;
> +
> +	*value = buf.gunm == GB_CAMERA_LENS_COVER_ON;
> +
> +	return 0;
> +}
> +
> +static ssize_t camera_lens_cover_store(struct kobject *kobj, struct kobj_attribute *attr,
> +				       const char *buffer, size_t count)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	if (!count)
> +		return -EINVAL;
> +
> +	err = kstrtobool(buffer, &value);
> +	if (err)
> +		return err;
> +
> +	mutex_lock(&galaxybook->camera_lens_cover_lock);
> +	err = camera_lens_cover_acpi_set(galaxybook, value);
> +	mutex_unlock(&galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	return count;
> +}
> +
> +static ssize_t camera_lens_cover_show(struct kobject *kobj, struct kobj_attribute *attr,
> +				      char *buffer)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
> +	bool value;
> +	int err;
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err)
> +		return err;
> +
> +	return sysfs_emit(buffer, "%u\n", value);
> +}
> +
> +static int galaxybook_camera_lens_cover_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_CAMERA_LENS_COVER);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"failed to initialize camera lens cover feature, error %d\n", err);
> +		return 0;
> +	}
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->camera_lens_cover_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->has_camera_lens_cover = true;
> +
> +	return 0;
> +}
> +
> +/* Attribute setup */
> +
> +static void galaxybook_power_on_lid_open_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->power_on_lid_open_attr.attr);
> +}
> +
> +static void galaxybook_usb_charging_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->usb_charging_attr.attr);
> +}
> +
> +static void galaxybook_camera_lens_cover_attr_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
> +			  &galaxybook->camera_lens_cover_attr.attr);
> +}
> +
> +static void galaxybook_fw_attrs_kset_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	kset_unregister(galaxybook->fw_attrs_kset);
> +}
> +
> +static void galaxybook_fw_attr_class_remove(void *data)
> +{
> +	device_destroy(fw_attr_class, MKDEV(0, 0));
> +	fw_attributes_class_put();
> +}
> +
> +static int galaxybook_fw_attrs_init(struct samsung_galaxybook *galaxybook)
> +{
> +	bool value;
> +	int err;
> +
> +	err = fw_attributes_class_get(&fw_attr_class);
> +	if (err)
> +		return err;
> +
> +	galaxybook->fw_attrs_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
> +						 NULL, "%s", DRIVER_NAME);
> +	if (IS_ERR(galaxybook->fw_attrs_dev)) {
> +		fw_attributes_class_put();
> +		err = PTR_ERR(galaxybook->fw_attrs_dev);
> +		return err;
> +	}
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_fw_attr_class_remove, NULL);
> +	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_fw_attrs_kset_remove, galaxybook);
> +	if (err)
> +		return err;
> +
> +	err = power_on_lid_open_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->power_on_lid_open_attr);
> +		galaxybook->power_on_lid_open_attr.attr.name = "power_on_lid_open";
> +		galaxybook->power_on_lid_open_attr.attr.mode = 0644;
> +		galaxybook->power_on_lid_open_attr.show = power_on_lid_open_show;
> +		galaxybook->power_on_lid_open_attr.store = power_on_lid_open_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->power_on_lid_open_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_power_on_lid_open_attr_remove,
> +					       galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	err = usb_charging_acpi_get(galaxybook, &value);
> +	if (!err) {
> +		sysfs_attr_init(&galaxybook->usb_charging_attr);
> +		galaxybook->usb_charging_attr.attr.name = "usb_charging";
> +		galaxybook->usb_charging_attr.attr.mode = 0644;
> +		galaxybook->usb_charging_attr.show = usb_charging_show;
> +		galaxybook->usb_charging_attr.store = usb_charging_store;
> +		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +					&galaxybook->usb_charging_attr.attr);
> +		if (err)
> +			return err;
> +		err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +					       galaxybook_usb_charging_attr_remove, galaxybook);
> +		if (err)
> +			return err;
> +	}
> +
> +	if (!galaxybook->has_camera_lens_cover)
> +		return 0;
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		galaxybook->has_camera_lens_cover = false;
> +		return 0;
> +	}
> +
> +	sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
> +	galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
> +	galaxybook->camera_lens_cover_attr.attr.mode = 0644;
> +	galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
> +	galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
> +	err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> +				&galaxybook->camera_lens_cover_attr.attr);
> +	if (err)
> +		return err;
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_camera_lens_cover_attr_remove, galaxybook);
> +}
> +
> +/*
> + * 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 = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_SET;
> +	buf.guds[2] = value;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook *galaxybook, u8 *value)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_POWER_MANAGEMENT;
> +	buf.gunm = GUNM_POWER_MANAGEMENT;
> +	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
> +	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				     &buf, SAWB_LEN_SETTINGS, &buf);
> +	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 *buffer, 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(buffer, 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 *buffer)
> +{
> +	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(buffer, "%d\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 void 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)
> +		goto return_with_dbg;
> +
> +	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;
> +
> +	err = devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize battery charge threshold, error %d\n", err);
> +}
> +
> +/*
> + * Platform Profile / Performance mode
> + */
> +
> +static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook,
> +				     const u8 performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_SET;
> +	buf.iob0 = performance_mode;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				      &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +}
> +
> +static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook, u8 *performance_mode)
> +{
> +	struct sawb buf = { 0 };
> +	int err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_GET;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	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)
> +{
> +	for (int i = 0; i < PLATFORM_PROFILE_LAST; i++) {
> +		if (galaxybook->profile_performance_modes[i] == performance_mode) {
> +			if (profile)
> +				*profile = i;
> +			return 0;
> +		}
> +	}
> +
> +	return -ENODATA;
> +}
> +
> +static int galaxybook_platform_profile_set(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +
> +	return performance_mode_acpi_set(galaxybook,
> +					 galaxybook->profile_performance_modes[profile]);
> +}
> +
> +static int galaxybook_platform_profile_get(struct platform_profile_handler *pprof,
> +					   enum platform_profile_option *profile)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(pprof, struct samsung_galaxybook, profile_handler);
> +	u8 performance_mode;
> +	int err;
> +
> +	err = performance_mode_acpi_get(galaxybook, &performance_mode);
> +	if (err)
> +		return err;
> +
> +	return get_performance_mode_profile(galaxybook, performance_mode, profile);
> +}
> +
> +static void galaxybook_profile_exit(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	platform_profile_remove(&galaxybook->profile_handler);
> +}
> +
> +#define IGNORE_PERFORMANCE_MODE_MAPPING  -1
> +
> +static void galaxybook_profile_init(struct samsung_galaxybook *galaxybook)
> +{
> +	u8 current_performance_mode;
> +	u8 init_performance_mode;
> +	struct sawb buf = { 0 };
> +	int mapped_profiles;
> +	int mode_profile;
> +	int err;
> +	int i;
> +
> +	galaxybook->profile_handler.name = DRIVER_NAME;
> +	galaxybook->profile_handler.dev = &galaxybook->platform->dev;
> +	galaxybook->profile_handler.profile_get = galaxybook_platform_profile_get;
> +	galaxybook->profile_handler.profile_set = galaxybook_platform_profile_set;
> +
> +	/* fetch supported performance mode values from ACPI method */
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_PERFORMANCE_MODE;
> +	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
> +	buf.fncn = FNCN_PERFORMANCE_MODE;
> +	buf.subn = SUBN_PERFORMANCE_MODE_LIST;
> +
> +	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
> +				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	/* set up profile_performance_modes with "unknown" as init value */
> +	for (i = 0; i < PLATFORM_PROFILE_LAST; i++)
> +		galaxybook->profile_performance_modes[i] = PERFORMANCE_MODE_UNKNOWN;
> +
> +	/*
> +	 * Value returned in iob0 will have the number of supported performance modes.
> +	 * The performance mode values will then be given as a list after this (iob1-iobX).
> +	 * Loop backwards from last value to first value (to handle fallback cases which come with
> +	 * smaller values) and map each supported value to its correct platform_profile_option.
> +	 */
> +	for (i = buf.iob0; i > 0; i--) {
> +		/*
> +		 * Prefer mapping to at least performance, balanced, and low-power profiles, as they
> +		 * are the profiles which are typically supported by userspace tools
> +		 * (power-profiles-daemon, etc).
> +		 * - performance = "ultra", otherwise "performance"
> +		 * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
> +		 * - low-power   = "silent", otherwise "quiet"
> +		 * Different models support different modes. Additional supported modes will be
> +		 * mapped to profiles that fall in between these 3.
> +		 */
> +		switch (buf.iob_values[i]) {
> +		case PERFORMANCE_MODE_ULTRA:
> +			/* ultra always maps to performance */
> +			mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE:
> +			/* if ultra exists, map performance to balanced-performance */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
> +			else /* otherwise map it to performance instead */
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			break;
> +
> +		case PERFORMANCE_MODE_SILENT:
> +			/* silent always maps to low-power */
> +			mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_QUIET:
> +			/* if silent exists, map quiet to quiet */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_LOW_POWER] !=
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_QUIET;
> +			else /* otherwise map it to low-power for better userspace tool support */
> +				mode_profile = PLATFORM_PROFILE_LOW_POWER;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED:
> +			/* optimized always maps to balanced */
> +			mode_profile = PLATFORM_PROFILE_BALANCED;
> +			break;
> +
> +		case PERFORMANCE_MODE_PERFORMANCE_LEGACY:
> +			/* map to performance if performance is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		case PERFORMANCE_MODE_OPTIMIZED_LEGACY:
> +			/* map to balanced if balanced is not already supported */
> +			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_BALANCED] ==
> +			    PERFORMANCE_MODE_UNKNOWN)
> +				mode_profile = PLATFORM_PROFILE_BALANCED;
> +			else /* otherwise, ignore */
> +				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +
> +		default: /* any other value is not supported */
> +			mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
> +			break;
> +		}
> +
> +		/* if current mode value mapped to a supported platform_profile_option, set it up */
> +		if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
> +			mapped_profiles++;
> +			galaxybook->profile_performance_modes[mode_profile] = buf.iob_values[i];
> +			set_bit(mode_profile, galaxybook->profile_handler.choices);
> +			if (mode_profile == DEFAULT_PLATFORM_PROFILE)
> +				init_performance_mode = buf.iob_values[i];
> +			dev_dbg(&galaxybook->platform->dev,
> +				"will support platform profile %d (performance mode 0x%x)\n",
> +				mode_profile, buf.iob_values[i]);
> +		} else {
> +			dev_dbg(&galaxybook->platform->dev,
> +				"unmapped performance mode 0x%x will be ignored\n",
> +				buf.iob_values[i]);
> +		}
> +	}
> +
> +	if (mapped_profiles == 0) {
> +		err = -ENODEV;
> +		goto return_with_dbg;
> +	}
> +
> +	/* now check currently set performance mode; if not supported then set default mode */
> +	err = performance_mode_acpi_get(galaxybook, &current_performance_mode);
> +	if (err)
> +		goto return_with_dbg;
> +	err = get_performance_mode_profile(galaxybook, current_performance_mode, NULL);
> +	if (err) {
> +		dev_dbg(&galaxybook->platform->dev,
> +			"initial performance mode value is not supported by device; "
> +			"setting to default\n");
> +		err = performance_mode_acpi_set(galaxybook, init_performance_mode);
> +		if (err)
> +			goto return_with_dbg;
> +	}
> +
> +	err = platform_profile_register(&galaxybook->profile_handler);
> +	if (err)
> +		goto return_with_dbg;

Good candidate for new devm_platform_profile_register().

> +
> +	err = devm_add_action_or_reset(&galaxybook->platform->dev,
> +				       galaxybook_profile_exit, galaxybook);
> +	if (err)
> +		goto return_with_dbg;
> +
> +	galaxybook->has_performance_mode = true;
> +
> +	return;
> +
> +return_with_dbg:
> +	dev_dbg(&galaxybook->platform->dev,
> +		"failed to initialize platform profile, error %d\n", err);
> +}
> +
> +/*
> + * Hotkeys and notifications
> + */
> +
> +static void galaxybook_input_notify(struct samsung_galaxybook *galaxybook, int event)
> +{
> +	if (!galaxybook->input)
> +		return;
> +	mutex_lock(&galaxybook->input_lock);

Is this lock really needed?
Also guards and explicit locking is used inconsistently.

> +	if (!sparse_keymap_report_event(galaxybook->input, event, 1, true))
> +		dev_warn(&galaxybook->acpi->dev, "unknown input notification event: 0x%x\n", event);
> +	mutex_unlock(&galaxybook->input_lock);
> +}
> +
> +static int galaxybook_input_init(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->input_lock);
> +	if (err)
> +		return err;
> +
> +	galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev);
> +	if (!galaxybook->input)
> +		return -ENOMEM;
> +
> +	galaxybook->input->name = "Samsung Galaxy Book Extra Buttons";
> +	galaxybook->input->phys = DRIVER_NAME "/input0";
> +	galaxybook->input->id.bustype = BUS_HOST;
> +	galaxybook->input->dev.parent = &galaxybook->platform->dev;

devm_input_allocate_device() already sets up the parent device.

> +
> +	err = sparse_keymap_setup(galaxybook->input, galaxybook_acpi_keymap, NULL);
> +	if (err)
> +		return err;
> +
> +	return input_register_device(galaxybook->input);
> +}
> +
> +static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, kbd_backlight_hotkey_work);

from_work()

> +	int new_brightness;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->kbd_backlight_lock);
> +
> +	if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)
> +		new_brightness = galaxybook->kbd_backlight.brightness + 1;
> +	else
> +		new_brightness = 0;

Could use a modulo, it's very long though.

new_brightness = galaxybook->kbd_backlight.brightness + 1 % galaxybook->kbd_backlight.max_brightness;

> +
> +	err = led_set_brightness_sync(&galaxybook->kbd_backlight, new_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, new_brightness);
> +}
> +
> +static void galaxybook_camera_lens_cover_hotkey_work(struct work_struct *work)
> +{
> +	struct samsung_galaxybook *galaxybook =
> +		container_of(work, struct samsung_galaxybook, camera_lens_cover_hotkey_work);
> +	bool value;
> +	int err;
> +
> +	guard(mutex)(&galaxybook->camera_lens_cover_lock);
> +
> +	err = camera_lens_cover_acpi_get(galaxybook, &value);
> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to get camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	err = camera_lens_cover_acpi_set(galaxybook, !value);

Would be slightly clearer with a temporary variable:
bool new_value = !value;

> +	if (err) {
> +		dev_err(&galaxybook->platform->dev,
> +			"failed to set camera_lens_cover, error %d\n", err);
> +		return;
> +	}
> +
> +	galaxybook_input_notify(galaxybook,
> +				!value ? INPUT_CAMERA_LENS_COVER_ON : INPUT_CAMERA_LENS_COVER_OFF);
> +}
> +
> +static bool galaxybook_i8042_filter(unsigned char data, unsigned char str, struct serio *port)
> +{
> +	static bool extended;
> +
> +	if (str & I8042_STR_AUXDATA)
> +		return false;
> +
> +	if (data == 0xe0) {
> +		extended = true;
> +		return true;
> +	} else if (extended) {
> +		extended = false;
> +		switch (data) {
> +		case GB_KEY_KBD_BACKLIGHT_KEYDOWN:
> +			return true;
> +		case GB_KEY_KBD_BACKLIGHT_KEYUP:
> +			if (galaxybook_ptr->has_kbd_backlight)
> +				schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work);
> +			return true;
> +
> +		case GB_KEY_CAMERA_LENS_COVER_KEYDOWN:
> +			return true;
> +		case GB_KEY_CAMERA_LENS_COVER_KEYUP:
> +			if (galaxybook_ptr->has_camera_lens_cover)
> +				schedule_work(&galaxybook_ptr->camera_lens_cover_hotkey_work);
> +			return true;
> +
> +		/* battery notification already sent to battery and ACPI device; ignore */
> +		case GB_KEY_BATTERY_NOTIFY_KEYUP:
> +		case GB_KEY_BATTERY_NOTIFY_KEYDOWN:
> +			return true;
> +
> +		default:
> +			/*
> +			 * Report the previously filtered e0 before continuing
> +			 * with the next non-filtered byte.
> +			 */
> +			serio_interrupt(port, 0xe0, 0);
> +			return false;
> +		}
> +	}
> +
> +	return false;
> +}
> +
> +static void galaxybook_i8042_filter_remove(void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	i8042_remove_filter(galaxybook_i8042_filter);
> +	if (galaxybook->has_kbd_backlight)
> +		cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
> +	if (galaxybook->has_camera_lens_cover)
> +		cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
> +}
> +
> +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
> +{
> +	int err;
> +
> +	if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
> +		return 0;
> +
> +	if (galaxybook->has_kbd_backlight)
> +		INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
> +			  galaxybook_kbd_backlight_hotkey_work);
> +
> +	if (galaxybook->has_camera_lens_cover)
> +		INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
> +			  galaxybook_camera_lens_cover_hotkey_work);

I would just always initialize and cancel the work_structs.
This is no hot path and it makes the code simpler.

> +
> +	err = i8042_install_filter(galaxybook_i8042_filter);
> +	if (err)
> +		return err;
> +
> +	return devm_add_action_or_reset(&galaxybook->platform->dev,
> +					galaxybook_i8042_filter_remove, galaxybook);
> +}
> +
> +/*
> + * ACPI device setup
> + */
> +
> +static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data)
> +{
> +	struct samsung_galaxybook *galaxybook = data;
> +
> +	switch (event) {
> +	case ACPI_NOTIFY_BATTERY_STATE_CHANGED:
> +	case ACPI_NOTIFY_DEVICE_ON_TABLE:
> +	case ACPI_NOTIFY_DEVICE_OFF_TABLE:
> +		break;
> +	case ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE:
> +		if (galaxybook->has_performance_mode)
> +			platform_profile_cycle();
> +		break;
> +	default:
> +		dev_warn(&galaxybook->acpi->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 = { 0 };
> +	int err;
> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_NOTIFICATIONS);
> +	if (err)
> +		return err;
> +
> +	buf.safn = SAFN;
> +	buf.sasb = SASB_NOTIFICATIONS;
> +	buf.gunm = GUNM_ACPI_NOTIFY_ENABLE;
> +	buf.guds[0] = GUDS_ACPI_NOTIFY_ENABLE;
> +
> +	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
> +				      &buf, SAWB_LEN_SETTINGS, &buf);
> +}
> +
> +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,
> +				   ACPI_METHOD_ENABLE, 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, ACPI_METHOD_ENABLE,
> +					    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);

devm_acpi_install_notify_handler looks like a good candidate to add to
the ACPI core.

> +	if (err)
> +		return err;
> +
> +	err = galaxybook_enable_acpi_notify(galaxybook);
> +	if (err)
> +		dev_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
> +			 "some hotkeys will not be supported\n");

Will this dev_warn() trigger always for certain devices? If so a
dev_info() would be more appropriate IMO.

> +
> +	err = galaxybook_enable_acpi_feature(galaxybook, SASB_POWER_MANAGEMENT);
> +	if (err)
> +		dev_warn(&galaxybook->acpi->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;
> +
> +	/* set static pointer here so it can be used in i8042 filter */
> +	if (galaxybook_ptr)
> +		return -EBUSY;
> +	galaxybook_ptr = galaxybook;
> +
> +	galaxybook->platform = pdev;
> +	galaxybook->acpi = adev;
> +
> +	dev_set_drvdata(&galaxybook->platform->dev, galaxybook);

This seems unused.

> +
> +	err = galaxybook_input_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->platform->dev, err,
> +				     "failed to initialize input device\n");

Failing here will not clear the galaxybook_ptr anymore.
Use a devm action to clear the pointer in all cases, removing the need
for a remove handler.

> +
> +	err = galaxybook_acpi_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize ACPI device\n");
> +
> +	galaxybook_profile_init(galaxybook);
> +	galaxybook_battery_threshold_init(galaxybook);
> +
> +	err = galaxybook_camera_lens_cover_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->dev, err,
> +				     "failed to initialize camera_lens_cover\n");
> +
> +	err = galaxybook_kbd_backlight_init(galaxybook);
> +	if (err)
> +		return dev_err_probe(&galaxybook->acpi->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 void galaxybook_remove(struct platform_device *pdev)
> +{
> +	if (galaxybook_ptr)

Can this ever be false? Isn't necessary in any case.
The remove() handler is *not* executed when probe() fails.

> +		galaxybook_ptr = NULL;
> +}
> +
> +static struct platform_driver galaxybook_platform_driver = {
> +	.driver = {
> +		.name = DRIVER_NAME,
> +		.acpi_match_table = galaxybook_device_ids,
> +	},
> +	.probe = galaxybook_probe,
> +	.remove = galaxybook_remove,
> +};
> +module_platform_driver(galaxybook_platform_driver);
> +
> +MODULE_AUTHOR("Joshua Grisham <josh@joshuagrisham.com>");
> +MODULE_DESCRIPTION("Samsung Galaxy Book Extras");
> +MODULE_LICENSE("GPL");
> -- 
> 2.45.2
> 
>
Armin Wolf Jan. 3, 2025, 8:56 p.m. UTC | #6
Am 03.01.25 um 19:52 schrieb Kurt Borja:

> On Fri, Jan 03, 2025 at 07:19:51PM +0100, Joshua Grisham wrote:
>> Hi Kurt, thanks for the comments! Will respond inline below...
>>
>> Den mån 30 dec. 2024 kl 18:50 skrev Kurt Borja <kuurtb@gmail.com>:
>>>> +     if (err)
>>>> +             goto return_with_dbg;
>>>> +
>>>> +     galaxybook->has_kbd_backlight = true;
>>>> +
>>>> +     return 0;
>>>> +
>>>> +return_with_dbg:
>>>> +     dev_dbg(&galaxybook->platform->dev,
>>>> +             "failed to initialize kbd_backlight, error %d\n", err);
>>>> +     return 0;
>>> Return `err` here.
>>>
>> I actually intentionally want to return 0 here -- the feature is "not
>> enabled" but other features of the driver can be (so probe should not
>> fail and unload the module). Not all devices that have these ACPI IDs
>> will have keyboard backlight (or various other features that are
>> supported by this module), but do have other features, so those
>> features that exist on the specific device should "work" ideally while
>> others are not made available. This logic matches the behavior from
>> before but just slightly refactored now to clean it up a bit. Per some
>> other comments from Armin I will change a bit of this so the debug
>> messages will be more clear at "point of use" so hopefully it will be
>> even more clear; does this seem ok or should there also be a comment
>> or clear text in the debug message that it will continue without
>> failing the probe?
> I thought this might have been the case, but you do propagate errors
> from this method to the probe, even though it always returns 0, so it
> seems that you wanted to return err instead.
>
> To me it would be better to make this method void like
> galaxybook_profile_init() or galaxybook_battery_threshold_init(). But
> I'd like to hear Armin's opinion.

I am OK with returning 0 in case some errors are expected and should not cause a probe error.

However errors coming from other subsystems (e.g. error during subsystem registration) should usually
be propagated so that the driver either registers all supported interfaces or none.

Thanks,
Armin Wolf

>>>> +     int mapped_profiles;
>>>>   [...]
>>>> +             /* if current mode value mapped to a supported platform_profile_option, set it up */
>>>> +             if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
>>>> +                     mapped_profiles++;
>>> mapped_profiles is uninitialized!!
>>>
>> Thank you! A total miss on my part .. and feels like just random
>> chance that I have not had an issue so far (it seems like it has
>> always grabbed fresh memory / a value that was already 0) but I will
>> fix this :)
> Thankfully, I think there are kernel configs to auto-initialize stack
> variables to 0. That may be why you didn't encounter problems.
>
>>>> +     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 void galaxybook_remove(struct platform_device *pdev)
>>>> +{
>>>> +     if (galaxybook_ptr)
>>>> +             galaxybook_ptr = NULL;
>>> Please someone correct me if I'm wrong.
>>>
>>> Device resources get released after calling the .remove callback,
>>> therefore there is a small window in which the i8042 filter is *still*
>>> installed after this point, which means you could dereference a NULL
>>> pointer.
>>>
>>> I suggest not using devres for the i8042 filter.
>>>
>> I believe you are correct, and I checked some of the driver core code
>> and was able to pinpoint the exact sequence to confirm. This was also
>> mentioned by Armin in a comment. My intention is that I will actually
>> fold everything to do with this global pointer into the i8042 init /
>> remove functions since it is the only thing that uses it, so hopefully
>> all will work out ok. Also my intention further is if Armin's changes
>> to add a context pointer to the i8042 filter hook get accepted and
>> merged then I will move to that and remove this global pointer
>> entirely :)
> Yes, I'm also waiting for it to get merged. I want to implement a filter
> in alienware-wmi.
>
>> Thanks again for looking into this, and please feel free to say if
>> there is anything else you find or something I responded with here
>> that does not sound good!
> Sure :)
>
> ~ Kurt
>
>> Joshua
Joshua Grisham Jan. 4, 2025, 1:07 a.m. UTC | #7
Thanks again Armin for all of the very detailed comments -- they are
always super helpful!! A few things I think are pretty obvious and I
agree with, as well as where I missed as copy/paste when moving around
stuff that you caught (great!), so I will not bother to respond to
those here and will instead just fix them. :) For other things where I
have some questions, I will respond inline below.

Den tors 2 jan. 2025 kl 20:14 skrev Armin Wolf <W_Armin@gmx.de>:
>
> > +What:                /sys/class/firmware-attributes/*/attributes/usb_charging
> > +Date:                December 2024
> > +KernelVersion:       6.13
> > +Contact:     Joshua Grisham <josh@joshuagrisham.com>
> > +Description:
> > +             This attribute can be used to control if USB ports can continue to deliver power to
> > +             connected devices when the device is powered off or in a low sleep state. The value
> > +             is a boolean represented by 0 for false and 1 for true.
>
> Hi,
>
> please move the documentation of the firmware attributes to samsung-galaxybook.rst to avoid cluttering
> the subsystem docs with too much driver-specific entries.
>

I guess I am a bit confused by the intention and usage
firmware-attributes in general (including what should be in this
documentation vs not) -- is the idea that these should be "relatively
generic" attributes that control settings in the firmware that can
persist across reboots and or steer the firmware/hardware in various
ways (e.g. with admin password and/or pending reboot status etc) ? And
if they are "relatively generic" (e.g. could be reused by more than
one platform driver) then would the documentation belong here in a
centralized place? Otherwise, if they are device-specific, why would
they not be device attributes (e.g. via dev_groups for example),
instead of firmware-attributes?

> > +static void galaxybook_fw_attr_class_remove(void *data)
> > +{
> > +     device_destroy(fw_attr_class, MKDEV(0, 0));
>
> Please use device_unregister() instead since multiple devices might share the same devt of MKDEV(0, 0).
> This would also allow you to remove the global variable "fw_attr_class".
>

Here I am a bit confused on exactly how this would/should look; all
existing usages of fw_attr_class I can see use exactly this same
pattern: device_create() and then device_destroy() with MKDEV(0, 0).
Taking a look at the latest proposed changes from Thomas and it stil
seems the intention is the same, just that it is slightly simplified
and use pointer to the firmware_attributes_class directly instead of
fetching it using fw_attributes_class_get(). Or is there a better way
to do this (including usage of device_unregister() and/or something
different with the devt) that will help solve some other problem(s)?

> > +     sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
> > +     galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
> > +     galaxybook->camera_lens_cover_attr.attr.mode = 0644;
> > +     galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
> > +     galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
> > +     err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
> > +                             &galaxybook->camera_lens_cover_attr.attr);
> > +     if (err)
> > +             return err;
> > +     return devm_add_action_or_reset(&galaxybook->platform->dev,
> > +                                     galaxybook_camera_lens_cover_attr_remove, galaxybook);
>
> That is not how the firmware attribute interface is supposed to work. For each firmware attribute you need to
> create an attribute group (with a unique name of course) with the following attributes:
>
> - type: should return "enumeration"
> - current_value: should return the current value of the firmware attribute
> - default_value: should return the default value of the firmware attribute
> - display_name: should contain a user friendly description of the firmware attribute
> - display_name_language_code: should return "en"
> - possible_values: should return "0;1" since this firmware attributes are boolean values
>
> You can theoretically use sysfs_create_groups() to add all groups in one go to simplify error handling. Since each
> attribute_group specifies a .is_visible callback you can handle the visibility of each group there.
>
> Those groups then need to be added to the fw_attrs_kset.
>

I guess as a follow-on to my earlier question regarding
firmware-attributes; here I was assuming that as these are very simple
"on vs off" attributes, I used the attribute "pending_reboot" as a
pattern for what I implemented here as my "best guess" on what to do
:) As there are not very many examples to look at then it was a bit of
a "best guess" on my part; apologies if I completely missed the boat!
I can of course add these entire groups but IMHO it does seem like
quite a bit of overkill to have all of these various attributes for
on/off or enabled/disabled kind of boolean switches -- my guess is
that if it is somehow "known" that an attribute is a boolean, then it
is relatively self-explanatory and the need for current / default /
possible_values etc attributes within this enumeration type group
should not be needed?  (this is why I followed "pending_reboot" as a
pattern when I did this, but as said I can change this to whatever is
deemed appropriate).

Also there are several other platform drivers that implement a very
similar device attribute as ones that I have added here as a firmware
attribute (namely I am thinking of "USB Charging" which exists in
several other pdx86 drivers but a few other devices should/would
probably support this kind of "Power on Lid Open" attribute as well);
in the event that maintainers of those drivers should and eventually
do migrate over to use the same or similar firmware attribute for this
same kind of setting, should it include all of these attributes in the
standard "enumeration" type attribute group or is it possible / would
it make sense to have some sort of boolean-based fw attr type that is
a bit more simple and a bit more self-explanatory?

> Just a small question: is the value of the camera lens cover persistent across reboots?
>

No (and I tested again to confirm), this "block recording" ACPI
setting does not persist over reboots. Should this one be a device
attribute (e.g. via dev_groups) instead of a firmware attribute in
that case?

> > +     /*
> > +      * Value returned in iob0 will have the number of supported performance modes.
> > +      * The performance mode values will then be given as a list after this (iob1-iobX).
> > +      * Loop backwards from last value to first value (to handle fallback cases which come with
> > +      * smaller values) and map each supported value to its correct platform_profile_option.
> > +      */
> > +     for (i = buf.iob0; i > 0; i--) {
> > +             /*
> > +              * Prefer mapping to at least performance, balanced, and low-power profiles, as they
> > +              * are the profiles which are typically supported by userspace tools
> > +              * (power-profiles-daemon, etc).
> > +              * - performance = "ultra", otherwise "performance"
> > +              * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
> > +              * - low-power   = "silent", otherwise "quiet"
> > +              * Different models support different modes. Additional supported modes will be
> > +              * mapped to profiles that fall in between these 3.
> > +              */
>
> To be honest i would prefer if you remove this overly complicated mapping algorithm. I rather suggest that the
> userspace utilities in question are updated to handle such situations themself (other drivers would also benefit
> from this).
>
> I think the following static mappings would make sense:
>
> PERFORMANCE_MODE_ULTRA -> performance
> PERFORMANCE_MODE_PERFORMANCE -> balanced-performance
> PERFORMANCE_MODE_OPTIMIZED -> balanced
> PERFORMANCE_MODE_QUIET -> quiet
> PERFORMANCE_MODE_SILENT -> low-power
>
> The legacy performance modes should not override other performance modes, i. e. PERFORMANCE_MODE_PERFORMANCE_LEGACY
> should not override PERFORMANCE_MODE_PERFORMANCE. However non-legacy performance modes should override legacy
> performance modes.
>
> If you can be sure that legacy performance modes are not mixed with non-legacy performance modes then you can omit
> the override mechanism.
>

This whole thing was a bit "tricky" and the reason why I built the
logic in the way I did is that there are so many variations in these
devices which have different modes enabled depending on the hardware
and what generation (keep in mind that there are around 20-30
different models as of this writing that work with this driver and
many of them have slight variations on what hardware exists and/or
which modes are supported for various features including this
"performance mode"!).

For background, here is the original GitHub issue where I worked with
my community to initially go from hard-coded modes to dynamic based on
response from the ACPI method which gives the list of "supported
modes": https://github.com/joshuagrisham/samsung-galaxybook-extras/issues/31

Basically, some devices only have 2 "actively used" performance modes,
some have 3, and some have 4. Some devices only have the "legacy"
modes, but newer devices report (according to the ACPI method +
payload that responds with "supported modes" on said device) to
support BOTH the "legacy" and the "newer" modes, but in Windows they
are only using the new modes, while "legacy" modes are ignored by the
Samsung-developed apps and services in Windows.

The response from the "supported performance modes" method gives the
total number of supported "modes" followed by a list of each of them,
and will look something like this (using enum names here to hopefully
help make more sense, but leaving out my prefix "PERFORMANCE_MODE_"
for brevity...):

On my "Pro" Galaxy Book, it looks like this:

6 (# of supported modes), OPTIMIZED_LEGACY, PERFORMANCE_LEGACY,
OPTIMIZED, QUIET, SILENT, PERFORMANCE

Because I have seen that upower + GNOME integration does not even
really work unless you have all three of low-power + balanced +
performance available, then my goal was to map the above modes to
these profiles:

PERFORMANCE -> performance
OPTIMIZED -> balanced
QUIET -> quiet
SILENT -> low-power

(and, just as in Windows, ignore the "legacy" modes as I have a valid
non-legacy mode to cover each different one)

On the "Ultra" line of Galaxy Books, it looks like this:

6, OPTIMIZED_LEGACY, PERFORMANCE_LEGACY, OPTIMIZED, QUIET, PERFORMANCE, ULTRA

(so no SILENT, but add an ULTRA...)

In this case, in order to ensure to map at least low-power + balanced
+ performance and fit the rest in between, I would want:

ULTRA -> performance
PERFORMANCE -> balanced-performance
OPTIMIZED -> balanced
QUIET -> low-power

Both of these examples match exactly how these devices also work in
Windows with Samsung's own developed applications and services.
Namely, that when the newer modes exist, they use them instead of the
"legacy" modes even though the ACPI method includes all of them as
"supported." I think it would be good to maintain this behavior, as
these are the values which Samsung is supporting already in Windows
and the others are potentially untested and I would worry about
potential issues including overheating or otherwise harming the device
in some way.

In cases where only the "legacy" modes exist, then those are the modes
that are used in Windows (e.g. for some devices with SAM0427) and the
ACPI method does not respond with anything except the legacy mode
values. Some of the SAM0427 devices I have seen can look like this,
for example:

5, OPTIMIZED_LEGACY, PERFORMANCE_LEGACY, QUIET, SILENT, PERFORMANCE

In this case, we want to map like this:

PERFORMANCE -> performance
OPTIMIZED_LEGACY -> balanced
QUIET -> quiet
SILENT -> low-power

(using OPTIMIZED_LEGACY for balanced, as OPTIMIZED does not exist on
this device, but there is a non-legacy mode for all of the others that
should be used)

So, with all of that background in mind, my idea and what I
implemented was to basically take the list provided in response from
the ACPI method, start from the end, looping backwards, and try to map
them one-at-a-time, all the while checking if the desired profile for
the given performance mode was already mapped or not, and if so,
either "fitting it in" to another profile or just ignoring it.

I am quite certain that the code can be cleaned up and/or refactored a
bit, but I would hope that the result of the logic should stay the
same (per what I described above); having said all of that, does it
still make sense to try and simplify this somehow and/or any tips or
recommendation how to achieve the desired result in a better way?

> > +     if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)
>
> Please use led_get_brightness() here.
>

When I looked at this originally I thought it would make sense to do
this, but then found that this function is not part of leds.h in the
kernel headers but instead you would have to include both
<linux/leds.h> and also  "../../leds/leds.h" as a file header from the
tree. Also, apart from led-core, there is only one LED driver that
uses the function currently .. does this seem reasonable to include
this extra file-based header or would it make more sense to just read
the value of the member directly as all of the other drivers that need
to do this work currently?

(FWIW I did test to include this header file and it works fine to use
the function instead, it just feels a bit "off"...)

> > +static void galaxybook_remove(struct platform_device *pdev)
> > +{
> > +     if (galaxybook_ptr)
> > +             galaxybook_ptr = NULL;
>
> As already being said, this will cause issues with the i8042 filter. I suggest you move the whole galaxybook_ptr
> handling inside galaxybook_i8042_filter_install()/_remove().
>

Yes I think this makes sense as well and is what I will do in the next
version in case your patch to pass a context pointer to i8042 filter
does not go before then :)

> All things considered the driver looks quite good, hoping for a v5 revision in the future :).
>
> Thanks,
> Armin Wolf
>

Yes it has taken me a few days to get back and dig into this again due
to the holidays, but am looking through it all again and will
hopefully have a new version soon-ish to try and resolve more of the
open issues. Thank you again for all of your help!!

Best,
Joshua
Armin Wolf Jan. 4, 2025, 6:27 a.m. UTC | #8
Am 04.01.25 um 02:07 schrieb Joshua Grisham:

> Thanks again Armin for all of the very detailed comments -- they are
> always super helpful!! A few things I think are pretty obvious and I
> agree with, as well as where I missed as copy/paste when moving around
> stuff that you caught (great!), so I will not bother to respond to
> those here and will instead just fix them. :) For other things where I
> have some questions, I will respond inline below.
>
> Den tors 2 jan. 2025 kl 20:14 skrev Armin Wolf <W_Armin@gmx.de>:
>>> +What:                /sys/class/firmware-attributes/*/attributes/usb_charging
>>> +Date:                December 2024
>>> +KernelVersion:       6.13
>>> +Contact:     Joshua Grisham <josh@joshuagrisham.com>
>>> +Description:
>>> +             This attribute can be used to control if USB ports can continue to deliver power to
>>> +             connected devices when the device is powered off or in a low sleep state. The value
>>> +             is a boolean represented by 0 for false and 1 for true.
>> Hi,
>>
>> please move the documentation of the firmware attributes to samsung-galaxybook.rst to avoid cluttering
>> the subsystem docs with too much driver-specific entries.
>>
> I guess I am a bit confused by the intention and usage
> firmware-attributes in general (including what should be in this
> documentation vs not) -- is the idea that these should be "relatively
> generic" attributes that control settings in the firmware that can
> persist across reboots and or steer the firmware/hardware in various
> ways (e.g. with admin password and/or pending reboot status etc) ? And
> if they are "relatively generic" (e.g. could be reused by more than
> one platform driver) then would the documentation belong here in a
> centralized place? Otherwise, if they are device-specific, why would
> they not be device attributes (e.g. via dev_groups for example),
> instead of firmware-attributes?

The reason for the firmware-attribute class original was that driver could export BIOS settings
to userspace applications, together with some metadata (min/max values, etc).

Because of this the exact meaning of each firmware attribute is usually only known to the user
changing those firmware attributes.

Your driver is a bit special in that it knows the meaning of each firmware attribute. However
you still have to follow the firmware-attribute class ABI since userspace applications do not
know this.

>
>>> +static void galaxybook_fw_attr_class_remove(void *data)
>>> +{
>>> +     device_destroy(fw_attr_class, MKDEV(0, 0));
>> Please use device_unregister() instead since multiple devices might share the same devt of MKDEV(0, 0).
>> This would also allow you to remove the global variable "fw_attr_class".
>>
> Here I am a bit confused on exactly how this would/should look; all
> existing usages of fw_attr_class I can see use exactly this same
> pattern: device_create() and then device_destroy() with MKDEV(0, 0).
> Taking a look at the latest proposed changes from Thomas and it stil
> seems the intention is the same, just that it is slightly simplified
> and use pointer to the firmware_attributes_class directly instead of
> fetching it using fw_attributes_class_get(). Or is there a better way
> to do this (including usage of device_unregister() and/or something
> different with the devt) that will help solve some other problem(s)?

This is the code of device_destroy():

void device_destroy(const struct class *class, dev_t devt)
{
	struct device *dev;

	dev = class_find_device_by_devt(class, devt);
	if (dev) {
		put_device(dev);
		device_unregister(dev);
	}
}

if multiple devices of a given class are using the same devt (like MKDEV(0, 0)) then
class_find_device_by_devt() might pick the wrong device.

The fact that the other drivers are using this function is actually an error. The only
reason why this error was not noticed until now seems to be that currently only a single
driver using the firmware-attribute class is typically active at the same time.

>
>>> +     sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
>>> +     galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
>>> +     galaxybook->camera_lens_cover_attr.attr.mode = 0644;
>>> +     galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
>>> +     galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
>>> +     err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
>>> +                             &galaxybook->camera_lens_cover_attr.attr);
>>> +     if (err)
>>> +             return err;
>>> +     return devm_add_action_or_reset(&galaxybook->platform->dev,
>>> +                                     galaxybook_camera_lens_cover_attr_remove, galaxybook);
>> That is not how the firmware attribute interface is supposed to work. For each firmware attribute you need to
>> create an attribute group (with a unique name of course) with the following attributes:
>>
>> - type: should return "enumeration"
>> - current_value: should return the current value of the firmware attribute
>> - default_value: should return the default value of the firmware attribute
>> - display_name: should contain a user friendly description of the firmware attribute
>> - display_name_language_code: should return "en"
>> - possible_values: should return "0;1" since this firmware attributes are boolean values
>>
>> You can theoretically use sysfs_create_groups() to add all groups in one go to simplify error handling. Since each
>> attribute_group specifies a .is_visible callback you can handle the visibility of each group there.
>>
>> Those groups then need to be added to the fw_attrs_kset.
>>
> I guess as a follow-on to my earlier question regarding
> firmware-attributes; here I was assuming that as these are very simple
> "on vs off" attributes, I used the attribute "pending_reboot" as a
> pattern for what I implemented here as my "best guess" on what to do
> :) As there are not very many examples to look at then it was a bit of
> a "best guess" on my part; apologies if I completely missed the boat!
> I can of course add these entire groups but IMHO it does seem like
> quite a bit of overkill to have all of these various attributes for
> on/off or enabled/disabled kind of boolean switches -- my guess is
> that if it is somehow "known" that an attribute is a boolean, then it
> is relatively self-explanatory and the need for current / default /
> possible_values etc attributes within this enumeration type group
> should not be needed?  (this is why I followed "pending_reboot" as a
> pattern when I did this, but as said I can change this to whatever is
> deemed appropriate).

Keep in mind that userspace applications usually do not know the meaning of such a firmware attribute.
This is why you need to supply type, current_value, etc, so that those userspace applications know enough
about the firmware attribute to show a nice settings GUI to the user (which will then use display_name to
understand the meaning of the firmware attribute).

> Also there are several other platform drivers that implement a very
> similar device attribute as ones that I have added here as a firmware
> attribute (namely I am thinking of "USB Charging" which exists in
> several other pdx86 drivers but a few other devices should/would
> probably support this kind of "Power on Lid Open" attribute as well);
> in the event that maintainers of those drivers should and eventually
> do migrate over to use the same or similar firmware attribute for this
> same kind of setting, should it include all of these attributes in the
> standard "enumeration" type attribute group or is it possible / would
> it make sense to have some sort of boolean-based fw attr type that is
> a bit more simple and a bit more self-explanatory?

Introducing a new "boolean" type would indeed be nice. This would allow userspace application to use a simple
on/off slider instead of a dropdown menu when displaying such firmware attributes.

In this case you could drop the "possible_values" attribute.

What is the opinion of the pdx86 maintainers on this proposal?

>> Just a small question: is the value of the camera lens cover persistent across reboots?
>>
> No (and I tested again to confirm), this "block recording" ACPI
> setting does not persist over reboots. Should this one be a device
> attribute (e.g. via dev_groups) instead of a firmware attribute in
> that case?

Good question, i will defer to the pdx86 maintainers for answering that question.

>>> +     /*
>>> +      * Value returned in iob0 will have the number of supported performance modes.
>>> +      * The performance mode values will then be given as a list after this (iob1-iobX).
>>> +      * Loop backwards from last value to first value (to handle fallback cases which come with
>>> +      * smaller values) and map each supported value to its correct platform_profile_option.
>>> +      */
>>> +     for (i = buf.iob0; i > 0; i--) {
>>> +             /*
>>> +              * Prefer mapping to at least performance, balanced, and low-power profiles, as they
>>> +              * are the profiles which are typically supported by userspace tools
>>> +              * (power-profiles-daemon, etc).
>>> +              * - performance = "ultra", otherwise "performance"
>>> +              * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
>>> +              * - low-power   = "silent", otherwise "quiet"
>>> +              * Different models support different modes. Additional supported modes will be
>>> +              * mapped to profiles that fall in between these 3.
>>> +              */
>> To be honest i would prefer if you remove this overly complicated mapping algorithm. I rather suggest that the
>> userspace utilities in question are updated to handle such situations themself (other drivers would also benefit
>> from this).
>>
>> I think the following static mappings would make sense:
>>
>> PERFORMANCE_MODE_ULTRA -> performance
>> PERFORMANCE_MODE_PERFORMANCE -> balanced-performance
>> PERFORMANCE_MODE_OPTIMIZED -> balanced
>> PERFORMANCE_MODE_QUIET -> quiet
>> PERFORMANCE_MODE_SILENT -> low-power
>>
>> The legacy performance modes should not override other performance modes, i. e. PERFORMANCE_MODE_PERFORMANCE_LEGACY
>> should not override PERFORMANCE_MODE_PERFORMANCE. However non-legacy performance modes should override legacy
>> performance modes.
>>
>> If you can be sure that legacy performance modes are not mixed with non-legacy performance modes then you can omit
>> the override mechanism.
>>
> This whole thing was a bit "tricky" and the reason why I built the
> logic in the way I did is that there are so many variations in these
> devices which have different modes enabled depending on the hardware
> and what generation (keep in mind that there are around 20-30
> different models as of this writing that work with this driver and
> many of them have slight variations on what hardware exists and/or
> which modes are supported for various features including this
> "performance mode"!).
>
> For background, here is the original GitHub issue where I worked with
> my community to initially go from hard-coded modes to dynamic based on
> response from the ACPI method which gives the list of "supported
> modes": https://github.com/joshuagrisham/samsung-galaxybook-extras/issues/31
>
> Basically, some devices only have 2 "actively used" performance modes,
> some have 3, and some have 4. Some devices only have the "legacy"
> modes, but newer devices report (according to the ACPI method +
> payload that responds with "supported modes" on said device) to
> support BOTH the "legacy" and the "newer" modes, but in Windows they
> are only using the new modes, while "legacy" modes are ignored by the
> Samsung-developed apps and services in Windows.
>
> The response from the "supported performance modes" method gives the
> total number of supported "modes" followed by a list of each of them,
> and will look something like this (using enum names here to hopefully
> help make more sense, but leaving out my prefix "PERFORMANCE_MODE_"
> for brevity...):
>
> On my "Pro" Galaxy Book, it looks like this:
>
> 6 (# of supported modes), OPTIMIZED_LEGACY, PERFORMANCE_LEGACY,
> OPTIMIZED, QUIET, SILENT, PERFORMANCE
>
> Because I have seen that upower + GNOME integration does not even
> really work unless you have all three of low-power + balanced +
> performance available, then my goal was to map the above modes to
> these profiles:
>
> PERFORMANCE -> performance
> OPTIMIZED -> balanced
> QUIET -> quiet
> SILENT -> low-power
>
> (and, just as in Windows, ignore the "legacy" modes as I have a valid
> non-legacy mode to cover each different one)
>
> On the "Ultra" line of Galaxy Books, it looks like this:
>
> 6, OPTIMIZED_LEGACY, PERFORMANCE_LEGACY, OPTIMIZED, QUIET, PERFORMANCE, ULTRA
>
> (so no SILENT, but add an ULTRA...)
>
> In this case, in order to ensure to map at least low-power + balanced
> + performance and fit the rest in between, I would want:
>
> ULTRA -> performance
> PERFORMANCE -> balanced-performance
> OPTIMIZED -> balanced
> QUIET -> low-power
>
> Both of these examples match exactly how these devices also work in
> Windows with Samsung's own developed applications and services.
> Namely, that when the newer modes exist, they use them instead of the
> "legacy" modes even though the ACPI method includes all of them as
> "supported." I think it would be good to maintain this behavior, as
> these are the values which Samsung is supporting already in Windows
> and the others are potentially untested and I would worry about
> potential issues including overheating or otherwise harming the device
> in some way.
>
> In cases where only the "legacy" modes exist, then those are the modes
> that are used in Windows (e.g. for some devices with SAM0427) and the
> ACPI method does not respond with anything except the legacy mode
> values. Some of the SAM0427 devices I have seen can look like this,
> for example:
>
> 5, OPTIMIZED_LEGACY, PERFORMANCE_LEGACY, QUIET, SILENT, PERFORMANCE
>
> In this case, we want to map like this:
>
> PERFORMANCE -> performance
> OPTIMIZED_LEGACY -> balanced
> QUIET -> quiet
> SILENT -> low-power
>
> (using OPTIMIZED_LEGACY for balanced, as OPTIMIZED does not exist on
> this device, but there is a non-legacy mode for all of the others that
> should be used)
>
> So, with all of that background in mind, my idea and what I
> implemented was to basically take the list provided in response from
> the ACPI method, start from the end, looping backwards, and try to map
> them one-at-a-time, all the while checking if the desired profile for
> the given performance mode was already mapped or not, and if so,
> either "fitting it in" to another profile or just ignoring it.
>
> I am quite certain that the code can be cleaned up and/or refactored a
> bit, but I would hope that the result of the logic should stay the
> same (per what I described above); having said all of that, does it
> still make sense to try and simplify this somehow and/or any tips or
> recommendation how to achieve the desired result in a better way?

I am OK with you preferring the non-legacy modes over the legacy ones. However trying to limit yourself
to the profiles currently supported by gnome (AFAIK uses platform-profiles-daemon) is not a good idea.

I would like to see a more static mapping be implemented:

PERFORMANCE_MODE_ULTRA -> performance
PERFORMANCE_MODE_PERFORMANCE -> balanced-performance (can also be legacy if non-legacy is not available)
PERFORMANCE_MODE_OPTIMIZED -> balanced (can also be legacy is non-legacy is not available)
PERFORMANCE_MODE_QUIET -> quiet
PERFORMANCE_MODE_SILENT -> low-power

In this case the platform-profiles-daemon would have a similar job as the Samsung service, which is to
determine a suitable mapping for the supported modes to {performance, balanced, powersave}.

Looking at the code of the daemon it seems something similar is already being done, but only for the profiles
"quiet" and "low-power" (one of which is getting mapped to the "powersave" mode).

I am confident that the daemon could be extended be a bit more intelligent when it comes to determine the
mapping of the other modes.

>>> +     if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)
>> Please use led_get_brightness() here.
>>
> When I looked at this originally I thought it would make sense to do
> this, but then found that this function is not part of leds.h in the
> kernel headers but instead you would have to include both
> <linux/leds.h> and also  "../../leds/leds.h" as a file header from the
> tree. Also, apart from led-core, there is only one LED driver that
> uses the function currently .. does this seem reasonable to include
> this extra file-based header or would it make more sense to just read
> the value of the member directly as all of the other drivers that need
> to do this work currently?
>
> (FWIW I did test to include this header file and it works fine to use
> the function instead, it just feels a bit "off"...)

Oops, my bad. In this case you can keep using the .brightness member of the LED device.

Thanks,
Armin Wolf

>
>>> +static void galaxybook_remove(struct platform_device *pdev)
>>> +{
>>> +     if (galaxybook_ptr)
>>> +             galaxybook_ptr = NULL;
>> As already being said, this will cause issues with the i8042 filter. I suggest you move the whole galaxybook_ptr
>> handling inside galaxybook_i8042_filter_install()/_remove().
>>
> Yes I think this makes sense as well and is what I will do in the next
> version in case your patch to pass a context pointer to i8042 filter
> does not go before then :)
>
>> All things considered the driver looks quite good, hoping for a v5 revision in the future :).
>>
>> Thanks,
>> Armin Wolf
>>
> Yes it has taken me a few days to get back and dig into this again due
> to the holidays, but am looking through it all again and will
> hopefully have a new version soon-ish to try and resolve more of the
> open issues. Thank you again for all of your help!!
>
> Best,
> Joshua
>
Joshua Grisham Jan. 7, 2025, 3:09 p.m. UTC | #9
Hi again Armin! I think I am finally with you on most of this, I think
jet lag and general craziness made me a little extra dense for a week
or two :)

Den lör 4 jan. 2025 kl 07:28 skrev Armin Wolf <W_Armin@gmx.de>:
>
> The reason for the firmware-attribute class original was that driver could export BIOS settings
> to userspace applications, together with some metadata (min/max values, etc).
>
> Because of this the exact meaning of each firmware attribute is usually only known to the user
> changing those firmware attributes.
>
> Your driver is a bit special in that it knows the meaning of each firmware attribute. However
> you still have to follow the firmware-attribute class ABI since userspace applications do not
> know this.
>

Yes ok, as said, I am with you all now on this I think :)

As a prototype for v5 I have created a new struct for each "firmware
attribute" that helps me keep everything linked together with all of
the different sub attributes for each different "fw attribute"
including allowing a link back to my samsung_galaxybook instance
without using the global pointer. At the end of the day, if I wanted
to avoid using a global pointer, I needed a way to grab the private
data based on either the kobj or the attr parameters to the show/store
method of these individual sub attributes within each "firmware
attribute", so what I have done is added the kobj_attribute as a
struct member and then manually init+filled this kobj_attributes
during probe, so I can now grab the parent struct instance using
container_of() within the show/store functions which then gets me to
my pointer. I thought about using the kset or something else for this
but it seemed like kobj_attribute supported being a struct member
better and gave the least amount of headaches from what I could tell.

After trying to fight my way through this problem, I have an idea of
what a better "dream scenario" would for me as a user/consumer of the
firmware attributes interface -- namely that there is some kind of way
to register and unregister by "type" (e.g. "I want a new enumeration
fw attr; here is its parent, its name, and all of the functions for
show/store of the required attributes, plus a data pointer that I can
pack together with my attribute/somehow reach within the show/store
functions"). I have handled a bit of this myself now in the working v5
of samsung-galaxybook (just a minimal version of what it requires) but
as said it currently relies on creating the kobj_attributes (at least
those where I need the pointer) as struct members that I can later use
with container_of() instead of creating static ones using the various
__ATTR.. macros.

Please feel free to say if any of this sounds totally (or partially?)
off, otherwise I will try to test a bit more, clean up, and work
through any checkpatch exceptions and get this sent as a v5.

> >>> +static void galaxybook_fw_attr_class_remove(void *data)
> >>> +{
> >>> +     device_destroy(fw_attr_class, MKDEV(0, 0));
> >> Please use device_unregister() instead since multiple devices might share the same devt of MKDEV(0, 0).
> >> This would also allow you to remove the global variable "fw_attr_class".
> >>
> > Here I am a bit confused on exactly how this would/should look; all
> > existing usages of fw_attr_class I can see use exactly this same
> > pattern: device_create() and then device_destroy() with MKDEV(0, 0).
> > Taking a look at the latest proposed changes from Thomas and it stil
> > seems the intention is the same, just that it is slightly simplified
> > and use pointer to the firmware_attributes_class directly instead of
> > fetching it using fw_attributes_class_get(). Or is there a better way
> > to do this (including usage of device_unregister() and/or something
> > different with the devt) that will help solve some other problem(s)?
>
> This is the code of device_destroy():
>
> void device_destroy(const struct class *class, dev_t devt)
> {
>         struct device *dev;
>
>         dev = class_find_device_by_devt(class, devt);
>         if (dev) {
>                 put_device(dev);
>                 device_unregister(dev);
>         }
> }
>
> if multiple devices of a given class are using the same devt (like MKDEV(0, 0)) then
> class_find_device_by_devt() might pick the wrong device.
>
> The fact that the other drivers are using this function is actually an error. The only
> reason why this error was not noticed until now seems to be that currently only a single
> driver using the firmware-attribute class is typically active at the same time.
>

Yes again sorry for being dense -- now with a little sleep and time to
marinate this makes total sense, and it is a lot easier to just use
device_unregister() like you say. This will be included in v5.

> > Also there are several other platform drivers that implement a very
> > similar device attribute as ones that I have added here as a firmware
> > attribute (namely I am thinking of "USB Charging" which exists in
> > several other pdx86 drivers but a few other devices should/would
> > probably support this kind of "Power on Lid Open" attribute as well);
> > in the event that maintainers of those drivers should and eventually
> > do migrate over to use the same or similar firmware attribute for this
> > same kind of setting, should it include all of these attributes in the
> > standard "enumeration" type attribute group or is it possible / would
> > it make sense to have some sort of boolean-based fw attr type that is
> > a bit more simple and a bit more self-explanatory?
>
> Introducing a new "boolean" type would indeed be nice. This would allow userspace application to use a simple
> on/off slider instead of a dropdown menu when displaying such firmware attributes.
>
> In this case you could drop the "possible_values" attribute.
>
> What is the opinion of the pdx86 maintainers on this proposal?
>

Now that I have finally taken a better understanding of this, I see
your point. Yes, nice with a boolean that could give a slider in a GUI
or similar, but does not really change a whole lot in the driver
implementation. I will go with enumeration type for now as mentioned
and it can always be changed later if this new type comes.

> > I am quite certain that the code can be cleaned up and/or refactored a
> > bit, but I would hope that the result of the logic should stay the
> > same (per what I described above); having said all of that, does it
> > still make sense to try and simplify this somehow and/or any tips or
> > recommendation how to achieve the desired result in a better way?
>
> I am OK with you preferring the non-legacy modes over the legacy ones. However trying to limit yourself
> to the profiles currently supported by gnome (AFAIK uses platform-profiles-daemon) is not a good idea.
>
> I would like to see a more static mapping be implemented:
>
> PERFORMANCE_MODE_ULTRA -> performance
> PERFORMANCE_MODE_PERFORMANCE -> balanced-performance (can also be legacy if non-legacy is not available)
> PERFORMANCE_MODE_OPTIMIZED -> balanced (can also be legacy is non-legacy is not available)
> PERFORMANCE_MODE_QUIET -> quiet
> PERFORMANCE_MODE_SILENT -> low-power
>
> In this case the platform-profiles-daemon would have a similar job as the Samsung service, which is to
> determine a suitable mapping for the supported modes to {performance, balanced, powersave}.
>
> Looking at the code of the daemon it seems something similar is already being done, but only for the profiles
> "quiet" and "low-power" (one of which is getting mapped to the "powersave" mode).
>
> I am confident that the daemon could be extended be a bit more intelligent when it comes to determine the
> mapping of the other modes.
>

I understand the thought here but my only problem and what sort of
"itches" at me is that most of these devices are not "Ultra" models
and they will never have an "Ultra" mode. For the non-Ultra models,
"Performance mode" *is* "Performance mode" (meaning, it is the mode
which prioritizes performance over anything else) so for me it feels
best if these non-Ultra models (again majority of these devices) can
have the Performance mode that they should have. And you can maybe
argue that "Ultra" is in fact its own mode entirely -- when you use
this mode on these devices, they really scream (the fans, mostly, that
is) and they get super hot haha :)

Other than this Ultra vs Performance question, I do agree with you and
think it makes sense. My first thought if we want to actually
"simplify" this in this way is if there could actually exist a
platform profile called "ultra" then it would be just a perfect 1:1
mapping (other than taking legacy modes into account).

This "perfect fit" for samsung-galaxybook would be to create a new
platform profile called something like PLATFORM_PROFILE_ULTRA, but
that seems like a bit of a tall order... Would it make more sense to
implement this "ultra" mode using the new PLATFORM_PROFILE_CUSTOM and
then map them like this?

PERFORMANCE_MODE_ULTRA -> custom (named "ultra" if that is possible?)
PERFORMANCE_MODE_PERFORMANCE (or PERFORMANCE_MODE_PERFORMANCE_LEGACY)
-> performance
PERFORMANCE_MODE_OPTIMIZED (or PERFORMANCE_MODE_OPTIMIZED_LEGACY) -> balanced
PERFORMANCE_MODE_QUIET -> quiet
PERFORMANCE_MODE_SILENT -> low-power

Thought admittedly I am not 100% familiar with how
PLATFORM_PROFILE_CUSTOM is implemented to work; I have a vague memory
that I read somewhere that this was roughly the intention? But I am
not sure if it is actually implemented to work this way. But if it
will in fact work "out of the box" including with
platform_profile_cycle() for the hotkey then it seems like the
cleanest and easiest approach.

If this is possible, then my best guess for the logic for this mapping
in samsung-galaxybook could be changed to loop the "supported modes"
forwards instead of backwards, and just let the "legacy" modes be
written first (as they seem to always come first in the list), and
then in case the non-legacy mode exists later in the array, it will
just replace the already-mapped legacy value with the new non-legacy
value, and thus skip any kind of condition-based checking/mapping
entirely. Is that sort of more like what you had in mind?

>
> Thanks,
> Armin Wolf
>

Thanks again!

Joshua

> [...]
Thomas Weißschuh Jan. 7, 2025, 5:15 p.m. UTC | #10
On 2025-01-07 16:09:51+0100, Joshua Grisham wrote:
> Hi again Armin! I think I am finally with you on most of this, I think
> jet lag and general craziness made me a little extra dense for a week
> or two :)
> 
> Den lör 4 jan. 2025 kl 07:28 skrev Armin Wolf <W_Armin@gmx.de>:
> >
> > The reason for the firmware-attribute class original was that driver could export BIOS settings
> > to userspace applications, together with some metadata (min/max values, etc).
> >
> > Because of this the exact meaning of each firmware attribute is usually only known to the user
> > changing those firmware attributes.
> >
> > Your driver is a bit special in that it knows the meaning of each firmware attribute. However
> > you still have to follow the firmware-attribute class ABI since userspace applications do not
> > know this.
> >
> 
> Yes ok, as said, I am with you all now on this I think :)
> 
> As a prototype for v5 I have created a new struct for each "firmware
> attribute" that helps me keep everything linked together with all of
> the different sub attributes for each different "fw attribute"
> including allowing a link back to my samsung_galaxybook instance
> without using the global pointer. At the end of the day, if I wanted
> to avoid using a global pointer, I needed a way to grab the private
> data based on either the kobj or the attr parameters to the show/store
> method of these individual sub attributes within each "firmware
> attribute", so what I have done is added the kobj_attribute as a
> struct member and then manually init+filled this kobj_attributes
> during probe, so I can now grab the parent struct instance using
> container_of() within the show/store functions which then gets me to
> my pointer. I thought about using the kset or something else for this
> but it seemed like kobj_attribute supported being a struct member
> better and gave the least amount of headaches from what I could tell.
> 
> After trying to fight my way through this problem, I have an idea of
> what a better "dream scenario" would for me as a user/consumer of the
> firmware attributes interface -- namely that there is some kind of way
> to register and unregister by "type" (e.g. "I want a new enumeration
> fw attr; here is its parent, its name, and all of the functions for
> show/store of the required attributes, plus a data pointer that I can
> pack together with my attribute/somehow reach within the show/store
> functions"). I have handled a bit of this myself now in the working v5
> of samsung-galaxybook (just a minimal version of what it requires) but
> as said it currently relies on creating the kobj_attributes (at least
> those where I need the pointer) as struct members that I can later use
> with container_of() instead of creating static ones using the various
> __ATTR.. macros.
> 
> Please feel free to say if any of this sounds totally (or partially?)
> off, otherwise I will try to test a bit more, clean up, and work
> through any checkpatch exceptions and get this sent as a v5.

Please take a look at my recent series[0].

It provides an API similar to what you propose.
Currently it does not provide an even higher level API with predefined
structs for enumeration attributes etc. That is intentional for now as
it is also meant to handle custom attribute types.
Nothing would prevent a creation of such a convenience API on top of my
series, however.

We'd need some coordination about landing the different patches,
but that shouldn't really be a problem.

> <snip>

Thomas

[0] https://lore.kernel.org/lkml/20250107-pdx86-firmware-attributes-v1-0-9d75c04a3b52@weissschuh.net/
Armin Wolf Jan. 7, 2025, 6:56 p.m. UTC | #11
Am 07.01.25 um 16:09 schrieb Joshua Grisham:

> Hi again Armin! I think I am finally with you on most of this, I think
> jet lag and general craziness made me a little extra dense for a week
> or two :)
>
> Den lör 4 jan. 2025 kl 07:28 skrev Armin Wolf <W_Armin@gmx.de>:
>> The reason for the firmware-attribute class original was that driver could export BIOS settings
>> to userspace applications, together with some metadata (min/max values, etc).
>>
>> Because of this the exact meaning of each firmware attribute is usually only known to the user
>> changing those firmware attributes.
>>
>> Your driver is a bit special in that it knows the meaning of each firmware attribute. However
>> you still have to follow the firmware-attribute class ABI since userspace applications do not
>> know this.
>>
> Yes ok, as said, I am with you all now on this I think :)
>
> As a prototype for v5 I have created a new struct for each "firmware
> attribute" that helps me keep everything linked together with all of
> the different sub attributes for each different "fw attribute"
> including allowing a link back to my samsung_galaxybook instance
> without using the global pointer. At the end of the day, if I wanted
> to avoid using a global pointer, I needed a way to grab the private
> data based on either the kobj or the attr parameters to the show/store
> method of these individual sub attributes within each "firmware
> attribute", so what I have done is added the kobj_attribute as a
> struct member and then manually init+filled this kobj_attributes
> during probe, so I can now grab the parent struct instance using
> container_of() within the show/store functions which then gets me to
> my pointer. I thought about using the kset or something else for this
> but it seemed like kobj_attribute supported being a struct member
> better and gave the least amount of headaches from what I could tell.

Sounds reasonable to me.

> After trying to fight my way through this problem, I have an idea of
> what a better "dream scenario" would for me as a user/consumer of the
> firmware attributes interface -- namely that there is some kind of way
> to register and unregister by "type" (e.g. "I want a new enumeration
> fw attr; here is its parent, its name, and all of the functions for
> show/store of the required attributes, plus a data pointer that I can
> pack together with my attribute/somehow reach within the show/store
> functions"). I have handled a bit of this myself now in the working v5
> of samsung-galaxybook (just a minimal version of what it requires) but
> as said it currently relies on creating the kobj_attributes (at least
> those where I need the pointer) as struct members that I can later use
> with container_of() instead of creating static ones using the various
> __ATTR.. macros.
>
> Please feel free to say if any of this sounds totally (or partially?)
> off, otherwise I will try to test a bit more, clean up, and work
> through any checkpatch exceptions and get this sent as a v5.

I think your current plan sounds good. Thomas already submitted a patch series which
provides a more abstract API for registering firmware attributes.

>>>>> +static void galaxybook_fw_attr_class_remove(void *data)
>>>>> +{
>>>>> +     device_destroy(fw_attr_class, MKDEV(0, 0));
>>>> Please use device_unregister() instead since multiple devices might share the same devt of MKDEV(0, 0).
>>>> This would also allow you to remove the global variable "fw_attr_class".
>>>>
>>> Here I am a bit confused on exactly how this would/should look; all
>>> existing usages of fw_attr_class I can see use exactly this same
>>> pattern: device_create() and then device_destroy() with MKDEV(0, 0).
>>> Taking a look at the latest proposed changes from Thomas and it stil
>>> seems the intention is the same, just that it is slightly simplified
>>> and use pointer to the firmware_attributes_class directly instead of
>>> fetching it using fw_attributes_class_get(). Or is there a better way
>>> to do this (including usage of device_unregister() and/or something
>>> different with the devt) that will help solve some other problem(s)?
>> This is the code of device_destroy():
>>
>> void device_destroy(const struct class *class, dev_t devt)
>> {
>>          struct device *dev;
>>
>>          dev = class_find_device_by_devt(class, devt);
>>          if (dev) {
>>                  put_device(dev);
>>                  device_unregister(dev);
>>          }
>> }
>>
>> if multiple devices of a given class are using the same devt (like MKDEV(0, 0)) then
>> class_find_device_by_devt() might pick the wrong device.
>>
>> The fact that the other drivers are using this function is actually an error. The only
>> reason why this error was not noticed until now seems to be that currently only a single
>> driver using the firmware-attribute class is typically active at the same time.
>>
> Yes again sorry for being dense -- now with a little sleep and time to
> marinate this makes total sense, and it is a lot easier to just use
> device_unregister() like you say. This will be included in v5.

I partly blame the comment of device_destroy(), at first glance it looks like the
natural complement of device_create(), even if its not.

I will see if i can create a patch series to fix this.

>>> Also there are several other platform drivers that implement a very
>>> similar device attribute as ones that I have added here as a firmware
>>> attribute (namely I am thinking of "USB Charging" which exists in
>>> several other pdx86 drivers but a few other devices should/would
>>> probably support this kind of "Power on Lid Open" attribute as well);
>>> in the event that maintainers of those drivers should and eventually
>>> do migrate over to use the same or similar firmware attribute for this
>>> same kind of setting, should it include all of these attributes in the
>>> standard "enumeration" type attribute group or is it possible / would
>>> it make sense to have some sort of boolean-based fw attr type that is
>>> a bit more simple and a bit more self-explanatory?
>> Introducing a new "boolean" type would indeed be nice. This would allow userspace application to use a simple
>> on/off slider instead of a dropdown menu when displaying such firmware attributes.
>>
>> In this case you could drop the "possible_values" attribute.
>>
>> What is the opinion of the pdx86 maintainers on this proposal?
>>
> Now that I have finally taken a better understanding of this, I see
> your point. Yes, nice with a boolean that could give a slider in a GUI
> or similar, but does not really change a whole lot in the driver
> implementation. I will go with enumeration type for now as mentioned
> and it can always be changed later if this new type comes.

Ok.

>
>>> I am quite certain that the code can be cleaned up and/or refactored a
>>> bit, but I would hope that the result of the logic should stay the
>>> same (per what I described above); having said all of that, does it
>>> still make sense to try and simplify this somehow and/or any tips or
>>> recommendation how to achieve the desired result in a better way?
>> I am OK with you preferring the non-legacy modes over the legacy ones. However trying to limit yourself
>> to the profiles currently supported by gnome (AFAIK uses platform-profiles-daemon) is not a good idea.
>>
>> I would like to see a more static mapping be implemented:
>>
>> PERFORMANCE_MODE_ULTRA -> performance
>> PERFORMANCE_MODE_PERFORMANCE -> balanced-performance (can also be legacy if non-legacy is not available)
>> PERFORMANCE_MODE_OPTIMIZED -> balanced (can also be legacy is non-legacy is not available)
>> PERFORMANCE_MODE_QUIET -> quiet
>> PERFORMANCE_MODE_SILENT -> low-power
>>
>> In this case the platform-profiles-daemon would have a similar job as the Samsung service, which is to
>> determine a suitable mapping for the supported modes to {performance, balanced, powersave}.
>>
>> Looking at the code of the daemon it seems something similar is already being done, but only for the profiles
>> "quiet" and "low-power" (one of which is getting mapped to the "powersave" mode).
>>
>> I am confident that the daemon could be extended be a bit more intelligent when it comes to determine the
>> mapping of the other modes.
>>
> I understand the thought here but my only problem and what sort of
> "itches" at me is that most of these devices are not "Ultra" models
> and they will never have an "Ultra" mode. For the non-Ultra models,
> "Performance mode" *is* "Performance mode" (meaning, it is the mode
> which prioritizes performance over anything else) so for me it feels
> best if these non-Ultra models (again majority of these devices) can
> have the Performance mode that they should have. And you can maybe
> argue that "Ultra" is in fact its own mode entirely -- when you use
> this mode on these devices, they really scream (the fans, mostly, that
> is) and they get super hot haha :)

Is this non-ultra performance mode any different than the ultra performance mode
in terms of performance gains, fan speed, etc?

> Other than this Ultra vs Performance question, I do agree with you and
> think it makes sense. My first thought if we want to actually
> "simplify" this in this way is if there could actually exist a
> platform profile called "ultra" then it would be just a perfect 1:1
> mapping (other than taking legacy modes into account).
>
> This "perfect fit" for samsung-galaxybook would be to create a new
> platform profile called something like PLATFORM_PROFILE_ULTRA, but
> that seems like a bit of a tall order... Would it make more sense to
> implement this "ultra" mode using the new PLATFORM_PROFILE_CUSTOM and
> then map them like this?
>
> PERFORMANCE_MODE_ULTRA -> custom (named "ultra" if that is possible?)
> PERFORMANCE_MODE_PERFORMANCE (or PERFORMANCE_MODE_PERFORMANCE_LEGACY)
> -> performance
> PERFORMANCE_MODE_OPTIMIZED (or PERFORMANCE_MODE_OPTIMIZED_LEGACY) -> balanced
> PERFORMANCE_MODE_QUIET -> quiet
> PERFORMANCE_MODE_SILENT -> low-power
>
> Thought admittedly I am not 100% familiar with how
> PLATFORM_PROFILE_CUSTOM is implemented to work; I have a vague memory
> that I read somewhere that this was roughly the intention? But I am
> not sure if it is actually implemented to work this way. But if it
> will in fact work "out of the box" including with
> platform_profile_cycle() for the hotkey then it seems like the
> cleanest and easiest approach.

PLATFORM_PROFILE_CUSTOM is meant to signal that the platform is not in a well-defined
profile state, usually due to manual tuning. So please do not use it for ULTRA.

>
> If this is possible, then my best guess for the logic for this mapping
> in samsung-galaxybook could be changed to loop the "supported modes"
> forwards instead of backwards, and just let the "legacy" modes be
> written first (as they seem to always come first in the list), and
> then in case the non-legacy mode exists later in the array, it will
> just replace the already-mapped legacy value with the new non-legacy
> value, and thus skip any kind of condition-based checking/mapping
> entirely. Is that sort of more like what you had in mind?

Can you be sure that legacy performance modes are always placed before non-legacy
performance modes?

If no then i suggest that you iterate over all supported modes and if you encounter
a legacy performance mode you check if the associated platform profile slot was already
taken by a non-legacy performance mode. If that is the case you ignore that legacy performance
mode.

If you are sure that the order is always the same then you can of course simplify this by
iterating forward. I will leave it to you to choose which one to implement, as you seem
to have more knowledge about the underlying hardware than me.

Thanks,
Armin Wolf

>> Thanks,
>> Armin Wolf
>>
> Thanks again!
>
> Joshua
>
>> [...]
Joshua Grisham Jan. 8, 2025, 9:37 p.m. UTC | #12
Hi Thomas! I was prepping my v5 patch to send in and trying to figure
out everything I changed for the change list comments, but I stumbled
on a few comments here that I wanted to ask you about as I realized I
did not fully address them.

Den fre 3 jan. 2025 kl 20:37 skrev Thomas Weißschuh <thomas@t-8ch.de>:
>

> > +This driver implements the
> > +Documentation/userspace-api/sysfs-platform_profile.rst interface for working
>
> You can make this real reST link which will be converted into a
> hyperlink.
>

Here I actually tried this a few different ways (linking to the entire
page instead of a specific section within the page) but would always
get a warning and then no link when I built the docs. However, from
finding other examples then I found just giving the path like this is
actually giving me a link in both the htmldocs and pdfdocs with the
title of the target page exactly as I wanted... with that in mind,
does it seem ok to leave as-is or is there a syntax that you would
recommend instead to link directly to a page (and not a section within
a page)?

> > +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> > +                               struct sawb *in_buf, size_t len, struct sawb *out_buf)
>
> in_buf and out_buf are always the same.
>
> > +{
> > +     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 *)in_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 < SAWB_GUNM_POS + 1) {
> > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > +                     "response length mismatch\n", method);
> > +             err = -EPROTO;
> > +             goto out_free;
> > +     }
> > +     if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > +                     "device did not respond with success code 0x%x\n",
> > +                     method, RFLG_SUCCESS);
> > +             err = -ENXIO;
> > +             goto out_free;
> > +     }
> > +     if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> > +             dev_err(&galaxybook->acpi->dev,
> > +                     "failed to execute method %s; device responded with failure code 0x%x\n",
> > +                     method, GUNM_FAIL);
> > +             err = -ENXIO;
> > +             goto out_free;
> > +     }
> > +
> > +     memcpy(out_buf, out_obj->buffer.pointer, len);
>
> Nit: This memcpy() could be avoided by having the ACPI core write directly
> into out_buf. It would also remove the allocation.
>

Now I have replaced in_buf and out_buf with just one parameter, buf.
Now it feels like I cannot write directly to it (since I am reusing
the same buf as the outgoing value) so have left the memcpy in place.
I guess I would need to choose to have 2 buffers or use one and do a
memcpy at the end like this (which is how I have it now in my v5
draft) .. am I thinking wrong here and/or is there a preference
between the two alternatives? I can just for now say that "usage" of
this function in all of the other functions feels easier to just have
one buffer... :)

> > +static int power_on_lid_open_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> > +{
> > +     struct sawb buf = { 0 };
> > +
> > +     buf.safn = SAFN;
> > +     buf.sasb = SASB_POWER_MANAGEMENT;
> > +     buf.gunm = GUNM_POWER_MANAGEMENT;
> > +     buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> > +     buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> > +     buf.guds[2] = value ? 1 : 0;
>
> No need for the ternary.
>

I did not have this before but it was requested to be added by Ilpo
IIRC. I am ok with either way but would just need to know which is
preferred between the two :)

> > +static void galaxybook_i8042_filter_remove(void *data)
> > +{
> > +     struct samsung_galaxybook *galaxybook = data;
> > +
> > +     i8042_remove_filter(galaxybook_i8042_filter);
> > +     if (galaxybook->has_kbd_backlight)
> > +             cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
> > +     if (galaxybook->has_camera_lens_cover)
> > +             cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
> > +}
> > +
> > +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
> > +{
> > +     int err;
> > +
> > +     if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
> > +             return 0;
> > +
> > +     if (galaxybook->has_kbd_backlight)
> > +             INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
> > +                       galaxybook_kbd_backlight_hotkey_work);
> > +
> > +     if (galaxybook->has_camera_lens_cover)
> > +             INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
> > +                       galaxybook_camera_lens_cover_hotkey_work);
>
> I would just always initialize and cancel the work_structs.
> This is no hot path and it makes the code simpler.
>

I apologize but I don't think I am 100% following what you mean here.
Is there an example or more information that can be provided so I can
know what should be changed here?

> > +     err = galaxybook_enable_acpi_notify(galaxybook);
> > +     if (err)
> > +             dev_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
> > +                      "some hotkeys will not be supported\n");
>
> Will this dev_warn() trigger always for certain devices? If so a
> dev_info() would be more appropriate IMO.
>

Yes good point here; for the devices which have this condition, they
will get this message every single time, so I will change it to info.
I can also change it to debug if that makes even more sense.

> [...]

Other than these I think (hope) I have tried to address everything
else from all other comments. I will hold off on sending this v5 in
case you reply soon-ish but otherwise will go ahead and send it as-is
in the next day or two just to keep the feedback cycle going.

Thank you again!

Best regards,
Joshua
Joshua Grisham Jan. 8, 2025, 10 p.m. UTC | #13
Den tis 7 jan. 2025 kl 19:56 skrev Armin Wolf <W_Armin@gmx.de>:
>
> Is this non-ultra performance mode any different than the ultra performance mode
> in terms of performance gains, fan speed, etc?
>

From what I can tell it ramps up the performance even more and might
even also ramp up the performance of the GPU (these devices have a
second dedicated GPU) a bit more. My understanding is that even for
these models, "Performance" is considered a high performance mode, but
that "Ultra" is like "super performance" ?

Fan speed I think is mostly controlled based on temperature but it
could also be that some thresholds are adjusted etc. All of this is
unfortunately embedded within the EC so you cannot really see any
voltage, clock, etc, or other differences on the CPU when these modes
are used, even though it is clear from basic stress testing especially
with the "Silent" / low-power mode that the CPU has been severely
limited.

>
> PLATFORM_PROFILE_CUSTOM is meant to signal that the platform is not in a well-defined
> profile state, usually due to manual tuning. So please do not use it for ULTRA.
>

Thank you yes I also realized this a bit more when I read through some
of the proposed changes to other drivers in the mailing list!

> > If this is possible, then my best guess for the logic for this mapping
> > in samsung-galaxybook could be changed to loop the "supported modes"
> > forwards instead of backwards, and just let the "legacy" modes be
> > written first (as they seem to always come first in the list), and
> > then in case the non-legacy mode exists later in the array, it will
> > just replace the already-mapped legacy value with the new non-legacy
> > value, and thus skip any kind of condition-based checking/mapping
> > entirely. Is that sort of more like what you had in mind?
>
> Can you be sure that legacy performance modes are always placed before non-legacy
> performance modes?
>
> If no then i suggest that you iterate over all supported modes and if you encounter
> a legacy performance mode you check if the associated platform profile slot was already
> taken by a non-legacy performance mode. If that is the case you ignore that legacy performance
> mode.
>
> If you are sure that the order is always the same then you can of course simplify this by
> iterating forward. I will leave it to you to choose which one to implement, as you seem
> to have more knowledge about the underlying hardware than me.
>

So far the order has always been the same for all devices I have seen
from users in the community, it is just that certain modes are or are
not present in the list depending on their support. However, based on
your comment I think it is maybe safe to add a bit more logic just in
case the modes suddenly come in a different or random order on some
new device. I have also now simplified the mapping so it is mostly 1:1
with one exception: if Ultra is found, then I map it to performance
and re-map what was Performance to balanced-performance. Otherwise and
for all other devices without Ultra, it is 1:1.

I have also tightened up and streamlined the logic a tiny bit so
hopefully it will feel slightly more straight-forward in the new v5.
This feels like an ok compromise if we should be using exactly the
profiles which are currently available .. how does this sound?

> [...]
> Thanks,
> Armin Wolf
>

Thank you!
Joshua
Thomas Weißschuh Jan. 8, 2025, 10:07 p.m. UTC | #14
Hi!

On 2025-01-08 22:37:01+0100, Joshua Grisham wrote:
> Hi Thomas! I was prepping my v5 patch to send in and trying to figure
> out everything I changed for the change list comments, but I stumbled
> on a few comments here that I wanted to ask you about as I realized I
> did not fully address them.
> 
> Den fre 3 jan. 2025 kl 20:37 skrev Thomas Weißschuh <thomas@t-8ch.de>:
> >
> 
> > > +This driver implements the
> > > +Documentation/userspace-api/sysfs-platform_profile.rst interface for working
> >
> > You can make this real reST link which will be converted into a
> > hyperlink.
> >
> 
> Here I actually tried this a few different ways (linking to the entire
> page instead of a specific section within the page) but would always
> get a warning and then no link when I built the docs. However, from
> finding other examples then I found just giving the path like this is
> actually giving me a link in both the htmldocs and pdfdocs with the
> title of the target page exactly as I wanted... with that in mind,
> does it seem ok to leave as-is or is there a syntax that you would
> recommend instead to link directly to a page (and not a section within
> a page)?

If it works, then leave it as is.
To exact warning would have been nice though :-)

Did you try :ref:`userspace-api/sysfs-platform_profile`?

> > > +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> > > +                               struct sawb *in_buf, size_t len, struct sawb *out_buf)
> >
> > in_buf and out_buf are always the same.
> >
> > > +{
> > > +     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 *)in_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 < SAWB_GUNM_POS + 1) {
> > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > +                     "response length mismatch\n", method);
> > > +             err = -EPROTO;
> > > +             goto out_free;
> > > +     }
> > > +     if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > +                     "device did not respond with success code 0x%x\n",
> > > +                     method, RFLG_SUCCESS);
> > > +             err = -ENXIO;
> > > +             goto out_free;
> > > +     }
> > > +     if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> > > +             dev_err(&galaxybook->acpi->dev,
> > > +                     "failed to execute method %s; device responded with failure code 0x%x\n",
> > > +                     method, GUNM_FAIL);
> > > +             err = -ENXIO;
> > > +             goto out_free;
> > > +     }
> > > +
> > > +     memcpy(out_buf, out_obj->buffer.pointer, len);
> >
> > Nit: This memcpy() could be avoided by having the ACPI core write directly
> > into out_buf. It would also remove the allocation.
> >
> 
> Now I have replaced in_buf and out_buf with just one parameter, buf.
> Now it feels like I cannot write directly to it (since I am reusing
> the same buf as the outgoing value) so have left the memcpy in place.
> I guess I would need to choose to have 2 buffers or use one and do a
> memcpy at the end like this (which is how I have it now in my v5
> draft) .. am I thinking wrong here and/or is there a preference
> between the two alternatives? I can just for now say that "usage" of
> this function in all of the other functions feels easier to just have
> one buffer... :)

I'm not sure if there is a preference.

But why can't you modify the buffer if it is shared between input and
output? The caller already has to accept that its buffer will be
overwritten.
If it is overwritten once or twice should not matter.

But maybe I'm misunderstanding.

> > > +static int power_on_lid_open_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> > > +{
> > > +     struct sawb buf = { 0 };
> > > +
> > > +     buf.safn = SAFN;
> > > +     buf.sasb = SASB_POWER_MANAGEMENT;
> > > +     buf.gunm = GUNM_POWER_MANAGEMENT;
> > > +     buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> > > +     buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> > > +     buf.guds[2] = value ? 1 : 0;
> >
> > No need for the ternary.
> >
> 
> I did not have this before but it was requested to be added by Ilpo
> IIRC. I am ok with either way but would just need to know which is
> preferred between the two :)

Then leave it as is.

> > > +static void galaxybook_i8042_filter_remove(void *data)
> > > +{
> > > +     struct samsung_galaxybook *galaxybook = data;
> > > +
> > > +     i8042_remove_filter(galaxybook_i8042_filter);
> > > +     if (galaxybook->has_kbd_backlight)
> > > +             cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
> > > +     if (galaxybook->has_camera_lens_cover)
> > > +             cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
> > > +}
> > > +
> > > +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
> > > +{
> > > +     int err;
> > > +
> > > +     if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
> > > +             return 0;
> > > +
> > > +     if (galaxybook->has_kbd_backlight)
> > > +             INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
> > > +                       galaxybook_kbd_backlight_hotkey_work);
> > > +
> > > +     if (galaxybook->has_camera_lens_cover)
> > > +             INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
> > > +                       galaxybook_camera_lens_cover_hotkey_work);
> >
> > I would just always initialize and cancel the work_structs.
> > This is no hot path and it makes the code simpler.
> >
> 
> I apologize but I don't think I am 100% following what you mean here.
> Is there an example or more information that can be provided so I can
> know what should be changed here?

I would remove the conditionals for has_kbd_backlight and
has_camera_lens_cover. And unconditionally do:

INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
          galaxybook_kbd_backlight_hotkey_work);
INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
          galaxybook_camera_lens_cover_hotkey_work);

> > > +     err = galaxybook_enable_acpi_notify(galaxybook);
> > > +     if (err)
> > > +             dev_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
> > > +                      "some hotkeys will not be supported\n");
> >
> > Will this dev_warn() trigger always for certain devices? If so a
> > dev_info() would be more appropriate IMO.
> >
> 
> Yes good point here; for the devices which have this condition, they
> will get this message every single time, so I will change it to info.
> I can also change it to debug if that makes even more sense.

debug would be even better, indeed.

> > [...]
> 
> Other than these I think (hope) I have tried to address everything
> else from all other comments. I will hold off on sending this v5 in
> case you reply soon-ish but otherwise will go ahead and send it as-is
> in the next day or two just to keep the feedback cycle going.

Looking forward to v5!
Armin Wolf Jan. 8, 2025, 11:13 p.m. UTC | #15
Am 08.01.25 um 23:00 schrieb Joshua Grisham:

> Den tis 7 jan. 2025 kl 19:56 skrev Armin Wolf <W_Armin@gmx.de>:
>> Is this non-ultra performance mode any different than the ultra performance mode
>> in terms of performance gains, fan speed, etc?
>>
>  From what I can tell it ramps up the performance even more and might
> even also ramp up the performance of the GPU (these devices have a
> second dedicated GPU) a bit more. My understanding is that even for
> these models, "Performance" is considered a high performance mode, but
> that "Ultra" is like "super performance" ?
>
> Fan speed I think is mostly controlled based on temperature but it
> could also be that some thresholds are adjusted etc. All of this is
> unfortunately embedded within the EC so you cannot really see any
> voltage, clock, etc, or other differences on the CPU when these modes
> are used, even though it is clear from basic stress testing especially
> with the "Silent" / low-power mode that the CPU has been severely
> limited.
>
>> PLATFORM_PROFILE_CUSTOM is meant to signal that the platform is not in a well-defined
>> profile state, usually due to manual tuning. So please do not use it for ULTRA.
>>
> Thank you yes I also realized this a bit more when I read through some
> of the proposed changes to other drivers in the mailing list!
>
>>> If this is possible, then my best guess for the logic for this mapping
>>> in samsung-galaxybook could be changed to loop the "supported modes"
>>> forwards instead of backwards, and just let the "legacy" modes be
>>> written first (as they seem to always come first in the list), and
>>> then in case the non-legacy mode exists later in the array, it will
>>> just replace the already-mapped legacy value with the new non-legacy
>>> value, and thus skip any kind of condition-based checking/mapping
>>> entirely. Is that sort of more like what you had in mind?
>> Can you be sure that legacy performance modes are always placed before non-legacy
>> performance modes?
>>
>> If no then i suggest that you iterate over all supported modes and if you encounter
>> a legacy performance mode you check if the associated platform profile slot was already
>> taken by a non-legacy performance mode. If that is the case you ignore that legacy performance
>> mode.
>>
>> If you are sure that the order is always the same then you can of course simplify this by
>> iterating forward. I will leave it to you to choose which one to implement, as you seem
>> to have more knowledge about the underlying hardware than me.
>>
> So far the order has always been the same for all devices I have seen
> from users in the community, it is just that certain modes are or are
> not present in the list depending on their support. However, based on
> your comment I think it is maybe safe to add a bit more logic just in
> case the modes suddenly come in a different or random order on some
> new device. I have also now simplified the mapping so it is mostly 1:1
> with one exception: if Ultra is found, then I map it to performance
> and re-map what was Performance to balanced-performance. Otherwise and
> for all other devices without Ultra, it is 1:1.
>
> I have also tightened up and streamlined the logic a tiny bit so
> hopefully it will feel slightly more straight-forward in the new v5.
> This feels like an ok compromise if we should be using exactly the
> profiles which are currently available .. how does this sound?

OK, i can live with that. Looking forward to the v5 revision :).

Thanks,
Armin Wolf

>> [...]
>> Thanks,
>> Armin Wolf
>>
> Thank you!
> Joshua
>
Ilpo Järvinen Jan. 9, 2025, 8:34 a.m. UTC | #16
On Wed, 8 Jan 2025, Thomas Weißschuh wrote:
> On 2025-01-08 22:37:01+0100, Joshua Grisham wrote:
> > Hi Thomas! I was prepping my v5 patch to send in and trying to figure
> > out everything I changed for the change list comments, but I stumbled
> > on a few comments here that I wanted to ask you about as I realized I
> > did not fully address them.
> > 
> > Den fre 3 jan. 2025 kl 20:37 skrev Thomas Weißschuh <thomas@t-8ch.de>:
> > >
> > 
> > > > +This driver implements the
> > > > +Documentation/userspace-api/sysfs-platform_profile.rst interface for working
> > >
> > > You can make this real reST link which will be converted into a
> > > hyperlink.
> > >
> > 
> > Here I actually tried this a few different ways (linking to the entire
> > page instead of a specific section within the page) but would always
> > get a warning and then no link when I built the docs. However, from
> > finding other examples then I found just giving the path like this is
> > actually giving me a link in both the htmldocs and pdfdocs with the
> > title of the target page exactly as I wanted... with that in mind,
> > does it seem ok to leave as-is or is there a syntax that you would
> > recommend instead to link directly to a page (and not a section within
> > a page)?
> 
> If it works, then leave it as is.
> To exact warning would have been nice though :-)
> 
> Did you try :ref:`userspace-api/sysfs-platform_profile`?
> 
> > > > +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> > > > +                               struct sawb *in_buf, size_t len, struct sawb *out_buf)
> > >
> > > in_buf and out_buf are always the same.
> > >
> > > > +{
> > > > +     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 *)in_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 < SAWB_GUNM_POS + 1) {
> > > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > > +                     "response length mismatch\n", method);
> > > > +             err = -EPROTO;
> > > > +             goto out_free;
> > > > +     }
> > > > +     if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> > > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > > +                     "device did not respond with success code 0x%x\n",
> > > > +                     method, RFLG_SUCCESS);
> > > > +             err = -ENXIO;
> > > > +             goto out_free;
> > > > +     }
> > > > +     if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> > > > +             dev_err(&galaxybook->acpi->dev,
> > > > +                     "failed to execute method %s; device responded with failure code 0x%x\n",
> > > > +                     method, GUNM_FAIL);
> > > > +             err = -ENXIO;
> > > > +             goto out_free;
> > > > +     }
> > > > +
> > > > +     memcpy(out_buf, out_obj->buffer.pointer, len);
> > >
> > > Nit: This memcpy() could be avoided by having the ACPI core write directly
> > > into out_buf. It would also remove the allocation.
> > >
> > 
> > Now I have replaced in_buf and out_buf with just one parameter, buf.
> > Now it feels like I cannot write directly to it (since I am reusing
> > the same buf as the outgoing value) so have left the memcpy in place.
> > I guess I would need to choose to have 2 buffers or use one and do a
> > memcpy at the end like this (which is how I have it now in my v5
> > draft) .. am I thinking wrong here and/or is there a preference
> > between the two alternatives? I can just for now say that "usage" of
> > this function in all of the other functions feels easier to just have
> > one buffer... :)
> 
> I'm not sure if there is a preference.
> 
> But why can't you modify the buffer if it is shared between input and
> output? The caller already has to accept that its buffer will be
> overwritten.
> If it is overwritten once or twice should not matter.
> 
> But maybe I'm misunderstanding.
> 
> > > > +static int power_on_lid_open_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
> > > > +{
> > > > +     struct sawb buf = { 0 };
> > > > +
> > > > +     buf.safn = SAFN;
> > > > +     buf.sasb = SASB_POWER_MANAGEMENT;
> > > > +     buf.gunm = GUNM_POWER_MANAGEMENT;
> > > > +     buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
> > > > +     buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
> > > > +     buf.guds[2] = value ? 1 : 0;
> > >
> > > No need for the ternary.
> > >
> > 
> > I did not have this before but it was requested to be added by Ilpo
> > IIRC. I am ok with either way but would just need to know which is
> > preferred between the two :)
> 
> Then leave it as is.

Yes, in the bool -> uXX conversions, I prefer explicit values even if they 
in many cases happen to match to what C implicit conversion does (if the 
value wouldn't match to 1, you'd need to use that operator anyway).

"true" and "BIT(0)" are conceptially distinct things even if they map to 
the same representation.

Having the explicit conversion confirms the submitter (hopefully :-)) 
spend a second to confirm that true => 1 holds. Without the explicit 
conversion, it would be hard to see what went on inside the submitter 
head. I can ask for it today, but my perspective is long-term, say 5-10 
years from now, the person might no longer be around and somebody ends up 
staring a commit which has such a problem. Explicit conversion avoids that 
ambiguity. (Obviously such problems are quire rare but could happen.)
Joshua Grisham Jan. 9, 2025, 9:33 p.m. UTC | #17
Hi Thomas,

Den ons 8 jan. 2025 kl 23:07 skrev Thomas Weißschuh <thomas@t-8ch.de>:
>
> If it works, then leave it as is.
> To exact warning would have been nice though :-)
>
> Did you try :ref:`userspace-api/sysfs-platform_profile`?
>

Just tried this specifically again and the warning was:
./Documentation/admin-guide/laptops/samsung-galaxybook.rst:72:
WARNING: undefined label: 'userspace-api/sysfs-platform_profile'
[ref.ref]

As it seems to work exactly as intended with only having the path as
clear text (a link is added in both pdf and html plus the title of the
target page is displayed as the link text) then I will leave as-is for
now but please say if you would like for me to try anything else!

> > > > +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
> > > > +                               struct sawb *in_buf, size_t len, struct sawb *out_buf)
> > >
> > > in_buf and out_buf are always the same.
> > >
> > > > +{
> > > > +     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 *)in_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 < SAWB_GUNM_POS + 1) {
> > > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > > +                     "response length mismatch\n", method);
> > > > +             err = -EPROTO;
> > > > +             goto out_free;
> > > > +     }
> > > > +     if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
> > > > +             dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
> > > > +                     "device did not respond with success code 0x%x\n",
> > > > +                     method, RFLG_SUCCESS);
> > > > +             err = -ENXIO;
> > > > +             goto out_free;
> > > > +     }
> > > > +     if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
> > > > +             dev_err(&galaxybook->acpi->dev,
> > > > +                     "failed to execute method %s; device responded with failure code 0x%x\n",
> > > > +                     method, GUNM_FAIL);
> > > > +             err = -ENXIO;
> > > > +             goto out_free;
> > > > +     }
> > > > +
> > > > +     memcpy(out_buf, out_obj->buffer.pointer, len);
> > >
> > > Nit: This memcpy() could be avoided by having the ACPI core write directly
> > > into out_buf. It would also remove the allocation.
> > >
> >
> > Now I have replaced in_buf and out_buf with just one parameter, buf.
> > Now it feels like I cannot write directly to it (since I am reusing
> > the same buf as the outgoing value) so have left the memcpy in place.
> > I guess I would need to choose to have 2 buffers or use one and do a
> > memcpy at the end like this (which is how I have it now in my v5
> > draft) .. am I thinking wrong here and/or is there a preference
> > between the two alternatives? I can just for now say that "usage" of
> > this function in all of the other functions feels easier to just have
> > one buffer... :)
>
> I'm not sure if there is a preference.
>
> But why can't you modify the buffer if it is shared between input and
> output? The caller already has to accept that its buffer will be
> overwritten.
> If it is overwritten once or twice should not matter.
>
> But maybe I'm misunderstanding.
>

There is a very non-zero chance that I am trying to do this completely
wrong ;) but basically if I swap

struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL};
to
struct acpi_buffer output = {len, buf};
or even
struct acpi_buffer output = {len, (u8 *)buf};

Then I am getting return code of AE_BUFFER_OVERFLOW when trying to
call the method, even though when using ACPI_ALLOCATE_BUFFER len is
always the same as the allocated out_obj->buffer.length.

I have also tried a few variations of using a union acpi_object and
setting the buffer member properties etc but always I am getting
AE_BUFFER_OVERFLOW so it seems like something is a bit off on the
length or I am using the wrong types or something. I have tried
looking through the entire tree and using ACPI_ALLOCATE_BUFFER is
almost universal so it is tough to find examples to try and understand
what else might be possible without really digging deep into the ACPI
tree.

If you know right off the top of your head then please feel free to
mention, otherwise I will keep the new buffer and do a memcpy and free
the newly allocated buffer at the end for the time being!

Thanks again!
Joshua
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes
index 2713efa509b4..dd36577b68f2 100644
--- a/Documentation/ABI/testing/sysfs-class-firmware-attributes
+++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes
@@ -326,6 +326,17 @@  Description:
 					This role is specific to Secure Platform Management (SPM) attribute.
 					It requires configuring an endorsement (kek) and signing certificate (sk).
 
+What:		/sys/class/firmware-attributes/*/attributes/camera_lens_cover
+Date:		December 2024
+KernelVersion:	6.13
+Contact:	Joshua Grisham <josh@joshuagrisham.com>
+Description:
+		This attribute can be used to control the behavior of a software-based camera lens
+		cover. The value is a boolean represented by 0 for false (camera is not blocked)
+		and 1 for true (camera is blocked).
+
+		On Samsung Galaxy Book systems, this attribute will also control a software-based
+		"cover" of the microphone in addition to the camera.
 
 What:		/sys/class/firmware-attributes/*/attributes/pending_reboot
 Date:		February 2021
@@ -356,6 +367,14 @@  Description:
 		Drivers may emit a CHANGE uevent when this value changes and userspace
 		may check it again.
 
+What:		/sys/class/firmware-attributes/*/attributes/power_on_lid_open
+Date:		December 2024
+KernelVersion:	6.13
+Contact:	Joshua Grisham <josh@joshuagrisham.com>
+Description:
+		This attribute can be used to control powering on a device when the lid is opened.
+		The value is a boolean represented by 0 for false and 1 for true.
+
 What:		/sys/class/firmware-attributes/*/attributes/reset_bios
 Date:		February 2021
 KernelVersion:	5.11
@@ -429,6 +448,15 @@  Description:
 		HP specific class extensions - Secure Platform Manager (SPM)
 		--------------------------------
 
+What:		/sys/class/firmware-attributes/*/attributes/usb_charging
+Date:		December 2024
+KernelVersion:	6.13
+Contact:	Joshua Grisham <josh@joshuagrisham.com>
+Description:
+		This attribute can be used to control if USB ports can continue to deliver power to
+		connected devices when the device is powered off or in a low sleep state. The value
+		is a boolean represented by 0 for false and 1 for true.
+
 What:		/sys/class/firmware-attributes/*/authentication/SPM/kek
 Date:		March 2023
 KernelVersion:	5.18
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..65da7cd84c01
--- /dev/null
+++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst
@@ -0,0 +1,165 @@ 
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+==========================
+Samsung Galaxy Book Extras
+==========================
+
+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 device extension
+- :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
+done dynamically based on a list of the supported modes reported by the device
+itself. Preference is given to always try and map ``low-power``, ``balanced``,
+and ``performance`` profiles, as these seem to be the most common profiles
+utilized (and sometimes even required) by various userspace tools.
+
+The result of the mapping will 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 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:
+
+- ``camera_lens_cover``
+- ``power_on_lid_open``
+- ``usb_charging``
+
+These attributes are documented in more detail under
+Documentation/admin-guide/abi.rst.
+
+.. _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 "camera lens cover" setting, which blocks
+or allows usage of the built-in camera and microphone.
+
+There is a new "Samsung Galaxy Book Extra Buttons" input device created which
+will send input events for the following notifications:
+
+- Switch ``SW_CAMERA_LENS_COVER`` (on or off) when the camera and microphone are
+  "blocked" or "allowed" when toggling the Camera Lens Cover setting.
+
+.. _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..e74873a1e74b 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 EXTRAS 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..ecc509f5df55 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -778,6 +778,24 @@  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 extras 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
+	select INPUT_SPARSEKMAP
+	help
+	  This is a driver for Samsung Galaxy Book series notebooks. It adds
+	  support for the keyboard backlight control, performance mode control, fan
+	  speed reporting, function keys, and various other device controls.
+
+	  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..c656471dd1c7
--- /dev/null
+++ b/drivers/platform/x86/samsung-galaxybook.c
@@ -0,0 +1,1493 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Samsung Galaxy Book series extras driver
+ *
+ * Copyright (c) 2024 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/err.h>
+#include <linux/i8042.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/sparse-keymap.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"
+
+static const struct acpi_device_id galaxybook_device_ids[] = {
+	{ "SAM0427" },
+	{ "SAM0428" },
+	{ "SAM0429" },
+	{ "SAM0430" },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids);
+
+struct samsung_galaxybook {
+	struct platform_device *platform;
+	struct acpi_device *acpi;
+
+	struct device *fw_attrs_dev;
+	struct kset *fw_attrs_kset;
+	struct kobj_attribute power_on_lid_open_attr;
+	struct kobj_attribute usb_charging_attr;
+	struct kobj_attribute camera_lens_cover_attr;
+
+	bool has_kbd_backlight;
+	bool has_camera_lens_cover;
+	bool has_performance_mode;
+
+	struct led_classdev kbd_backlight;
+	/* block out of sync condition in hotkey action if brightness updated in another thread */
+	struct mutex kbd_backlight_lock;
+	struct work_struct kbd_backlight_hotkey_work;
+
+	struct input_dev *input;
+	/* protect sparse keymap event reporting getting out of sync from multiple threads */
+	struct mutex input_lock;
+	void *i8042_filter_ptr;
+
+	/* block out of sync condition in hotkey action if value updated in another thread */
+	struct mutex camera_lens_cover_lock;
+	struct work_struct camera_lens_cover_hotkey_work;
+
+	struct acpi_battery_hook battery_hook;
+	struct device_attribute charge_control_end_threshold_attr;
+
+	u8 profile_performance_modes[PLATFORM_PROFILE_LAST];
+	struct platform_profile_handler profile_handler;
+};
+
+static struct samsung_galaxybook *galaxybook_ptr;
+static const struct class *fw_attr_class;
+
+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 SAWB_LEN_SETTINGS         0x15
+#define SAWB_LEN_PERFORMANCE_MODE 0x100
+
+#define SAFN  0x5843
+
+#define SASB_KBD_BACKLIGHT      0x78
+#define SASB_POWER_MANAGEMENT   0x7a
+#define SASB_USB_CHARGING_GET   0x67
+#define SASB_USB_CHARGING_SET   0x68
+#define SASB_NOTIFICATIONS      0x86
+#define SASB_CAMERA_LENS_COVER  0x8a
+#define SASB_PERFORMANCE_MODE   0x91
+
+#define SAWB_RFLG_POS  4
+#define SAWB_GUNM_POS  5
+
+#define RFLG_SUCCESS  0xaa
+#define GUNM_FAIL     0xff
+
+#define GUNM_FEATURE_ENABLE          0xbb
+#define GUNM_FEATURE_ENABLE_SUCCESS  0xdd
+#define GUDS_FEATURE_ENABLE          0xaa
+#define GUDS_FEATURE_ENABLE_SUCCESS  0xcc
+
+#define GUNM_GET  0x81
+#define GUNM_SET  0x82
+
+#define GUNM_POWER_MANAGEMENT  0x82
+
+#define GUNM_USB_CHARGING_GET            0x80
+#define GUNM_USB_CHARGING_ON             0x81
+#define GUNM_USB_CHARGING_OFF            0x80
+#define GUDS_POWER_ON_LID_OPEN           0xa3
+#define GUDS_POWER_ON_LID_OPEN_GET       0x81
+#define GUDS_POWER_ON_LID_OPEN_SET       0x80
+#define GUDS_BATTERY_CHARGE_CONTROL      0xe9
+#define GUDS_BATTERY_CHARGE_CONTROL_GET  0x91
+#define GUDS_BATTERY_CHARGE_CONTROL_SET  0x90
+#define GUNM_ACPI_NOTIFY_ENABLE          0x80
+#define GUDS_ACPI_NOTIFY_ENABLE          0x02
+
+#define GB_CAMERA_LENS_COVER_ON   0x0
+#define GB_CAMERA_LENS_COVER_OFF  0x1
+
+#define FNCN_PERFORMANCE_MODE       0x51
+#define SUBN_PERFORMANCE_MODE_LIST  0x01
+#define SUBN_PERFORMANCE_MODE_GET   0x02
+#define SUBN_PERFORMANCE_MODE_SET   0x03
+
+/* guid 8246028d-8bca-4a55-ba0f-6f1e6b921b8f */
+static const guid_t performance_mode_guid_value =
+	GUID_INIT(0x8246028d, 0x8bca, 0x4a55, 0xba, 0x0f, 0x6f, 0x1e, 0x6b, 0x92, 0x1b, 0x8f);
+#define PERFORMANCE_MODE_GUID performance_mode_guid_value
+
+#define PERFORMANCE_MODE_ULTRA               0x16
+#define PERFORMANCE_MODE_PERFORMANCE         0x15
+#define PERFORMANCE_MODE_SILENT              0xb
+#define PERFORMANCE_MODE_QUIET               0xa
+#define PERFORMANCE_MODE_OPTIMIZED           0x2
+#define PERFORMANCE_MODE_PERFORMANCE_LEGACY  0x1
+#define PERFORMANCE_MODE_OPTIMIZED_LEGACY    0x0
+#define PERFORMANCE_MODE_UNKNOWN             0xff
+
+#define DEFAULT_PLATFORM_PROFILE PLATFORM_PROFILE_BALANCED
+
+#define ACPI_METHOD_ENABLE            "SDLS"
+#define ACPI_METHOD_ENABLE_ON         1
+#define ACPI_METHOD_ENABLE_OFF        0
+#define ACPI_METHOD_SETTINGS          "CSFI"
+#define ACPI_METHOD_PERFORMANCE_MODE  "CSXI"
+
+#define KBD_BACKLIGHT_MAX_BRIGHTNESS  3
+
+#define ACPI_NOTIFY_BATTERY_STATE_CHANGED    0x61
+#define ACPI_NOTIFY_DEVICE_ON_TABLE          0x6c
+#define ACPI_NOTIFY_DEVICE_OFF_TABLE         0x6d
+#define ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE  0x70
+
+#define GB_KEY_KBD_BACKLIGHT_KEYDOWN      0x2c
+#define GB_KEY_KBD_BACKLIGHT_KEYUP        0xac
+#define GB_KEY_CAMERA_LENS_COVER_KEYDOWN  0x1f
+#define GB_KEY_CAMERA_LENS_COVER_KEYUP    0x9f
+#define GB_KEY_BATTERY_NOTIFY_KEYUP       0xf
+#define GB_KEY_BATTERY_NOTIFY_KEYDOWN     0x8f
+
+#define INPUT_CAMERA_LENS_COVER_ON   0x01
+#define INPUT_CAMERA_LENS_COVER_OFF  0x02
+
+static const struct key_entry galaxybook_acpi_keymap[] = {
+	{ KE_SW,  INPUT_CAMERA_LENS_COVER_ON,  { .sw = { SW_CAMERA_LENS_COVER, 1 } } },
+	{ KE_SW,  INPUT_CAMERA_LENS_COVER_OFF, { .sw = { SW_CAMERA_LENS_COVER, 0 } } },
+	{ KE_END, 0 },
+};
+
+/*
+ * ACPI method handling
+ */
+
+static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, acpi_string method,
+				  struct sawb *in_buf, size_t len, struct sawb *out_buf)
+{
+	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 *)in_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 < SAWB_GUNM_POS + 1) {
+		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
+			"response length mismatch\n", method);
+		err = -EPROTO;
+		goto out_free;
+	}
+	if (out_obj->buffer.pointer[SAWB_RFLG_POS] != RFLG_SUCCESS) {
+		dev_err(&galaxybook->acpi->dev, "failed to execute method %s; "
+			"device did not respond with success code 0x%x\n",
+			method, RFLG_SUCCESS);
+		err = -ENXIO;
+		goto out_free;
+	}
+	if (out_obj->buffer.pointer[SAWB_GUNM_POS] == GUNM_FAIL) {
+		dev_err(&galaxybook->acpi->dev,
+			"failed to execute method %s; device responded with failure code 0x%x\n",
+			method, GUNM_FAIL);
+		err = -ENXIO;
+		goto out_free;
+	}
+
+	memcpy(out_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 = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = sasb;
+	buf.gunm = GUNM_FEATURE_ENABLE;
+	buf.guds[0] = GUDS_FEATURE_ENABLE;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	if (err)
+		return err;
+
+	if (buf.gunm != GUNM_FEATURE_ENABLE_SUCCESS && buf.guds[0] != 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 = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_KBD_BACKLIGHT;
+	buf.gunm = GUNM_SET;
+
+	buf.guds[0] = brightness;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook,
+				  enum led_brightness *brightness)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_KBD_BACKLIGHT;
+	buf.gunm = GUNM_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	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, SASB_KBD_BACKLIGHT);
+	if (err)
+		goto return_with_dbg;
+
+	/* verify we can read the value, otherwise stop without setting has_kbd_backlight */
+	err = kbd_backlight_acpi_get(galaxybook, &brightness);
+	if (err)
+		goto return_with_dbg;
+
+	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 = KBD_BACKLIGHT_MAX_BRIGHTNESS;
+
+	err = devm_led_classdev_register_ext(&galaxybook->platform->dev,
+					     &galaxybook->kbd_backlight, &init_data);
+	if (err)
+		goto return_with_dbg;
+
+	galaxybook->has_kbd_backlight = true;
+
+	return 0;
+
+return_with_dbg:
+	dev_dbg(&galaxybook->platform->dev,
+		"failed to initialize kbd_backlight, error %d\n", err);
+	return 0;
+}
+
+/*
+ * Platform device attributes (configuration properties which can be controlled via userspace)
+ */
+
+/* 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 = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_POWER_MANAGEMENT;
+	buf.gunm = GUNM_POWER_MANAGEMENT;
+	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
+	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_SET;
+	buf.guds[2] = value ? 1 : 0;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+static int power_on_lid_open_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_POWER_MANAGEMENT;
+	buf.gunm = GUNM_POWER_MANAGEMENT;
+	buf.guds[0] = GUDS_POWER_ON_LID_OPEN;
+	buf.guds[1] = GUDS_POWER_ON_LID_OPEN_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	if (err)
+		return err;
+
+	*value = buf.guds[1];
+
+	return 0;
+}
+
+static ssize_t power_on_lid_open_store(struct kobject *kobj, struct kobj_attribute *attr,
+				       const char *buffer, size_t count)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
+
+	bool value;
+	int err;
+
+	if (!count)
+		return -EINVAL;
+
+	err = kstrtobool(buffer, &value);
+	if (err)
+		return err;
+
+	err = power_on_lid_open_acpi_set(galaxybook, value);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static ssize_t power_on_lid_open_show(struct kobject *kobj, struct kobj_attribute *attr,
+				      char *buffer)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, power_on_lid_open_attr);
+	bool value;
+	int err;
+
+	err = power_on_lid_open_acpi_get(galaxybook, &value);
+	if (err)
+		return err;
+
+	return sysfs_emit(buffer, "%u\n", value);
+}
+
+/* USB Charging (USB ports can charge other devices even when device is powered off) */
+
+static int usb_charging_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
+{
+	struct sawb buf = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_USB_CHARGING_SET;
+	buf.gunm = value ? GUNM_USB_CHARGING_ON : GUNM_USB_CHARGING_OFF;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+static int usb_charging_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_USB_CHARGING_GET;
+	buf.gunm = GUNM_USB_CHARGING_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	if (err)
+		return err;
+
+	*value = buf.gunm == 1;
+
+	return 0;
+}
+
+static ssize_t usb_charging_store(struct kobject *kobj, struct kobj_attribute *attr,
+				  const char *buffer, size_t count)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
+	bool value;
+	int err;
+
+	if (!count)
+		return -EINVAL;
+
+	err = kstrtobool(buffer, &value);
+	if (err)
+		return err;
+
+	err = usb_charging_acpi_set(galaxybook, value);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static ssize_t usb_charging_show(struct kobject *kobj, struct kobj_attribute *attr, char *buffer)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, usb_charging_attr);
+	bool value;
+	int err;
+
+	err = usb_charging_acpi_get(galaxybook, &value);
+	if (err)
+		return err;
+
+	return sysfs_emit(buffer, "%u\n", value);
+}
+
+/* Camera lens cover (blocks access to camera and microphone) */
+
+static int camera_lens_cover_acpi_set(struct samsung_galaxybook *galaxybook, const bool value)
+{
+	struct sawb buf = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_CAMERA_LENS_COVER;
+	buf.gunm = GUNM_SET;
+	buf.guds[0] = value ? GB_CAMERA_LENS_COVER_ON : GB_CAMERA_LENS_COVER_OFF;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+static int camera_lens_cover_acpi_get(struct samsung_galaxybook *galaxybook, bool *value)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_CAMERA_LENS_COVER;
+	buf.gunm = GUNM_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	if (err)
+		return err;
+
+	*value = buf.gunm == GB_CAMERA_LENS_COVER_ON;
+
+	return 0;
+}
+
+static ssize_t camera_lens_cover_store(struct kobject *kobj, struct kobj_attribute *attr,
+				       const char *buffer, size_t count)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
+	bool value;
+	int err;
+
+	if (!count)
+		return -EINVAL;
+
+	err = kstrtobool(buffer, &value);
+	if (err)
+		return err;
+
+	mutex_lock(&galaxybook->camera_lens_cover_lock);
+	err = camera_lens_cover_acpi_set(galaxybook, value);
+	mutex_unlock(&galaxybook->camera_lens_cover_lock);
+	if (err)
+		return err;
+
+	return count;
+}
+
+static ssize_t camera_lens_cover_show(struct kobject *kobj, struct kobj_attribute *attr,
+				      char *buffer)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(attr, struct samsung_galaxybook, camera_lens_cover_attr);
+	bool value;
+	int err;
+
+	err = camera_lens_cover_acpi_get(galaxybook, &value);
+	if (err)
+		return err;
+
+	return sysfs_emit(buffer, "%u\n", value);
+}
+
+static int galaxybook_camera_lens_cover_init(struct samsung_galaxybook *galaxybook)
+{
+	int err;
+
+	err = galaxybook_enable_acpi_feature(galaxybook, SASB_CAMERA_LENS_COVER);
+	if (err) {
+		dev_dbg(&galaxybook->platform->dev,
+			"failed to initialize camera lens cover feature, error %d\n", err);
+		return 0;
+	}
+
+	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->camera_lens_cover_lock);
+	if (err)
+		return err;
+
+	galaxybook->has_camera_lens_cover = true;
+
+	return 0;
+}
+
+/* Attribute setup */
+
+static void galaxybook_power_on_lid_open_attr_remove(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
+			  &galaxybook->power_on_lid_open_attr.attr);
+}
+
+static void galaxybook_usb_charging_attr_remove(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
+			  &galaxybook->usb_charging_attr.attr);
+}
+
+static void galaxybook_camera_lens_cover_attr_remove(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	sysfs_remove_file(&galaxybook->fw_attrs_kset->kobj,
+			  &galaxybook->camera_lens_cover_attr.attr);
+}
+
+static void galaxybook_fw_attrs_kset_remove(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	kset_unregister(galaxybook->fw_attrs_kset);
+}
+
+static void galaxybook_fw_attr_class_remove(void *data)
+{
+	device_destroy(fw_attr_class, MKDEV(0, 0));
+	fw_attributes_class_put();
+}
+
+static int galaxybook_fw_attrs_init(struct samsung_galaxybook *galaxybook)
+{
+	bool value;
+	int err;
+
+	err = fw_attributes_class_get(&fw_attr_class);
+	if (err)
+		return err;
+
+	galaxybook->fw_attrs_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
+						 NULL, "%s", DRIVER_NAME);
+	if (IS_ERR(galaxybook->fw_attrs_dev)) {
+		fw_attributes_class_put();
+		err = PTR_ERR(galaxybook->fw_attrs_dev);
+		return err;
+	}
+	err = devm_add_action_or_reset(&galaxybook->platform->dev,
+				       galaxybook_fw_attr_class_remove, NULL);
+	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_fw_attrs_kset_remove, galaxybook);
+	if (err)
+		return err;
+
+	err = power_on_lid_open_acpi_get(galaxybook, &value);
+	if (!err) {
+		sysfs_attr_init(&galaxybook->power_on_lid_open_attr);
+		galaxybook->power_on_lid_open_attr.attr.name = "power_on_lid_open";
+		galaxybook->power_on_lid_open_attr.attr.mode = 0644;
+		galaxybook->power_on_lid_open_attr.show = power_on_lid_open_show;
+		galaxybook->power_on_lid_open_attr.store = power_on_lid_open_store;
+		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
+					&galaxybook->power_on_lid_open_attr.attr);
+		if (err)
+			return err;
+		err = devm_add_action_or_reset(&galaxybook->platform->dev,
+					       galaxybook_power_on_lid_open_attr_remove,
+					       galaxybook);
+		if (err)
+			return err;
+	}
+
+	err = usb_charging_acpi_get(galaxybook, &value);
+	if (!err) {
+		sysfs_attr_init(&galaxybook->usb_charging_attr);
+		galaxybook->usb_charging_attr.attr.name = "usb_charging";
+		galaxybook->usb_charging_attr.attr.mode = 0644;
+		galaxybook->usb_charging_attr.show = usb_charging_show;
+		galaxybook->usb_charging_attr.store = usb_charging_store;
+		err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
+					&galaxybook->usb_charging_attr.attr);
+		if (err)
+			return err;
+		err = devm_add_action_or_reset(&galaxybook->platform->dev,
+					       galaxybook_usb_charging_attr_remove, galaxybook);
+		if (err)
+			return err;
+	}
+
+	if (!galaxybook->has_camera_lens_cover)
+		return 0;
+	err = camera_lens_cover_acpi_get(galaxybook, &value);
+	if (err) {
+		galaxybook->has_camera_lens_cover = false;
+		return 0;
+	}
+
+	sysfs_attr_init(&galaxybook->camera_lens_cover_attr);
+	galaxybook->camera_lens_cover_attr.attr.name = "camera_lens_cover";
+	galaxybook->camera_lens_cover_attr.attr.mode = 0644;
+	galaxybook->camera_lens_cover_attr.show = camera_lens_cover_show;
+	galaxybook->camera_lens_cover_attr.store = camera_lens_cover_store;
+	err = sysfs_create_file(&galaxybook->fw_attrs_kset->kobj,
+				&galaxybook->camera_lens_cover_attr.attr);
+	if (err)
+		return err;
+	return devm_add_action_or_reset(&galaxybook->platform->dev,
+					galaxybook_camera_lens_cover_attr_remove, galaxybook);
+}
+
+/*
+ * 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 = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_POWER_MANAGEMENT;
+	buf.gunm = GUNM_POWER_MANAGEMENT;
+	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
+	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_SET;
+	buf.guds[2] = value;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook *galaxybook, u8 *value)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_POWER_MANAGEMENT;
+	buf.gunm = GUNM_POWER_MANAGEMENT;
+	buf.guds[0] = GUDS_BATTERY_CHARGE_CONTROL;
+	buf.guds[1] = GUDS_BATTERY_CHARGE_CONTROL_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				     &buf, SAWB_LEN_SETTINGS, &buf);
+	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 *buffer, 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(buffer, 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 *buffer)
+{
+	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(buffer, "%d\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 void 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)
+		goto return_with_dbg;
+
+	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;
+
+	err = devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook->battery_hook);
+	if (err)
+		goto return_with_dbg;
+
+	return;
+
+return_with_dbg:
+	dev_dbg(&galaxybook->platform->dev,
+		"failed to initialize battery charge threshold, error %d\n", err);
+}
+
+/*
+ * Platform Profile / Performance mode
+ */
+
+static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook,
+				     const u8 performance_mode)
+{
+	struct sawb buf = { 0 };
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_PERFORMANCE_MODE;
+	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
+	buf.fncn = FNCN_PERFORMANCE_MODE;
+	buf.subn = SUBN_PERFORMANCE_MODE_SET;
+	buf.iob0 = performance_mode;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
+				      &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
+}
+
+static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook, u8 *performance_mode)
+{
+	struct sawb buf = { 0 };
+	int err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_PERFORMANCE_MODE;
+	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
+	buf.fncn = FNCN_PERFORMANCE_MODE;
+	buf.subn = SUBN_PERFORMANCE_MODE_GET;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
+				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
+	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)
+{
+	for (int i = 0; i < PLATFORM_PROFILE_LAST; i++) {
+		if (galaxybook->profile_performance_modes[i] == performance_mode) {
+			if (profile)
+				*profile = i;
+			return 0;
+		}
+	}
+
+	return -ENODATA;
+}
+
+static int galaxybook_platform_profile_set(struct platform_profile_handler *pprof,
+					   enum platform_profile_option profile)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(pprof, struct samsung_galaxybook, profile_handler);
+
+	return performance_mode_acpi_set(galaxybook,
+					 galaxybook->profile_performance_modes[profile]);
+}
+
+static int galaxybook_platform_profile_get(struct platform_profile_handler *pprof,
+					   enum platform_profile_option *profile)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(pprof, struct samsung_galaxybook, profile_handler);
+	u8 performance_mode;
+	int err;
+
+	err = performance_mode_acpi_get(galaxybook, &performance_mode);
+	if (err)
+		return err;
+
+	return get_performance_mode_profile(galaxybook, performance_mode, profile);
+}
+
+static void galaxybook_profile_exit(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	platform_profile_remove(&galaxybook->profile_handler);
+}
+
+#define IGNORE_PERFORMANCE_MODE_MAPPING  -1
+
+static void galaxybook_profile_init(struct samsung_galaxybook *galaxybook)
+{
+	u8 current_performance_mode;
+	u8 init_performance_mode;
+	struct sawb buf = { 0 };
+	int mapped_profiles;
+	int mode_profile;
+	int err;
+	int i;
+
+	galaxybook->profile_handler.name = DRIVER_NAME;
+	galaxybook->profile_handler.dev = &galaxybook->platform->dev;
+	galaxybook->profile_handler.profile_get = galaxybook_platform_profile_get;
+	galaxybook->profile_handler.profile_set = galaxybook_platform_profile_set;
+
+	/* fetch supported performance mode values from ACPI method */
+	buf.safn = SAFN;
+	buf.sasb = SASB_PERFORMANCE_MODE;
+	export_guid(buf.caid, &PERFORMANCE_MODE_GUID);
+	buf.fncn = FNCN_PERFORMANCE_MODE;
+	buf.subn = SUBN_PERFORMANCE_MODE_LIST;
+
+	err = galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE,
+				     &buf, SAWB_LEN_PERFORMANCE_MODE, &buf);
+	if (err)
+		goto return_with_dbg;
+
+	/* set up profile_performance_modes with "unknown" as init value */
+	for (i = 0; i < PLATFORM_PROFILE_LAST; i++)
+		galaxybook->profile_performance_modes[i] = PERFORMANCE_MODE_UNKNOWN;
+
+	/*
+	 * Value returned in iob0 will have the number of supported performance modes.
+	 * The performance mode values will then be given as a list after this (iob1-iobX).
+	 * Loop backwards from last value to first value (to handle fallback cases which come with
+	 * smaller values) and map each supported value to its correct platform_profile_option.
+	 */
+	for (i = buf.iob0; i > 0; i--) {
+		/*
+		 * Prefer mapping to at least performance, balanced, and low-power profiles, as they
+		 * are the profiles which are typically supported by userspace tools
+		 * (power-profiles-daemon, etc).
+		 * - performance = "ultra", otherwise "performance"
+		 * - balanced    = "optimized", otherwise "performance" when "ultra" is supported
+		 * - low-power   = "silent", otherwise "quiet"
+		 * Different models support different modes. Additional supported modes will be
+		 * mapped to profiles that fall in between these 3.
+		 */
+		switch (buf.iob_values[i]) {
+		case PERFORMANCE_MODE_ULTRA:
+			/* ultra always maps to performance */
+			mode_profile = PLATFORM_PROFILE_PERFORMANCE;
+			break;
+
+		case PERFORMANCE_MODE_PERFORMANCE:
+			/* if ultra exists, map performance to balanced-performance */
+			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] !=
+			    PERFORMANCE_MODE_UNKNOWN)
+				mode_profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE;
+			else /* otherwise map it to performance instead */
+				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
+			break;
+
+		case PERFORMANCE_MODE_SILENT:
+			/* silent always maps to low-power */
+			mode_profile = PLATFORM_PROFILE_LOW_POWER;
+			break;
+
+		case PERFORMANCE_MODE_QUIET:
+			/* if silent exists, map quiet to quiet */
+			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_LOW_POWER] !=
+			    PERFORMANCE_MODE_UNKNOWN)
+				mode_profile = PLATFORM_PROFILE_QUIET;
+			else /* otherwise map it to low-power for better userspace tool support */
+				mode_profile = PLATFORM_PROFILE_LOW_POWER;
+			break;
+
+		case PERFORMANCE_MODE_OPTIMIZED:
+			/* optimized always maps to balanced */
+			mode_profile = PLATFORM_PROFILE_BALANCED;
+			break;
+
+		case PERFORMANCE_MODE_PERFORMANCE_LEGACY:
+			/* map to performance if performance is not already supported */
+			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE] ==
+			    PERFORMANCE_MODE_UNKNOWN)
+				mode_profile = PLATFORM_PROFILE_PERFORMANCE;
+			else /* otherwise, ignore */
+				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
+			break;
+
+		case PERFORMANCE_MODE_OPTIMIZED_LEGACY:
+			/* map to balanced if balanced is not already supported */
+			if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_BALANCED] ==
+			    PERFORMANCE_MODE_UNKNOWN)
+				mode_profile = PLATFORM_PROFILE_BALANCED;
+			else /* otherwise, ignore */
+				mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
+			break;
+
+		default: /* any other value is not supported */
+			mode_profile = IGNORE_PERFORMANCE_MODE_MAPPING;
+			break;
+		}
+
+		/* if current mode value mapped to a supported platform_profile_option, set it up */
+		if (mode_profile != IGNORE_PERFORMANCE_MODE_MAPPING) {
+			mapped_profiles++;
+			galaxybook->profile_performance_modes[mode_profile] = buf.iob_values[i];
+			set_bit(mode_profile, galaxybook->profile_handler.choices);
+			if (mode_profile == DEFAULT_PLATFORM_PROFILE)
+				init_performance_mode = buf.iob_values[i];
+			dev_dbg(&galaxybook->platform->dev,
+				"will support platform profile %d (performance mode 0x%x)\n",
+				mode_profile, buf.iob_values[i]);
+		} else {
+			dev_dbg(&galaxybook->platform->dev,
+				"unmapped performance mode 0x%x will be ignored\n",
+				buf.iob_values[i]);
+		}
+	}
+
+	if (mapped_profiles == 0) {
+		err = -ENODEV;
+		goto return_with_dbg;
+	}
+
+	/* now check currently set performance mode; if not supported then set default mode */
+	err = performance_mode_acpi_get(galaxybook, &current_performance_mode);
+	if (err)
+		goto return_with_dbg;
+	err = get_performance_mode_profile(galaxybook, current_performance_mode, NULL);
+	if (err) {
+		dev_dbg(&galaxybook->platform->dev,
+			"initial performance mode value is not supported by device; "
+			"setting to default\n");
+		err = performance_mode_acpi_set(galaxybook, init_performance_mode);
+		if (err)
+			goto return_with_dbg;
+	}
+
+	err = platform_profile_register(&galaxybook->profile_handler);
+	if (err)
+		goto return_with_dbg;
+
+	err = devm_add_action_or_reset(&galaxybook->platform->dev,
+				       galaxybook_profile_exit, galaxybook);
+	if (err)
+		goto return_with_dbg;
+
+	galaxybook->has_performance_mode = true;
+
+	return;
+
+return_with_dbg:
+	dev_dbg(&galaxybook->platform->dev,
+		"failed to initialize platform profile, error %d\n", err);
+}
+
+/*
+ * Hotkeys and notifications
+ */
+
+static void galaxybook_input_notify(struct samsung_galaxybook *galaxybook, int event)
+{
+	if (!galaxybook->input)
+		return;
+	mutex_lock(&galaxybook->input_lock);
+	if (!sparse_keymap_report_event(galaxybook->input, event, 1, true))
+		dev_warn(&galaxybook->acpi->dev, "unknown input notification event: 0x%x\n", event);
+	mutex_unlock(&galaxybook->input_lock);
+}
+
+static int galaxybook_input_init(struct samsung_galaxybook *galaxybook)
+{
+	int err;
+
+	err = devm_mutex_init(&galaxybook->platform->dev, &galaxybook->input_lock);
+	if (err)
+		return err;
+
+	galaxybook->input = devm_input_allocate_device(&galaxybook->platform->dev);
+	if (!galaxybook->input)
+		return -ENOMEM;
+
+	galaxybook->input->name = "Samsung Galaxy Book Extra Buttons";
+	galaxybook->input->phys = DRIVER_NAME "/input0";
+	galaxybook->input->id.bustype = BUS_HOST;
+	galaxybook->input->dev.parent = &galaxybook->platform->dev;
+
+	err = sparse_keymap_setup(galaxybook->input, galaxybook_acpi_keymap, NULL);
+	if (err)
+		return err;
+
+	return input_register_device(galaxybook->input);
+}
+
+static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(work, struct samsung_galaxybook, kbd_backlight_hotkey_work);
+	int new_brightness;
+	int err;
+
+	guard(mutex)(&galaxybook->kbd_backlight_lock);
+
+	if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_brightness)
+		new_brightness = galaxybook->kbd_backlight.brightness + 1;
+	else
+		new_brightness = 0;
+
+	err = led_set_brightness_sync(&galaxybook->kbd_backlight, new_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, new_brightness);
+}
+
+static void galaxybook_camera_lens_cover_hotkey_work(struct work_struct *work)
+{
+	struct samsung_galaxybook *galaxybook =
+		container_of(work, struct samsung_galaxybook, camera_lens_cover_hotkey_work);
+	bool value;
+	int err;
+
+	guard(mutex)(&galaxybook->camera_lens_cover_lock);
+
+	err = camera_lens_cover_acpi_get(galaxybook, &value);
+	if (err) {
+		dev_err(&galaxybook->platform->dev,
+			"failed to get camera_lens_cover, error %d\n", err);
+		return;
+	}
+
+	err = camera_lens_cover_acpi_set(galaxybook, !value);
+	if (err) {
+		dev_err(&galaxybook->platform->dev,
+			"failed to set camera_lens_cover, error %d\n", err);
+		return;
+	}
+
+	galaxybook_input_notify(galaxybook,
+				!value ? INPUT_CAMERA_LENS_COVER_ON : INPUT_CAMERA_LENS_COVER_OFF);
+}
+
+static bool galaxybook_i8042_filter(unsigned char data, unsigned char str, struct serio *port)
+{
+	static bool extended;
+
+	if (str & I8042_STR_AUXDATA)
+		return false;
+
+	if (data == 0xe0) {
+		extended = true;
+		return true;
+	} else if (extended) {
+		extended = false;
+		switch (data) {
+		case GB_KEY_KBD_BACKLIGHT_KEYDOWN:
+			return true;
+		case GB_KEY_KBD_BACKLIGHT_KEYUP:
+			if (galaxybook_ptr->has_kbd_backlight)
+				schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work);
+			return true;
+
+		case GB_KEY_CAMERA_LENS_COVER_KEYDOWN:
+			return true;
+		case GB_KEY_CAMERA_LENS_COVER_KEYUP:
+			if (galaxybook_ptr->has_camera_lens_cover)
+				schedule_work(&galaxybook_ptr->camera_lens_cover_hotkey_work);
+			return true;
+
+		/* battery notification already sent to battery and ACPI device; ignore */
+		case GB_KEY_BATTERY_NOTIFY_KEYUP:
+		case GB_KEY_BATTERY_NOTIFY_KEYDOWN:
+			return true;
+
+		default:
+			/*
+			 * Report the previously filtered e0 before continuing
+			 * with the next non-filtered byte.
+			 */
+			serio_interrupt(port, 0xe0, 0);
+			return false;
+		}
+	}
+
+	return false;
+}
+
+static void galaxybook_i8042_filter_remove(void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	i8042_remove_filter(galaxybook_i8042_filter);
+	if (galaxybook->has_kbd_backlight)
+		cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work);
+	if (galaxybook->has_camera_lens_cover)
+		cancel_work_sync(&galaxybook->camera_lens_cover_hotkey_work);
+}
+
+static int galaxybook_i8042_filter_install(struct samsung_galaxybook *galaxybook)
+{
+	int err;
+
+	if (!galaxybook->has_kbd_backlight && !galaxybook->has_camera_lens_cover)
+		return 0;
+
+	if (galaxybook->has_kbd_backlight)
+		INIT_WORK(&galaxybook->kbd_backlight_hotkey_work,
+			  galaxybook_kbd_backlight_hotkey_work);
+
+	if (galaxybook->has_camera_lens_cover)
+		INIT_WORK(&galaxybook->camera_lens_cover_hotkey_work,
+			  galaxybook_camera_lens_cover_hotkey_work);
+
+	err = i8042_install_filter(galaxybook_i8042_filter);
+	if (err)
+		return err;
+
+	return devm_add_action_or_reset(&galaxybook->platform->dev,
+					galaxybook_i8042_filter_remove, galaxybook);
+}
+
+/*
+ * ACPI device setup
+ */
+
+static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct samsung_galaxybook *galaxybook = data;
+
+	switch (event) {
+	case ACPI_NOTIFY_BATTERY_STATE_CHANGED:
+	case ACPI_NOTIFY_DEVICE_ON_TABLE:
+	case ACPI_NOTIFY_DEVICE_OFF_TABLE:
+		break;
+	case ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE:
+		if (galaxybook->has_performance_mode)
+			platform_profile_cycle();
+		break;
+	default:
+		dev_warn(&galaxybook->acpi->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 = { 0 };
+	int err;
+
+	err = galaxybook_enable_acpi_feature(galaxybook, SASB_NOTIFICATIONS);
+	if (err)
+		return err;
+
+	buf.safn = SAFN;
+	buf.sasb = SASB_NOTIFICATIONS;
+	buf.gunm = GUNM_ACPI_NOTIFY_ENABLE;
+	buf.guds[0] = GUDS_ACPI_NOTIFY_ENABLE;
+
+	return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS,
+				      &buf, SAWB_LEN_SETTINGS, &buf);
+}
+
+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,
+				   ACPI_METHOD_ENABLE, 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, ACPI_METHOD_ENABLE,
+					    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_warn(&galaxybook->platform->dev, "failed to enable ACPI notifications; "
+			 "some hotkeys will not be supported\n");
+
+	err = galaxybook_enable_acpi_feature(galaxybook, SASB_POWER_MANAGEMENT);
+	if (err)
+		dev_warn(&galaxybook->acpi->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;
+
+	/* set static pointer here so it can be used in i8042 filter */
+	if (galaxybook_ptr)
+		return -EBUSY;
+	galaxybook_ptr = galaxybook;
+
+	galaxybook->platform = pdev;
+	galaxybook->acpi = adev;
+
+	dev_set_drvdata(&galaxybook->platform->dev, galaxybook);
+
+	err = galaxybook_input_init(galaxybook);
+	if (err)
+		return dev_err_probe(&galaxybook->platform->dev, err,
+				     "failed to initialize input device\n");
+
+	err = galaxybook_acpi_init(galaxybook);
+	if (err)
+		return dev_err_probe(&galaxybook->acpi->dev, err,
+				     "failed to initialize ACPI device\n");
+
+	galaxybook_profile_init(galaxybook);
+	galaxybook_battery_threshold_init(galaxybook);
+
+	err = galaxybook_camera_lens_cover_init(galaxybook);
+	if (err)
+		return dev_err_probe(&galaxybook->acpi->dev, err,
+				     "failed to initialize camera_lens_cover\n");
+
+	err = galaxybook_kbd_backlight_init(galaxybook);
+	if (err)
+		return dev_err_probe(&galaxybook->acpi->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 void galaxybook_remove(struct platform_device *pdev)
+{
+	if (galaxybook_ptr)
+		galaxybook_ptr = NULL;
+}
+
+static struct platform_driver galaxybook_platform_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.acpi_match_table = galaxybook_device_ids,
+	},
+	.probe = galaxybook_probe,
+	.remove = galaxybook_remove,
+};
+module_platform_driver(galaxybook_platform_driver);
+
+MODULE_AUTHOR("Joshua Grisham <josh@joshuagrisham.com>");
+MODULE_DESCRIPTION("Samsung Galaxy Book Extras");
+MODULE_LICENSE("GPL");