From nobody Wed Dec 17 19:37:32 2025 Received: from mail-lf1-f45.google.com (mail-lf1-f45.google.com [209.85.167.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F1D551C07D1; Mon, 16 Dec 2024 10:39:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734345565; cv=none; b=WkZ6IXIkko+WtWu2agyMKqdOJvqvy7nRsgYcriQNXjVqLyjwI48Avgq6+Q+3A4+bx1lsUxqAVG+eoXntQzMUg36xMCTulEPysNg8h5Fnhh3IYDi94lIiFzD8vO+dlNpipXP8rwY80oD6mfg1yAMUh3LhaXzorMSCjYmEDDIh7xs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1734345565; c=relaxed/simple; bh=TzJGMYnJO/3kUKXbeB9bIG6qXDL8q6xfqX9J2Ru7cOQ=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version:Content-Type; b=K7WhKfdjt3AOXISTFs+oMX5QUmTLEDE79FXYYV9+3Ugv+yN76L8XCyblfWAQiUkgsf6jkV24Qsg+J1/JsGDzGYNHGK4EtVF04DigyFUVuqlbSMGtT4pTTum9F3EM+ioFzAyNTMwJGYMIpaF+fezYiphX2oXxOAz0Lm9Lu8gC2Og= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=joshuagrisham.com; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.167.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=joshuagrisham.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-lf1-f45.google.com with SMTP id 2adb3069b0e04-53e399e3310so4743559e87.1; Mon, 16 Dec 2024 02:39:20 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1734345559; x=1734950359; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=wc6DcbYvQereSEHOElX9Do+9/+PYx1M+DfX0v9S5Qmc=; b=s5g0S+UrsFCI3T9jMdJUyikXDbYDE5t6JZXxROfUnaXFd1aMn6rXxPqdrwPpzNLJh6 xnN10T6439ahOFGuXqKChRXtbsqIjyAIdJxYkPeD9P2iTiSswrRcMhUdtoZqs+shkLvO 5q88MCW9hLJVA8fRlZ6BhaaL7b0ooFxOlSntZaW8JFCscMMxeCEVzQqZF/79XLGt9EHK XxJlAPFukmF9kYaEp9Gs4FEB2M39MK5PfsQrhAkurmYbvhVmnKbiyD0rMFenH1Icjs1j 8FwBsimKGitNiPw0bWQnYXUaBAx13SxyVPrpPV+1fI4u1gWkWHd5DeT+H8+JjxJX9fHT qRWw== X-Forwarded-Encrypted: i=1; AJvYcCVfv2RaMyZOnXIwV9W7uYYn2EW5wF5hKgUgVdRRp1c+bWRpbdqOrGkOwlRJqKPhzrD2Bobw3hitCagfbQPq@vger.kernel.org, AJvYcCWDXRCsTBQveoL0XLa4h5QdA5XnTECv6+ri0umXz1DWdMH8oMl+OfG8JroROS+1oOXpqAY94kctC9o=@vger.kernel.org, AJvYcCWvENiYYUciIvmaAuDeE4aJSD3x3pjXJQOVGZtmuWu/GabGEbQfP+yOuaV1aA+lW51jKIEezBN2H0lm9TaK/vGHBux+Xg==@vger.kernel.org X-Gm-Message-State: AOJu0YwYTiCipr86xhgt/ksVZ0UUWtfc38XMShGiTPie1TgBcvoMHJUk uR7+4fEVDIL59zE0QVGPPtW1JMAYfulWOkCQKR+58kv29xXrHHIu X-Gm-Gg: ASbGncvHoYlfCnoP3VEt18e9Moj8GwMVMZfrfniJPWRMgeRFLM6c5p2phEnqnFAv9kd FKZAjIDp0/OAj0YSmcrF3mT2J7urEr7+jTzcsNuN1D33Tw2qau9TusvyW318OU7JylUah7KkkHD vFxN6td3XuALo/TFFY0aqJzFbOub9RgRlm14b8TAzwbICIDF+LXms9/jKf5U5Fi2qkIrMZeXsZn ikCt2PRlnVO29yCSAa6epjx3rWzMRzyqvnCE3fCku1kavMOr1iskQ23d+pCRj+ziBC4lGccnWUA sWLdQM4B7P+NzESMao/bjA== X-Google-Smtp-Source: AGHT+IE9r3/Y5tfjPyfbeiTkq+iJn0OCRHiz03HuB/oeYYKyti/kdTpyUiRWa4LjBp0a0ePDdWo6VA== X-Received: by 2002:a05:6512:3b90:b0:53f:8c46:42a7 with SMTP id 2adb3069b0e04-54090561558mr3181003e87.26.1734345558340; Mon, 16 Dec 2024 02:39:18 -0800 (PST) Received: from galaxybook.local (82-183-24-76.customers.ownit.se. [82.183.24.76]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-54120c13ca2sm801117e87.212.2024.12.16.02.39.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 16 Dec 2024 02:39:16 -0800 (PST) From: Joshua Grisham To: ilpo.jarvinen@linux.intel.com, hdegoede@redhat.com, W_Armin@gmx.de, platform-driver-x86@vger.kernel.org, corbet@lwn.net, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Joshua Grisham Subject: [PATCH v3] platform/x86: samsung-galaxybook: Add samsung-galaxybook driver Date: Mon, 16 Dec 2024 11:38:54 +0100 Message-ID: <20241216103855.18457-1-josh@joshuagrisham.com> X-Mailer: git-send-email 2.45.2 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Adds a new driver for Samsung Galaxy Book series notebook devices. This includes the driver itself, a tracepoint header file, a new page in the documentation, and relevant updates to Kconfig, Makefile, and MAINTAINERS files related to this new driver. Signed-off-by: Joshua Grisham --- v1->v2: - Attempt to resolve all review comments from v1 as written here: https://lore.kernel.org/platform-driver-x86/53c5075b-1967-45d0-937f-463912d= d966d@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 --- .../laptops/samsung-galaxybook.rst | 280 ++++ MAINTAINERS | 8 + drivers/platform/x86/Kconfig | 18 + drivers/platform/x86/Makefile | 5 +- .../platform/x86/samsung-galaxybook-trace.h | 51 + drivers/platform/x86/samsung-galaxybook.c | 1380 +++++++++++++++++ 6 files changed, 1740 insertions(+), 2 deletions(-) create mode 100644 Documentation/admin-guide/laptops/samsung-galaxybook.rst create mode 100644 drivers/platform/x86/samsung-galaxybook-trace.h create mode 100644 drivers/platform/x86/samsung-galaxybook.c diff --git a/Documentation/admin-guide/laptops/samsung-galaxybook.rst b/Doc= umentation/admin-guide/laptops/samsung-galaxybook.rst new file mode 100644 index 000000000000..947810c5f998 --- /dev/null +++ b/Documentation/admin-guide/laptops/samsung-galaxybook.rst @@ -0,0 +1,280 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D +Samsung Galaxy Book Extras +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D + +December 15, 2024 + +Joshua Grisham + +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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +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 current= ly +available as of this writing, and could include other Samsung notebook dev= ices +as well. + + +Status +=3D=3D=3D=3D=3D=3D + +The following features are currently supported: + +- :ref:`Keyboard backlight ` control +- :ref:`Performance mode ` control implemented using the + platform profile interface +- :ref:`Battery charge control end threshold + ` (stop charging battery at given + percentage value) implemented as a battery device extension +- :ref:`Settings Attributes ` to allow control of var= ious + device settings +- :ref:`Handling of Fn hotkeys ` for various acti= ons +- :ref:`Tracepoint ` event for debugging ACPI device communica= tion + +Because different models of these devices can vary in their features, ther= e is +logic built within the driver which attempts to test each implemented feat= ure +for a valid response before enabling its support (registering additional d= evices +or extensions, adding sysfs attributes, etc). Therefore, it can be importa= nt 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`` + + +Parameters +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver includes a list of boolean parameters that allow for manually +enabling or disabling various features: + +- ``kbd_backlight``: Enable Keyboard Backlight control (default on) +- ``performance_mode``: Enable Performance Mode control (default on) +- ``battery_threshold``: Enable battery charge threshold control (default = on) +- ``allow_recording``: Enable control to allow or block access to camera a= nd + microphone (default on) +- ``i8042_filter``: Enable capture and execution of keyboard-based hotkey = events + (default on) + +.. note:: + Even if you explicitly try to enable a feature using its parameter, supp= ort + for it will still be evaluated by the driver, and the feature will be + disabled if it does not appear to be supported on your device. + +The availability of various sysfs file-based "settings" attributes +(``usb_charge``, ``start_on_lid_open``, etc) will be determined automatica= lly +and cannot be manually disabled at this time. + + +.. _keyboard-backlight: + +Keyboard backlight +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Controlled by parameter: ``kbd_backlight`` + +A new LED class named ``samsung-galaxybook::kbd_backlight`` is created whi= ch +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 values 0 to 3 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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Controlled by parameter: ``performance_mode`` + +This driver implements the +Documentation/userspace-api/sysfs-platform_profile.rst interface for worki= ng +with the "performance mode" function of the Samsung ACPI device. + +Mapping of each Samsung "performance mode" to its respective platform prof= ile is +done dynamically based on a list of the supported modes reported by the de= vice +itself. Preference is given to always try and map ``low-power``, ``balance= d``, +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 modul= e 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 pro= file +has been previously set. + + +.. _battery-charge-control-end-threshold: + +Battery charge control end threshold +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Controlled by parameter: ``battery_threshold`` + +This platform driver will add the ability to set the battery's charge cont= rol +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 0 to represent "off", as these are the v= alues +currently recognized by the various Windows-based Samsung applications and +services as "on" or "off". Otherwise, the device will accept any value bet= ween 0 +(off) and 99 as the percentage that you wish the battery to stop charging = at. + +.. note:: + If you try to set a value of 100, the driver will also accept this input= , but + will set the attribute value to 0 (i.e. 100% will "remove" the end thres= hold). + + +.. _settings-attributes: + +Settings Attributes +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Various hardware settings can be controlled by the following sysfs attribu= tes: + +- ``allow_recording`` (allows or blocks usage of built-in camera and micro= phone) +- ``start_on_lid_open`` (power on automatically when opening the lid) +- ``usb_charge`` (allows USB ports to provide power even when device is of= f) + +These attributes will be available under the path for your supported ACPI = Device +ID's platform device (``SAM0428``, ``SAM0429``, etc), and can most reliably +be found by seeing which device has been bound to the ``samsung-galaxybook= `` +driver. Here are some examples: :: + + # find which device ID has been bound to the driver + ls /sys/bus/platform/drivers/samsung-galaxybook/ | grep SAM + + # see SAM0429 attributes + ls /sys/bus/platform/drivers/samsung-galaxybook/SAM0429\:00 + + # see attributes no matter the device ID (using wildcard expansion) + ls /sys/bus/platform/drivers/samsung-galaxybook/SAM* + +Most shells should support using wildcard expansion to directly read and w= rite +these attributes using the above pattern. Example: :: + + # read value of start_on_lid_open + cat /sys/bus/platform/drivers/samsung-galaxybook/SAM*/start_on_lid_open + + # turn on start_on_lid_open + echo true | sudo tee /sys/bus/platform/drivers/samsung-galaxybook/SAM*/s= tart_on_lid_open + +It is also possible to use a udev rule to create a fixed-path symlink to y= our +device under ``/dev`` (e.g. ``/dev/samsung-galaxybook``), no matter the de= vice +ID, to further simplify reading and writing these attributes in the usersp= ace. + +Allow recording (allow_recording) +--------------------------------- + +``/sys/bus/platform/drivers/samsung-galaxybook/SAM*/allow_recording`` + +Controlled by parameter: ``allow_recording`` + +Controls the "Allow recording" setting, which allows or blocks usage of the +built-in camera and microphone (boolean). + +Start on lid open (start_on_lid_open) +------------------------------------- + +``/sys/bus/platform/drivers/samsung-galaxybook/SAM*/start_on_lid_open`` + +Controls the "Start on lid open" setting, which sets the device to power on +automatically when the lid is opened (boolean). + +USB charge (usb_charge) +----------------------- + +``/sys/bus/platform/drivers/samsung-galaxybook/SAM*/usb_charge`` + +Controls the "USB charge" setting, which allows USB ports to provide power= even +when the device is turned off (boolean). + +.. note:: + For most devices, this setting seems to only apply to the USB-C ports. + + +.. _keyboard-hotkey-actions: + +Keyboard hotkey actions (i8042 filter) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Controlled by parameter: ``i8042_filter`` + +The i8042 filter will swallow the keyboard events for the Fn+F9 hotkey (Mu= lti- +level keyboard backlight toggle) and Fn+F10 hotkey (Allow/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_change= d`` +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 "Allow recording" setting. + + +ACPI notifications and ACPI hotkey actions +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +There is a new "Samsung Galaxy Book extra buttons" input device created wh= ich +will send input events for the following notifications from the ACPI devic= e: + +- Notification when the battery charge control end threshold has been reac= hed + and the "battery saver" feature has stopped the battery from charging +- Notification when the device has been placed on a table (not available o= n all + models) +- Notification when the device has been lifted from a table (not available= on + all models) + +The Fn+F11 Performance mode hotkey is received as an ACPI notification. It= will +be handled in a similar way as the Fn+F9 and Fn+F10 hotkeys; namely, that = the +keypress will be swallowed by the driver and each press will cycle to the = next +available platform profile. + + +.. _tracepoint: + +Tracepoint for debugging ACPI communication +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +A new tracepoint event ``samsung_galaxybook:samsung_galaxybook_acpi`` will +provide a trace of the buffers sent to, and received from, the ACPI device +methods. + +Here is an example of how to use it: :: + + # Enable tracepoint events + echo 1 | sudo tee /sys/kernel/tracing/events/samsung_galaxybook/enable + + # Perform some actions using the driver and then read the result + sudo cat /sys/kernel/tracing/trace + + # Disable tracepoint events when you are finished + echo 0 | sudo tee /sys/kernel/tracing/events/samsung_galaxybook/enable diff --git a/MAINTAINERS b/MAINTAINERS index 3809931b9240..9e3b45cf799f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20733,6 +20733,14 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/s3c-fb.c =20 +SAMSUNG GALAXY BOOK EXTRAS DRIVER +M: Joshua Grisham +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/admin-guide/laptops/samsung-galaxybook.rst +F: drivers/platform/x86/samsung-galaxybook-trace.h +F: drivers/platform/x86/samsung-galaxybook.c + SAMSUNG INTERCONNECT DRIVERS M: Sylwester Nawrocki M: Artur =C5=9Awigo=C5=84 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0258dd879d64..03f4fb0e93e7 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. =20 +config SAMSUNG_GALAXYBOOK + tristate "Samsung Galaxy Book extras driver" + depends on ACPI + depends on ACPI_BATTERY + depends on INPUT + depends on SERIO_I8042 + select ACPI_PLATFORM_PROFILE + select INPUT_SPARSEKMAP + select NEW_LEDS + select LEDS_CLASS + help + This is a driver for Samsung Galaxy Book series notebooks. It adds + support for the keyboard backlight control, performance mode control, f= an + speed reporting, function keys, and various other device controls. + + For more information about this driver, see + . + config SAMSUNG_LAPTOP tristate "Samsung Laptop driver" depends on RFKILL || RFKILL =3D 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) +=3D pcengines-apuv2.o obj-$(CONFIG_BARCO_P50_GPIO) +=3D barco-p50-gpio.o =20 # Samsung -obj-$(CONFIG_SAMSUNG_LAPTOP) +=3D samsung-laptop.o -obj-$(CONFIG_SAMSUNG_Q10) +=3D samsung-q10.o +obj-$(CONFIG_SAMSUNG_GALAXYBOOK) +=3D samsung-galaxybook.o +obj-$(CONFIG_SAMSUNG_LAPTOP) +=3D samsung-laptop.o +obj-$(CONFIG_SAMSUNG_Q10) +=3D samsung-q10.o =20 # Toshiba obj-$(CONFIG_TOSHIBA_BT_RFKILL) +=3D toshiba_bluetooth.o diff --git a/drivers/platform/x86/samsung-galaxybook-trace.h b/drivers/plat= form/x86/samsung-galaxybook-trace.h new file mode 100644 index 000000000000..09ab6dbe6586 --- /dev/null +++ b/drivers/platform/x86/samsung-galaxybook-trace.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Samsung Galaxy Book series extras driver tracepoint events + * + * Copyright (c) 2024 Joshua Grisham + */ + +#if !defined(_TRACE_SAMSUNG_GALAXYBOOK_H_) || defined(TRACE_HEADER_MULTI_R= EAD) +#define _TRACE_SAMSUNG_GALAXYBOOK_H_ + +#include +#include + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM samsung_galaxybook + +#define GALAXYBOOK_TRACE_MAX_ACPI_BUF_LENGTH 0x100 + +TRACE_EVENT(samsung_galaxybook_acpi, + TP_PROTO(const char *devname, const char *method, const char *label, u8 *= buf, size_t len), + TP_ARGS(devname, method, label, buf, len), + TP_STRUCT__entry( + __string(devname, devname) + __string(method, method) + __string(label, label) + __array(u8, buf, GALAXYBOOK_TRACE_MAX_ACPI_BUF_LENGTH) + __field(size_t, len) + ), + TP_fast_assign( + __assign_str(devname); + __assign_str(method); + __assign_str(label); + memcpy(__entry->buf, buf, len); + __entry->len =3D len; + ), + TP_printk("device: %s, method: %s, %s: %s", + __get_str(devname), + __get_str(method), + __get_str(label), + __print_hex(__entry->buf, __entry->len)) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#undef TRACE_INCLUDE_FILE + +#define TRACE_INCLUDE_PATH ../../drivers/platform/x86 +#define TRACE_INCLUDE_FILE samsung-galaxybook-trace + +#include diff --git a/drivers/platform/x86/samsung-galaxybook.c b/drivers/platform/x= 86/samsung-galaxybook.c new file mode 100644 index 000000000000..7baa3441fbfa --- /dev/null +++ b/drivers/platform/x86/samsung-galaxybook.c @@ -0,0 +1,1380 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung Galaxy Book series extras driver + * + * Copyright (c) 2024 Joshua Grisham + * + * With contributions to the SCAI ACPI device interface: + * Copyright (c) 2024 Giulio Girardi + * + * Implementation inspired by existing x86 platform drivers. + * Thank you to the authors! + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include "samsung-galaxybook-trace.h" + +#define DRIVER_NAME "samsung-galaxybook" + +/* + * Module parameters + */ + +static bool kbd_backlight =3D true; +static bool battery_threshold =3D true; +static bool performance_mode =3D true; +static bool allow_recording =3D true; +static bool i8042_filter =3D true; + +module_param(kbd_backlight, bool, 0); +MODULE_PARM_DESC(kbd_backlight, "Enable Keyboard Backlight control (defaul= t on)"); +module_param(battery_threshold, bool, 0); +MODULE_PARM_DESC(battery_threshold, "Enable battery charge threshold contr= ol (default on)"); +module_param(performance_mode, bool, 0); +MODULE_PARM_DESC(performance_mode, "Enable Performance Mode control (defau= lt on)"); +module_param(allow_recording, bool, 0); +MODULE_PARM_DESC(allow_recording, + "Enable control to allow or block access to camera and microphone (defa= ult on)"); +module_param(i8042_filter, bool, 0); +MODULE_PARM_DESC(i8042_filter, "Enable capturing keyboard hotkey events (d= efault on)"); + +/* + * Device definitions and matching + */ + +static const struct acpi_device_id galaxybook_device_ids[] =3D { + { "SAM0427" }, + { "SAM0428" }, + { "SAM0429" }, + { "SAM0430" }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, galaxybook_device_ids); + +struct samsung_galaxybook { + struct platform_device *platform; + struct acpi_device *acpi; + struct mutex acpi_lock; + + struct led_classdev kbd_backlight; + + struct input_dev *input; + struct mutex input_lock; + + void *i8042_filter_ptr; + struct work_struct kbd_backlight_hotkey_work; + struct work_struct allow_recording_hotkey_work; + + struct acpi_battery_hook battery_hook; + struct device_attribute charge_control_end_threshold_attr; + + u8 *profile_performance_modes; + struct platform_profile_handler profile_handler; +}; +static struct samsung_galaxybook *galaxybook_ptr; + +struct sawb { + u16 safn; + u16 sasb; + u8 rflg; + union { + struct { + u8 gunm; + u8 guds[250]; + }; + 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; + }; + struct { + u8 iob_prefix[18]; + u8 iob_values[10]; + }; + }; +}; + +#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_CHARGE_GET 0x67 +#define SASB_USB_CHARGE_SET 0x68 +#define SASB_NOTIFICATIONS 0x86 +#define SASB_ALLOW_RECORDING 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_CHARGE_GET 0x80 +#define GUNM_USB_CHARGE_ON 0x81 +#define GUNM_USB_CHARGE_OFF 0x80 +#define GUDS_START_ON_LID_OPEN 0xa3 +#define GUDS_START_ON_LID_OPEN_GET 0x81 +#define GUDS_START_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 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 =3D + 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_ALLOW_RECORDING_KEYDOWN 0x1f +#define GB_KEY_ALLOW_RECORDING_KEYUP 0x9f + +static const struct key_entry galaxybook_acpi_keymap[] =3D { + { KE_KEY, ACPI_NOTIFY_BATTERY_STATE_CHANGED, { KEY_BATTERY } }, + { KE_KEY, ACPI_NOTIFY_DEVICE_ON_TABLE, { KEY_F14 } }, + { KE_KEY, ACPI_NOTIFY_DEVICE_OFF_TABLE, { KEY_F15 } }, + { KE_KEY, ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE, { KEY_UNKNOWN } }, + { KE_END, 0 }, +}; + +/* + * ACPI method handling + */ + +static int galaxybook_acpi_method(struct samsung_galaxybook *galaxybook, a= cpi_string method, + struct sawb *in_buf, size_t len, const char *purpose_str, + struct sawb *out_buf) +{ + struct acpi_buffer output =3D {ACPI_ALLOCATE_BUFFER, NULL}; + union acpi_object in_obj, *out_obj; + struct acpi_object_list input; + acpi_status status; + int err; + + mutex_lock(&galaxybook->acpi_lock); + + in_obj.type =3D ACPI_TYPE_BUFFER; + in_obj.buffer.length =3D len; + in_obj.buffer.pointer =3D (u8 *)in_buf; + + input.count =3D 1; + input.pointer =3D &in_obj; + + trace_samsung_galaxybook_acpi(dev_name(&galaxybook->acpi->dev), method, p= urpose_str, + in_obj.buffer.pointer, in_obj.buffer.length); + + status =3D acpi_evaluate_object_typed(galaxybook->acpi->handle, method, &= input, &output, + ACPI_TYPE_BUFFER); + + if (ACPI_FAILURE(status)) { + dev_err(&galaxybook->acpi->dev, "failed %s with ACPI method %s; got %s\n= ", + purpose_str, method, acpi_format_exception(status)); + err =3D -ENXIO; + goto out_free; + } + + out_obj =3D output.pointer; + + trace_samsung_galaxybook_acpi(dev_name(&galaxybook->acpi->dev), method, "= response", + out_obj->buffer.pointer, out_obj->buffer.length); + + if (out_obj->buffer.length !=3D len || out_obj->buffer.length < SAWB_GUNM= _POS + 1) { + dev_err(&galaxybook->acpi->dev, "failed %s with ACPI method %s; " + "response length mismatch\n", + purpose_str, method); + err =3D -ETOOSMALL; + goto out_free; + } + if (out_obj->buffer.pointer[SAWB_RFLG_POS] !=3D RFLG_SUCCESS) { + dev_err(&galaxybook->acpi->dev, "failed %s with ACPI method %s; " + "device did not respond with success code 0x%x\n", + purpose_str, method, RFLG_SUCCESS); + err =3D -EIO; + goto out_free; + } + if (out_obj->buffer.pointer[SAWB_GUNM_POS] =3D=3D GUNM_FAIL) { + dev_err(&galaxybook->acpi->dev, + "failed %s with ACPI method %s; device responded with failure code 0x%x= \n", + purpose_str, method, GUNM_FAIL); + err =3D -EIO; + goto out_free; + } + + memcpy(out_buf, out_obj->buffer.pointer, len); + err =3D 0; + +out_free: + kfree(out_obj); + mutex_unlock(&galaxybook->acpi_lock); + return err; +} + +static int galaxybook_enable_acpi_feature(struct samsung_galaxybook *galax= ybook, const u16 sasb) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D sasb; + buf.gunm =3D GUNM_FEATURE_ENABLE; + buf.guds[0] =3D GUDS_FEATURE_ENABLE; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "enabling ACPI feature", &buf); + if (err) + return err; + + if (buf.gunm !=3D GUNM_FEATURE_ENABLE_SUCCESS && buf.guds[0] !=3D GUDS_FE= ATURE_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 =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_KBD_BACKLIGHT; + buf.gunm =3D GUNM_SET; + + buf.guds[0] =3D brightness; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "setting kbd_backlight brightness", &buf); + if (err) + return err; + + galaxybook->kbd_backlight.brightness =3D brightness; + + return 0; +} + +static int kbd_backlight_acpi_get(struct samsung_galaxybook *galaxybook, + enum led_brightness *brightness) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_KBD_BACKLIGHT; + buf.gunm =3D GUNM_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "getting kbd_backlight brightness", &buf); + if (err) + return err; + + *brightness =3D buf.gunm; + galaxybook->kbd_backlight.brightness =3D buf.gunm; + + return 0; +} + +static int kbd_backlight_store(struct led_classdev *led, + const enum led_brightness brightness) +{ + struct samsung_galaxybook *galaxybook =3D + container_of(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 =3D + container_of(led, struct samsung_galaxybook, kbd_backlight); + enum led_brightness brightness; + int err; + + err =3D kbd_backlight_acpi_get(galaxybook, &brightness); + if (err) + return err; + + return brightness; +} + +static int galaxybook_kbd_backlight_init(struct samsung_galaxybook *galaxy= book) +{ + struct led_init_data init_data =3D {}; + enum led_brightness brightness; + int err; + + err =3D galaxybook_enable_acpi_feature(galaxybook, SASB_KBD_BACKLIGHT); + if (err) + return err; + + /* verify we can read the value, otherwise init should stop and fail */ + err =3D kbd_backlight_acpi_get(galaxybook, &brightness); + if (err) + return err; + + init_data.devicename =3D DRIVER_NAME; + init_data.default_label =3D ":" LED_FUNCTION_KBD_BACKLIGHT; + init_data.devname_mandatory =3D true; + + galaxybook->kbd_backlight.brightness_get =3D kbd_backlight_show; + galaxybook->kbd_backlight.brightness_set_blocking =3D kbd_backlight_store; + galaxybook->kbd_backlight.flags =3D LED_BRIGHT_HW_CHANGED; + galaxybook->kbd_backlight.max_brightness =3D KBD_BACKLIGHT_MAX_BRIGHTNESS; + + return devm_led_classdev_register_ext(&galaxybook->platform->dev, + &galaxybook->kbd_backlight, &init_data); +} + +/* + * Platform device attributes (configuration properties which can be contr= olled via userspace) + */ + +/* Start on lid open (device should power on when lid is opened) */ + +static int start_on_lid_open_acpi_set(struct samsung_galaxybook *galaxyboo= k, const bool value) +{ + struct sawb buf =3D { 0 }; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_POWER_MANAGEMENT; + buf.gunm =3D GUNM_POWER_MANAGEMENT; + buf.guds[0] =3D GUDS_START_ON_LID_OPEN; + buf.guds[1] =3D GUDS_START_ON_LID_OPEN_SET; + buf.guds[2] =3D value ? 1 : 0; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SAW= B_LEN_SETTINGS, + "setting start_on_lid_open", &buf); +} + +static int start_on_lid_open_acpi_get(struct samsung_galaxybook *galaxyboo= k, bool *value) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_POWER_MANAGEMENT; + buf.gunm =3D GUNM_POWER_MANAGEMENT; + buf.guds[0] =3D GUDS_START_ON_LID_OPEN; + buf.guds[1] =3D GUDS_START_ON_LID_OPEN_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "getting start_on_lid_open", &buf); + if (err) + return err; + + *value =3D buf.guds[1]; + + return 0; +} + +static ssize_t start_on_lid_open_store(struct device *dev, struct device_a= ttribute *attr, + const char *buffer, size_t count) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + if (!count) + return -EINVAL; + + err =3D kstrtobool(buffer, &value); + if (err) + return err; + + err =3D start_on_lid_open_acpi_set(galaxybook, value); + if (err) + return err; + + return count; +} + +static ssize_t start_on_lid_open_show(struct device *dev, struct device_at= tribute *attr, + char *buffer) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + err =3D start_on_lid_open_acpi_get(galaxybook, &value); + if (err) + return err; + + return sysfs_emit(buffer, "%u\n", value); +} + +static DEVICE_ATTR_RW(start_on_lid_open); + +/* USB Charge (USB ports can charge other devices even when device is powe= red off) */ + +static int usb_charge_acpi_set(struct samsung_galaxybook *galaxybook, cons= t bool value) +{ + struct sawb buf =3D { 0 }; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_USB_CHARGE_SET; + buf.gunm =3D value ? GUNM_USB_CHARGE_ON : GUNM_USB_CHARGE_OFF; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SAW= B_LEN_SETTINGS, + "setting usb_charge", &buf); +} + +static int usb_charge_acpi_get(struct samsung_galaxybook *galaxybook, bool= *value) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_USB_CHARGE_GET; + buf.gunm =3D GUNM_USB_CHARGE_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "getting usb_charge", &buf); + if (err) + return err; + + *value =3D buf.gunm; + + return 0; +} + +static ssize_t usb_charge_store(struct device *dev, struct device_attribut= e *attr, + const char *buffer, size_t count) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + if (!count) + return -EINVAL; + + err =3D kstrtobool(buffer, &value); + if (err) + return err; + + err =3D usb_charge_acpi_set(galaxybook, value); + if (err) + return err; + + return count; +} + +static ssize_t usb_charge_show(struct device *dev, struct device_attribute= *attr, char *buffer) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + err =3D usb_charge_acpi_get(galaxybook, &value); + if (err) + return err; + + return sysfs_emit(buffer, "%u\n", value); +} + +static DEVICE_ATTR_RW(usb_charge); + +/* Allow recording (allows or blocks access to camera and microphone) */ + +static int allow_recording_acpi_set(struct samsung_galaxybook *galaxybook,= const bool value) +{ + struct sawb buf =3D { 0 }; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_ALLOW_RECORDING; + buf.gunm =3D GUNM_SET; + buf.guds[0] =3D value ? 1 : 0; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SAW= B_LEN_SETTINGS, + "setting allow_recording", &buf); +} + +static int allow_recording_acpi_get(struct samsung_galaxybook *galaxybook,= bool *value) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_ALLOW_RECORDING; + buf.gunm =3D GUNM_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "getting allow_recording", &buf); + if (err) + return err; + + *value =3D buf.gunm =3D=3D 1; + + return 0; +} + +static ssize_t allow_recording_store(struct device *dev, struct device_att= ribute *attr, + const char *buffer, size_t count) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + if (!count) + return -EINVAL; + + err =3D kstrtobool(buffer, &value); + if (err) + return err; + + err =3D allow_recording_acpi_set(galaxybook, value); + if (err) + return err; + + return count; +} + +static ssize_t allow_recording_show(struct device *dev, struct device_attr= ibute *attr, char *buffer) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + err =3D allow_recording_acpi_get(galaxybook, &value); + if (err) + return err; + + return sysfs_emit(buffer, "%u\n", value); +} + +static DEVICE_ATTR_RW(allow_recording); + +static umode_t galaxybook_attr_is_visible(struct kobject *kobj, struct att= ribute *attr, int idx) +{ + struct device *dev =3D kobj_to_dev(kobj); + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(dev); + bool value; + int err; + + if (attr =3D=3D &dev_attr_start_on_lid_open.attr) { + err =3D start_on_lid_open_acpi_get(galaxybook, &value); + return err ? 0 : attr->mode; + } + + if (attr =3D=3D &dev_attr_usb_charge.attr) { + err =3D usb_charge_acpi_get(galaxybook, &value); + return err ? 0 : attr->mode; + } + + if (attr =3D=3D &dev_attr_allow_recording.attr) { + if (!allow_recording) + return 0; + err =3D galaxybook_enable_acpi_feature(galaxybook, SASB_ALLOW_RECORDING); + if (err) { + dev_dbg(&galaxybook->platform->dev, + "failed to initialize ACPI allow_recording feature\n"); + allow_recording =3D false; + return 0; + } + err =3D allow_recording_acpi_get(galaxybook, &value); + if (err) { + allow_recording =3D false; + return 0; + } + return attr->mode; + } + + return attr->mode; +} + +static struct attribute *galaxybook_attrs[] =3D { + &dev_attr_start_on_lid_open.attr, + &dev_attr_usb_charge.attr, + &dev_attr_allow_recording.attr, +}; + +static const struct attribute_group galaxybook_attrs_group =3D { + .attrs =3D galaxybook_attrs, + .is_visible =3D galaxybook_attr_is_visible, +}; + +/* + * Battery Extension (adds charge_control_end_threshold to the battery dev= ice) + */ + +static int charge_control_end_threshold_acpi_set(struct samsung_galaxybook= *galaxybook, u8 value) +{ + struct sawb buf =3D { 0 }; + + if (value > 100) + return -EINVAL; + /* if setting to 100, should be set to 0 (no threshold) */ + if (value =3D=3D 100) + value =3D 0; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_POWER_MANAGEMENT; + buf.gunm =3D GUNM_POWER_MANAGEMENT; + buf.guds[0] =3D GUDS_BATTERY_CHARGE_CONTROL; + buf.guds[1] =3D GUDS_BATTERY_CHARGE_CONTROL_SET; + buf.guds[2] =3D value; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SAW= B_LEN_SETTINGS, + "setting battery charge_control_end_threshold", &buf); +} + +static int charge_control_end_threshold_acpi_get(struct samsung_galaxybook= *galaxybook, u8 *value) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_POWER_MANAGEMENT; + buf.gunm =3D GUNM_POWER_MANAGEMENT; + buf.guds[0] =3D GUDS_BATTERY_CHARGE_CONTROL; + buf.guds[1] =3D GUDS_BATTERY_CHARGE_CONTROL_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SA= WB_LEN_SETTINGS, + "getting battery charge_control_end_threshold", &buf); + if (err) + return err; + + *value =3D buf.guds[1]; + + return 0; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, stru= ct device_attribute *attr, + const char *buffer, size_t count) +{ + struct samsung_galaxybook *galaxybook =3D + container_of(attr, struct samsung_galaxybook, charge_control_end_thresho= ld_attr); + u8 value; + int err; + + if (!count) + return -EINVAL; + + err =3D kstrtou8(buffer, 0, &value); + if (err) + return err; + + err =3D 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, struc= t device_attribute *attr, + char *buffer) +{ + struct samsung_galaxybook *galaxybook =3D + container_of(attr, struct samsung_galaxybook, charge_control_end_thresho= ld_attr); + u8 value; + int err; + + err =3D charge_control_end_threshold_acpi_get(galaxybook, &value); + if (err) + return err; + + return sysfs_emit(buffer, "%d\n", value); +} + +static int galaxybook_battery_add(struct power_supply *battery, struct acp= i_battery_hook *hook) +{ + struct samsung_galaxybook *galaxybook =3D + 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 =3D + container_of(hook, struct samsung_galaxybook, battery_hook); + + device_remove_file(&battery->dev, &galaxybook->charge_control_end_thresho= ld_attr); + return 0; +} + +static int galaxybook_battery_threshold_init(struct samsung_galaxybook *ga= laxybook) +{ + struct acpi_battery_hook *hook; + struct device_attribute *attr; + u8 value; + int err; + + err =3D charge_control_end_threshold_acpi_get(galaxybook, &value); + if (err) + return err; + + hook =3D &galaxybook->battery_hook; + hook->add_battery =3D galaxybook_battery_add; + hook->remove_battery =3D galaxybook_battery_remove; + hook->name =3D "Samsung Galaxy Book Battery Extension"; + + attr =3D &galaxybook->charge_control_end_threshold_attr; + sysfs_attr_init(attr->attr); + attr->attr.name =3D "charge_control_end_threshold"; + attr->attr.mode =3D 0644; + attr->show =3D charge_control_end_threshold_show; + attr->store =3D charge_control_end_threshold_store; + + return devm_battery_hook_register(&galaxybook->platform->dev, &galaxybook= ->battery_hook); +} + +/* + * Platform Profile / Performance mode + */ + +static int performance_mode_acpi_set(struct samsung_galaxybook *galaxybook, + const u8 performance_mode) +{ + struct sawb buf =3D { 0 }; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_PERFORMANCE_MODE; + export_guid(buf.caid, &PERFORMANCE_MODE_GUID); + buf.fncn =3D FNCN_PERFORMANCE_MODE; + buf.subn =3D SUBN_PERFORMANCE_MODE_SET; + buf.iob0 =3D performance_mode; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE, &= buf, + SAWB_LEN_PERFORMANCE_MODE, "setting performance_mode", &buf); +} + +static int performance_mode_acpi_get(struct samsung_galaxybook *galaxybook= , u8 *performance_mode) +{ + struct sawb buf =3D { 0 }; + int err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_PERFORMANCE_MODE; + export_guid(buf.caid, &PERFORMANCE_MODE_GUID); + buf.fncn =3D FNCN_PERFORMANCE_MODE; + buf.subn =3D SUBN_PERFORMANCE_MODE_GET; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE, = &buf, + SAWB_LEN_PERFORMANCE_MODE, "getting performance_mode", &buf); + if (err) + return err; + + *performance_mode =3D buf.iob0; + + return 0; +} + +static int get_performance_mode_profile(struct samsung_galaxybook *galaxyb= ook, + const u8 performance_mode, + enum platform_profile_option *profile) +{ + for (int i =3D 0; i < PLATFORM_PROFILE_LAST; i++) { + if (galaxybook->profile_performance_modes[i] =3D=3D performance_mode) { + if (profile) + *profile =3D 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 =3D + 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 =3D + container_of(pprof, struct samsung_galaxybook, profile_handler); + u8 performance_mode; + int err; + + err =3D 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 =3D data; + + platform_profile_remove(&galaxybook->profile_handler); +} + +#define IGNORE_PERFORMANCE_MODE_MAPPING -1 + +static int galaxybook_profile_init(struct samsung_galaxybook *galaxybook) +{ + u8 current_performance_mode; + struct sawb buf =3D { 0 }; + int mapped_profiles; + int mode_profile; + int err; + int i; + + galaxybook->profile_handler.profile_get =3D galaxybook_platform_profile_g= et; + galaxybook->profile_handler.profile_set =3D galaxybook_platform_profile_s= et; + + /* fetch supported performance mode values from ACPI method */ + buf.safn =3D SAFN; + buf.sasb =3D SASB_PERFORMANCE_MODE; + export_guid(buf.caid, &PERFORMANCE_MODE_GUID); + buf.fncn =3D FNCN_PERFORMANCE_MODE; + buf.subn =3D SUBN_PERFORMANCE_MODE_LIST; + + err =3D galaxybook_acpi_method(galaxybook, ACPI_METHOD_PERFORMANCE_MODE, + &buf, SAWB_LEN_PERFORMANCE_MODE, + "get supported performance modes", &buf); + if (err) + return err; + + /* set up profile_performance_modes with "unknown" as init value */ + galaxybook->profile_performance_modes =3D + kcalloc(PLATFORM_PROFILE_LAST, sizeof(u8), GFP_KERNEL); + if (!galaxybook->profile_performance_modes) + return -ENOMEM; + for (i =3D 0; i < PLATFORM_PROFILE_LAST; i++) + galaxybook->profile_performance_modes[i] =3D PERFORMANCE_MODE_UNKNOWN; + + /* + * Value returned in iob0 will have the number of supported performance m= odes. + * The performance mode values will then be given as a list after this (i= ob1-iobX). + * Loop backwards from last value to first value (to handle fallback case= s which come with + * smaller values) and map each supported value to its correct platform_p= rofile_option. + */ + for (i =3D buf.iob0; i > 0; i--) { + /* + * Prefer mapping to at least performance, balanced, and low-power profi= les, as they + * are the profiles which are typically supported by userspace tools + * (power-profiles-daemon, etc). + * - performance =3D "ultra", otherwise "performance" + * - balanced =3D "optimized", otherwise "performance" when "ultra" i= s supported + * - low-power =3D "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 =3D PLATFORM_PROFILE_PERFORMANCE; + break; + + case PERFORMANCE_MODE_PERFORMANCE: + /* if ultra exists, map performance to balanced-performance */ + if (galaxybook->profile_performance_modes[PLATFORM_PROFILE_PERFORMANCE]= !=3D + PERFORMANCE_MODE_UNKNOWN) + mode_profile =3D PLATFORM_PROFILE_BALANCED_PERFORMANCE; + else /* otherwise map it to performance instead */ + mode_profile =3D PLATFORM_PROFILE_PERFORMANCE; + break; + + case PERFORMANCE_MODE_SILENT: + /* silent always maps to low-power */ + mode_profile =3D 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] != =3D + PERFORMANCE_MODE_UNKNOWN) + mode_profile =3D PLATFORM_PROFILE_QUIET; + else /* otherwise map it to low-power for better userspace tool support= */ + mode_profile =3D PLATFORM_PROFILE_LOW_POWER; + break; + + case PERFORMANCE_MODE_OPTIMIZED: + /* optimized always maps to balanced */ + mode_profile =3D 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]= =3D=3D + PERFORMANCE_MODE_UNKNOWN) + mode_profile =3D PLATFORM_PROFILE_PERFORMANCE; + else /* otherwise, ignore */ + mode_profile =3D 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] = =3D=3D + PERFORMANCE_MODE_UNKNOWN) + mode_profile =3D PLATFORM_PROFILE_BALANCED; + else /* otherwise, ignore */ + mode_profile =3D IGNORE_PERFORMANCE_MODE_MAPPING; + break; + + default: /* any other value is not supported */ + mode_profile =3D IGNORE_PERFORMANCE_MODE_MAPPING; + break; + } + + /* if current mode value mapped to a supported platform_profile_option, = set it up */ + if (mode_profile !=3D IGNORE_PERFORMANCE_MODE_MAPPING) { + mapped_profiles++; + galaxybook->profile_performance_modes[mode_profile] =3D buf.iob_values[= i]; + set_bit(mode_profile, galaxybook->profile_handler.choices); + 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 =3D=3D 0) + return -ENODEV; + + err =3D platform_profile_register(&galaxybook->profile_handler); + if (err) + return err; + + /* now check currently set performance mode; if not supported then set de= fault profile */ + err =3D performance_mode_acpi_get(galaxybook, ¤t_performance_mode); + if (err) + goto err_remove_exit; + err =3D 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 =3D galaxybook_platform_profile_set(&galaxybook->profile_handler, + DEFAULT_PLATFORM_PROFILE); + if (err) + goto err_remove_exit; + } + + /* if adding dev remove action fails, remove now and return failure */ + err =3D devm_add_action_or_reset(&galaxybook->platform->dev, + galaxybook_profile_exit, galaxybook); + if (err) + goto err_remove_exit; + + return 0; + +err_remove_exit: + galaxybook_profile_exit(galaxybook); + return err; +} + +/* + * Hotkey work and filters + */ + +static void galaxybook_kbd_backlight_hotkey_work(struct work_struct *work) +{ + struct samsung_galaxybook *galaxybook =3D + container_of(work, struct samsung_galaxybook, kbd_backlight_hotkey_work); + + if (galaxybook->kbd_backlight.brightness < galaxybook->kbd_backlight.max_= brightness) + kbd_backlight_acpi_set(galaxybook, galaxybook->kbd_backlight.brightness = + 1); + else + kbd_backlight_acpi_set(galaxybook, 0); + + led_classdev_notify_brightness_hw_changed( + &galaxybook->kbd_backlight, + galaxybook->kbd_backlight.brightness); +} + +static void galaxybook_allow_recording_hotkey_work(struct work_struct *wor= k) +{ + struct samsung_galaxybook *galaxybook =3D + container_of(work, struct samsung_galaxybook, allow_recording_hotkey_wor= k); + bool value; + + allow_recording_acpi_get(galaxybook, &value); + allow_recording_acpi_set(galaxybook, !value); +} + +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 (unlikely(data =3D=3D 0xe0)) { + extended =3D true; + return true; + } else if (unlikely(extended)) { + extended =3D false; + switch (data) { + + case GB_KEY_KBD_BACKLIGHT_KEYDOWN: + return true; + case GB_KEY_KBD_BACKLIGHT_KEYUP: + if (kbd_backlight) + schedule_work(&galaxybook_ptr->kbd_backlight_hotkey_work); + return true; + + case GB_KEY_ALLOW_RECORDING_KEYDOWN: + return true; + case GB_KEY_ALLOW_RECORDING_KEYUP: + if (allow_recording) + schedule_work(&galaxybook_ptr->allow_recording_hotkey_work); + 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 =3D data; + + i8042_remove_filter(galaxybook_i8042_filter); + cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work); + cancel_work_sync(&galaxybook->allow_recording_hotkey_work); +} + +static int galaxybook_i8042_filter_install(struct samsung_galaxybook *gala= xybook) +{ + int err; + + /* initialize hotkey work queues */ + if (kbd_backlight) + INIT_WORK(&galaxybook->kbd_backlight_hotkey_work, + galaxybook_kbd_backlight_hotkey_work); + if (allow_recording) + INIT_WORK(&galaxybook->allow_recording_hotkey_work, + galaxybook_allow_recording_hotkey_work); + + err =3D i8042_install_filter(galaxybook_i8042_filter); + if (err) { + cancel_work_sync(&galaxybook->kbd_backlight_hotkey_work); + cancel_work_sync(&galaxybook->allow_recording_hotkey_work); + return err; + } + + /* if adding dev remove action fails, remove now and return failure */ + err =3D devm_add_action_or_reset(&galaxybook->platform->dev, + galaxybook_i8042_filter_remove, galaxybook); + if (err) { + galaxybook_i8042_filter_remove(galaxybook); + return err; + } + + return 0; +} + +/* + * Input device (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; + + galaxybook->input =3D devm_input_allocate_device(&galaxybook->platform->d= ev); + if (!galaxybook->input) + return -ENOMEM; + + galaxybook->input->name =3D "Samsung Galaxy Book Extra Buttons"; + galaxybook->input->phys =3D DRIVER_NAME "/input0"; + galaxybook->input->id.bustype =3D BUS_HOST; + galaxybook->input->dev.parent =3D &galaxybook->platform->dev; + + err =3D sparse_keymap_setup(galaxybook->input, galaxybook_acpi_keymap, NU= LL); + if (err) + return err; + + return input_register_device(galaxybook->input); +} + +/* + * ACPI device setup + */ + +static void galaxybook_acpi_notify(acpi_handle handle, u32 event, void *da= ta) +{ + struct samsung_galaxybook *galaxybook =3D data; + + if (event =3D=3D ACPI_NOTIFY_HOTKEY_PERFORMANCE_MODE) { + if (performance_mode) { + platform_profile_cycle(); + goto exit; + } + } + + galaxybook_input_notify(galaxybook, event); + +exit: + acpi_bus_generate_netlink_event(DRIVER_NAME, dev_name(&galaxybook->platfo= rm->dev), + event, 0); +} + +static int galaxybook_enable_acpi_notify(struct samsung_galaxybook *galaxy= book) +{ + struct sawb buf =3D { 0 }; + int err; + + err =3D galaxybook_enable_acpi_feature(galaxybook, SASB_NOTIFICATIONS); + if (err) + return err; + + buf.safn =3D SAFN; + buf.sasb =3D SASB_NOTIFICATIONS; + buf.gunm =3D GUNM_ACPI_NOTIFY_ENABLE; + buf.guds[0] =3D GUDS_ACPI_NOTIFY_ENABLE; + + return galaxybook_acpi_method(galaxybook, ACPI_METHOD_SETTINGS, &buf, SAW= B_LEN_SETTINGS, + "activate ACPI notifications", &buf); +} + +static void galaxybook_acpi_remove_notify_handler(void *data) +{ + struct samsung_galaxybook *galaxybook =3D 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 =3D 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 =3D acpi_execute_simple_method(galaxybook->acpi->handle, ACPI_METH= OD_ENABLE, + ACPI_METHOD_ENABLE_ON); + if (ACPI_FAILURE(status)) + return -ENXIO; + err =3D devm_add_action_or_reset(&galaxybook->platform->dev, + galaxybook_acpi_disable, galaxybook); + if (err) + return err; + + status =3D acpi_install_notify_handler(galaxybook->acpi->handle, ACPI_ALL= _NOTIFY, + galaxybook_acpi_notify, galaxybook); + if (ACPI_FAILURE(status)) + return -ENXIO; + err =3D devm_add_action_or_reset(&galaxybook->platform->dev, + galaxybook_acpi_remove_notify_handler, galaxybook); + if (err) + return err; + + err =3D galaxybook_enable_acpi_notify(galaxybook); + if (err) { + dev_warn(&galaxybook->platform->dev, "failed to enable ACPI notification= s; " + "some hotkeys will not be supported\n"); + } else { + err =3D galaxybook_input_init(galaxybook); + if (err) + dev_warn(&galaxybook->platform->dev, + "failed to initialize input device\n"); + } + + return 0; +} + +/* + * Platform driver + */ + +#define galaxybook_init_feature(module_param, init_func) \ +do { \ + if (module_param) { \ + err =3D init_func(galaxybook); \ + if (err) { \ + dev_dbg(&galaxybook->platform->dev, \ + "failed to initialize " #module_param "\n"); \ + module_param =3D false; \ + } \ + } \ +} while (0) + +static int galaxybook_probe(struct platform_device *pdev) +{ + struct acpi_device *adev =3D ACPI_COMPANION(&pdev->dev); + struct samsung_galaxybook *galaxybook; + int err; + + if (!adev) + return -ENODEV; + + dev_dbg(&pdev->dev, "loading driver\n"); + + galaxybook =3D devm_kzalloc(&pdev->dev, sizeof(*galaxybook), GFP_KERNEL); + if (!galaxybook) + return -ENOMEM; + /* set static pointer here so it can be used in i8042 filter */ + galaxybook_ptr =3D galaxybook; + + galaxybook->platform =3D pdev; + galaxybook->acpi =3D adev; + + dev_set_drvdata(&galaxybook->platform->dev, galaxybook); + devm_mutex_init(&galaxybook->platform->dev, &galaxybook->acpi_lock); + devm_mutex_init(&galaxybook->platform->dev, &galaxybook->input_lock); + + err =3D galaxybook_acpi_init(galaxybook); + if (err) { + dev_err(&galaxybook->acpi->dev, "failed to initialize the ACPI device\n"= ); + return err; + } + + err =3D 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"); + performance_mode =3D false; + battery_threshold =3D false; + } + + galaxybook_init_feature(performance_mode, galaxybook_profile_init); + galaxybook_init_feature(battery_threshold, galaxybook_battery_threshold_i= nit); + + /* add attrs group here as the is_visible requires above initializations = */ + err =3D devm_device_add_group(&galaxybook->platform->dev, &galaxybook_att= rs_group); + if (err) + dev_warn(&galaxybook->platform->dev, "failed to add platform device attr= ibutes\n"); + + galaxybook_init_feature(kbd_backlight, galaxybook_kbd_backlight_init); + + /* i8042_filter should be disabled if kbd_backlight and allow_recording a= re disabled */ + if (!kbd_backlight && !allow_recording) + i8042_filter =3D false; + + galaxybook_init_feature(i8042_filter, galaxybook_i8042_filter_install); + + dev_dbg(&galaxybook->platform->dev, "driver successfully loaded\n"); + + return 0; +} + +static void galaxybook_remove(struct platform_device *pdev) +{ + struct samsung_galaxybook *galaxybook =3D dev_get_drvdata(&pdev->dev); + + if (galaxybook_ptr) + galaxybook_ptr =3D NULL; + + dev_dbg(&galaxybook->platform->dev, "driver removed\n"); +} + +static struct platform_driver galaxybook_platform_driver =3D { + .driver =3D { + .name =3D DRIVER_NAME, + .acpi_match_table =3D galaxybook_device_ids, + }, + .probe =3D galaxybook_probe, + .remove =3D galaxybook_remove, +}; +module_platform_driver(galaxybook_platform_driver); + +MODULE_AUTHOR("Joshua Grisham "); +MODULE_DESCRIPTION("Samsung Galaxy Book Extras"); +MODULE_LICENSE("GPL"); --=20 2.45.2