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 |
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, ¤t_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");
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, ¤t_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");
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
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
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, ¤t_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 > >
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
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
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 >
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 > [...]
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/
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 > >> [...]
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
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
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!
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 >
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.)
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 --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, ¤t_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");
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