platform-drivers-x86 for v5.14-1
Highlights: - New think-lmi driver adding support for changing BIOS settings from within Linux using the standard firmware-attributes class sysfs API - MS Surface aggregator-cdev now also supports forwarding events to user-space (for debugging / new driver development purposes only) - New intel_skl_int3472 driver this provides the necessary glue to translate ACPI table information to GPIOs, regulators, etc. for camera sensors on Intel devices with IPU3 attached MIPI cameras - A whole bunch of other fixes + device-specific quirk additions - New devm_work_autocancel() devm-helpers.h function Note this also contains merges of the following immutable branches/tags shared with other subsystems: - platform-drivers-x86-goodix-v5.14-1 - intel-gpio-v5.14-1 - linux-pm/acpi-scan - devm-helpers-v5.14-1 The following is an automated git shortlog grouped by driver: ACPI: - scan: initialize local variable to avoid garbage being returned - scan: Add function to fetch dependent of ACPI device - scan: Extend acpi_walk_dep_device_list() - scan: Rearrange dep_unmet initialization Add intel_skl_int3472 driver: - Add intel_skl_int3472 driver ISST: - Use numa node id for cpu pci dev mapping - Optimize CPU to PCI device mapping Input: - goodix - platform/x86: touchscreen_dmi - Move upside down quirks to touchscreen_dmi.c MAINTAINERS: - Update IRC link for Surface System Aggregator subsystem - Update info for telemetry Merge remote-tracking branch 'linux-pm/acpi-scan' into review-hans: - Merge remote-tracking branch 'linux-pm/acpi-scan' into review-hans Merge tag 'devm-helpers-v5.14-1' into review-hans: - Merge tag 'devm-helpers-v5.14-1' into review-hans Merge tag 'intel-gpio-v5.14-1' into review-hans: - Merge tag 'intel-gpio-v5.14-1' into review-hans Merge tag 'platform-drivers-x86-goodix-v5.14-1' into review-hans: - Merge tag 'platform-drivers-x86-goodix-v5.14-1' into review-hans Remove "default n" entries: - Remove "default n" entries Rename hp-wireless to wireless-hotkey: - Rename hp-wireless to wireless-hotkey asus-nb-wmi: - Revert "add support for ASUS ROG Zephyrus G14 and G15" - Revert "Drop duplicate DMI quirk structures" dcdbas: - drop unneeded assignment in host_control_smi() dell-privacy: - Add support for Dell hardware privacy dell-wmi: - Rename dell-wmi.c to dell-wmi-base.c dell-wmi-sysman: - Change user experience when Admin/System Password is modified - fw_attr_inuse can be static - Use firmware_attributes_class helper - Make populate_foo_data functions more robust dell-wmi-sysman/think-lmi: - Make fw_attr_class global static devm-helpers: - Add resource managed version of work init docs: - driver-api: Update Surface Aggregator user-space interface documentation extcon: - extcon-max8997: Simplify driver using devm - extcon-max8997: Fix IRQ freeing at error path - extcon-max77693.c: Fix potential work-queue cancellation race - extcon-max14577: Fix potential work-queue cancellation race firmware_attributes_class: - Create helper file for handling firmware-attributes class registration events gpio: - wcove: Split error handling for CTRL and IRQ registers - wcove: Unify style of to_reg() with to_ireg() - wcove: Use IRQ hardware number getter instead of direct access - crystalcove: remove platform_set_drvdata() + cleanup probe gpiolib: - acpi: Add acpi_gpio_get_io_resource() - acpi: Introduce acpi_get_and_request_gpiod() helper hdaps: - Constify static attribute_group struct ideapad-laptop: - Ignore VPC event bit 10 intel_cht_int33fe: - Move to its own subfolder - Correct "displayport" fwnode reference intel_ips: - fix set but unused warning in read_mgtv intel_pmt_crashlog: - Constify static attribute_group struct intel_skl_int3472: - Uninitialized variable in skl_int3472_handle_gpio_resources() - Move to intel/ subfolder - Provide skl_int3472_unregister_clock() - Provide skl_int3472_unregister_regulator() - Use ACPI GPIO resource directly - Fix dependencies (drop CLKDEV_LOOKUP) - Free ACPI device resources after use mfd: - tps68470: Remove tps68470 MFD driver platform/mellanox: - mlxreg-hotplug: Revert "move to use request_irq by IRQF_NO_AUTOEN flag" platform/surface: - aggregator: Use list_move_tail instead of list_del/list_add_tail in ssh_packet_layer.c - aggregator: Use list_move_tail instead of list_del/list_add_tail in ssh_request_layer.c - aggregator: Drop unnecessary variable initialization - aggregator: Do not return uninitialized value - aggregator_cdev: Add lockdep support - aggregator_cdev: Allow enabling of events from user-space - aggregator_cdev: Add support for forwarding events to user-space - aggregator: Update copyright - aggregator: Allow enabling of events without notifiers - aggregator: Allow registering notifiers without enabling events - dtx: Add missing mutex_destroy() call in failure path - aggregator: Fix event disable function - aggregator_registry: Consolidate node groups for 5th- and 6th-gen devices - aggregator_registry: Add support for 13" Intel Surface Laptop 4 - aggregator_registry: Update comments for 15" AMD Surface Laptop 4 samsung-laptop: - set debugfs blobs to read only - use octal numbers for rwx file permissions tc1100-wmi: - Constify static attribute_group struct think-lmi: - Move kfree(setting->possible_values) to tlmi_attr_setting_release() - Split current_value to reflect only the value - Fix issues with duplicate attributes - Return EINVAL when kbdlang gets set to a 0 length string - Add missing MODULE_DEVICE_TABLE - Avoid potential read before start of the buffer - Fix check for admin password being set - Add WMI interface support on Lenovo platforms thinkpad-lmi: - Remove unused display_name member from struct tlmi_pwd_setting thinkpad_acpi: - Add X1 Carbon Gen 9 second fan support - Fix inconsistent indenting tools/power/x86/intel-speed-select: - v1.10 release - Fix uncore memory frequency display toshiba_acpi: - Fix missing error code in toshiba_acpi_setup_keyboard() toshiba_haps: - Fix missing newline in pr_debug call in toshiba_haps_notify touchscreen_dmi: - Fix Chuwi Hi10 Pro comment - Add info for the Goodix GT912 panel of TM800A550L tablets - Add an extra entry for the upside down Goodix touchscreen on Teclast X89 tablets x86/platform/uv: - Constify static attribute_group struct -----BEGIN PGP SIGNATURE----- iQFIBAABCAAyFiEEuvA7XScYQRpenhd+kuxHeUQDJ9wFAmDbELwUHGhkZWdvZWRl QHJlZGhhdC5jb20ACgkQkuxHeUQDJ9yp2wgAj1mTOJi/4Rx1g8wXLpP/hflEkFMU yyMeKe3LOEzuo/LZUfW4tqWiXa4aTgN6rUOF8KUumsIor/72hKcczuPVY+qCqF7V qYZ0vMG93DfAyVPQvBrNjHMXiVevD/gMFRqJEOOgXt96B6Zea4vh1pBvLACAHFZ0 bjkZDX3cO89TSfUF7uhiU9UkMvMMAVs34Knc1Pe4QnZ16e2kPGcKip3qb73yT+xC 8NVRgE6fdSIJfDAVzqpdh91rfDdzHDJ6vT10uijOTkriJciN07UKtYuK5StCpAo5 sXIQllHySHRHj5N0IWZ04w6RMQ+l/9CaHDttkYWW3fV1EU9SVzvp/+d6zA== =tAuE -----END PGP SIGNATURE----- Merge tag 'platform-drivers-x86-v5.14-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86 Pull x86 platform driver updates from Hans de Goede: "Highlights: - New think-lmi driver adding support for changing Lenovo Thinkpad BIOS settings from within Linux using the standard firmware- attributes class sysfs API - MS Surface aggregator-cdev now also supports forwarding events to user-space (for debugging / new driver development purposes only) - New intel_skl_int3472 driver this provides the necessary glue to translate ACPI table information to GPIOs, regulators, etc. for camera sensors on Intel devices with IPU3 attached MIPI cameras - A whole bunch of other fixes + device-specific quirk additions - New devm_work_autocancel() devm-helpers.h function" * tag 'platform-drivers-x86-v5.14-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (83 commits) platform/x86: dell-wmi-sysman: Change user experience when Admin/System Password is modified platform/x86: intel_skl_int3472: Uninitialized variable in skl_int3472_handle_gpio_resources() platform/x86: think-lmi: Move kfree(setting->possible_values) to tlmi_attr_setting_release() platform/x86: think-lmi: Split current_value to reflect only the value platform/x86: think-lmi: Fix issues with duplicate attributes platform/x86: think-lmi: Return EINVAL when kbdlang gets set to a 0 length string platform/x86: intel_cht_int33fe: Move to its own subfolder platform/x86: intel_skl_int3472: Move to intel/ subfolder platform/x86: intel_skl_int3472: Provide skl_int3472_unregister_clock() platform/x86: intel_skl_int3472: Provide skl_int3472_unregister_regulator() platform/x86: intel_skl_int3472: Use ACPI GPIO resource directly platform/x86: intel_skl_int3472: Fix dependencies (drop CLKDEV_LOOKUP) platform/x86: intel_skl_int3472: Free ACPI device resources after use platform/x86: Remove "default n" entries platform/x86: ISST: Use numa node id for cpu pci dev mapping platform/x86: ISST: Optimize CPU to PCI device mapping tools/power/x86/intel-speed-select: v1.10 release tools/power/x86/intel-speed-select: Fix uncore memory frequency display extcon: extcon-max8997: Simplify driver using devm extcon: extcon-max8997: Fix IRQ freeing at error path ...
This commit is contained in:
commit
776ba3ad65
|
@ -197,8 +197,24 @@ Description:
|
|||
Drivers may emit a CHANGE uevent when a password is set or unset
|
||||
userspace may check it again.
|
||||
|
||||
On Dell systems, if Admin password is set, then all BIOS attributes
|
||||
On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes
|
||||
require password validation.
|
||||
On Lenovo systems if you change the Admin password the new password is not active until
|
||||
the next boot.
|
||||
|
||||
Lenovo specific class extensions
|
||||
------------------------------
|
||||
|
||||
On Lenovo systems the following additional settings are available:
|
||||
|
||||
lenovo_encoding:
|
||||
The encoding method that is used. This can be either "ascii"
|
||||
or "scancode". Default is set to "ascii"
|
||||
|
||||
lenovo_kbdlang:
|
||||
The keyboard language method that is used. This is generally a
|
||||
two char code (e.g. "us", "fr", "gr") and may vary per platform.
|
||||
Default is set to "us"
|
||||
|
||||
What: /sys/class/firmware-attributes/*/attributes/pending_reboot
|
||||
Date: February 2021
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type
|
||||
Date: Apr 2021
|
||||
KernelVersion: 5.13
|
||||
Contact: "perry.yuan@dell.com>"
|
||||
Description:
|
||||
Display which dell hardware level privacy devices are supported
|
||||
“Dell Privacy” is a set of HW, FW, and SW features to enhance
|
||||
Dell’s commitment to platform privacy for MIC, Camera, and
|
||||
ePrivacy screens.
|
||||
The supported hardware privacy devices are:
|
||||
Attributes:
|
||||
Microphone Mute:
|
||||
Identifies the local microphone can be muted by hardware, no applications
|
||||
is available to capture system mic sound
|
||||
|
||||
Camera Shutter:
|
||||
Identifies camera shutter controlled by hardware, which is a micromechanical
|
||||
shutter assembly that is built onto the camera module to block capturing images
|
||||
from outside the laptop
|
||||
|
||||
supported:
|
||||
The privacy device is supported by this system
|
||||
|
||||
unsupported:
|
||||
The privacy device is not supported on this system
|
||||
|
||||
For example to check which privacy devices are supported:
|
||||
|
||||
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type
|
||||
[Microphone Mute] [supported]
|
||||
[Camera Shutter] [supported]
|
||||
[ePrivacy Screen] [unsupported]
|
||||
|
||||
What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state
|
||||
Date: Apr 2021
|
||||
KernelVersion: 5.13
|
||||
Contact: "perry.yuan@dell.com>"
|
||||
Description:
|
||||
Allow user space to check current dell privacy device state.
|
||||
Describes the Device State class exposed by BIOS which can be
|
||||
consumed by various applications interested in knowing the Privacy
|
||||
feature capabilities
|
||||
Attributes:
|
||||
muted:
|
||||
Identifies the privacy device is turned off and cannot send stream to OS applications
|
||||
|
||||
unmuted:
|
||||
Identifies the privacy device is turned on ,audio or camera driver can get
|
||||
stream from mic and camera module to OS applications
|
||||
|
||||
For example to check all supported current privacy device states:
|
||||
|
||||
# cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state
|
||||
[Microphone] [unmuted]
|
||||
[Camera Shutter] [unmuted]
|
|
@ -1,9 +1,8 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
.. |u8| replace:: :c:type:`u8 <u8>`
|
||||
.. |u16| replace:: :c:type:`u16 <u16>`
|
||||
.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>`
|
||||
.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>`
|
||||
.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event <ssam_cdev_event>`
|
||||
|
||||
==============================
|
||||
User-Space EC Interface (cdev)
|
||||
|
@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in
|
|||
A small python library and scripts for accessing this interface can be found
|
||||
at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam.
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
Receiving Events
|
||||
================
|
||||
|
||||
Events can be received by reading from the device-file. The are represented by
|
||||
the |ssam_cdev_event| datatype.
|
||||
|
||||
Before events are available to be read, however, the desired notifiers must be
|
||||
registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in
|
||||
essence, callbacks, called when the EC sends an event. They are, in this
|
||||
interface, associated with a specific target category and device-file-instance.
|
||||
They forward any event of this category to the buffer of the corresponding
|
||||
instance, from which it can then be read.
|
||||
|
||||
Notifiers themselves do not enable events on the EC. Thus, it may additionally
|
||||
be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While
|
||||
notifiers work per-client (i.e. per-device-file-instance), events are enabled
|
||||
globally, for the EC and all of its clients (regardless of userspace or
|
||||
non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE``
|
||||
IOCTLs take care of reference counting the events, such that an event is
|
||||
enabled as long as there is a client that has requested it.
|
||||
|
||||
Note that enabled events are not automatically disabled once the client
|
||||
instance is closed. Therefore any client process (or group of processes) should
|
||||
balance their event enable calls with the corresponding event disable calls. It
|
||||
is, however, perfectly valid to enable and disable events on different client
|
||||
instances. For example, it is valid to set up notifiers and read events on
|
||||
client instance ``A``, enable those events on instance ``B`` (note that these
|
||||
will also be received by A since events are enabled/disabled globally), and
|
||||
after no more events are desired, disable the previously enabled events via
|
||||
instance ``C``.
|
||||
|
||||
|
||||
Controller IOCTLs
|
||||
=================
|
||||
|
@ -45,9 +78,33 @@ The following IOCTLs are provided:
|
|||
- ``REQUEST``
|
||||
- Perform synchronous SAM request.
|
||||
|
||||
* - ``0xA5``
|
||||
- ``2``
|
||||
- ``W``
|
||||
- ``NOTIF_REGISTER``
|
||||
- Register event notifier.
|
||||
|
||||
``REQUEST``
|
||||
-----------
|
||||
* - ``0xA5``
|
||||
- ``3``
|
||||
- ``W``
|
||||
- ``NOTIF_UNREGISTER``
|
||||
- Unregister event notifier.
|
||||
|
||||
* - ``0xA5``
|
||||
- ``4``
|
||||
- ``W``
|
||||
- ``EVENT_ENABLE``
|
||||
- Enable event source.
|
||||
|
||||
* - ``0xA5``
|
||||
- ``5``
|
||||
- ``W``
|
||||
- ``EVENT_DISABLE``
|
||||
- Disable event source.
|
||||
|
||||
|
||||
``SSAM_CDEV_REQUEST``
|
||||
---------------------
|
||||
|
||||
Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``.
|
||||
|
||||
|
@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from
|
|||
inside the IOCTL, but the request ``status`` member may still be negative in
|
||||
case the actual execution of the request failed after it has been submitted.
|
||||
|
||||
A full definition of the argument struct is provided below:
|
||||
A full definition of the argument struct is provided below.
|
||||
|
||||
``SSAM_CDEV_NOTIF_REGISTER``
|
||||
----------------------------
|
||||
|
||||
Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``.
|
||||
|
||||
Register a notifier for the event target category specified in the given
|
||||
notifier description with the specified priority. Notifiers registration is
|
||||
required to receive events, but does not enable events themselves. After a
|
||||
notifier for a specific target category has been registered, all events of that
|
||||
category will be forwarded to the userspace client and can then be read from
|
||||
the device file instance. Note that events may have to be enabled, e.g. via the
|
||||
``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them.
|
||||
|
||||
Only one notifier can be registered per target category and client instance. If
|
||||
a notifier has already been registered, this IOCTL will fail with ``-EEXIST``.
|
||||
|
||||
Notifiers will automatically be removed when the device file instance is
|
||||
closed.
|
||||
|
||||
``SSAM_CDEV_NOTIF_UNREGISTER``
|
||||
------------------------------
|
||||
|
||||
Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``.
|
||||
|
||||
Unregisters the notifier associated with the specified target category. The
|
||||
priority field will be ignored by this IOCTL. If no notifier has been
|
||||
registered for this client instance and the given category, this IOCTL will
|
||||
fail with ``-ENOENT``.
|
||||
|
||||
``SSAM_CDEV_EVENT_ENABLE``
|
||||
--------------------------
|
||||
|
||||
Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``.
|
||||
|
||||
Enable the event associated with the given event descriptor.
|
||||
|
||||
Note that this call will not register a notifier itself, it will only enable
|
||||
events on the controller. If you want to receive events by reading from the
|
||||
device file, you will need to register the corresponding notifier(s) on that
|
||||
instance.
|
||||
|
||||
Events are not automatically disabled when the device file is closed. This must
|
||||
be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL.
|
||||
|
||||
``SSAM_CDEV_EVENT_DISABLE``
|
||||
---------------------------
|
||||
|
||||
Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``.
|
||||
|
||||
Disable the event associated with the given event descriptor.
|
||||
|
||||
Note that this will not unregister any notifiers. Events may still be received
|
||||
and forwarded to user-space after this call. The only safe way of stopping
|
||||
events from being received is unregistering all previously registered
|
||||
notifiers.
|
||||
|
||||
|
||||
Structures and Enums
|
||||
====================
|
||||
|
||||
.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h
|
||||
|
|
|
@ -325,7 +325,7 @@ Code Seq# Include File Comments
|
|||
0xA3 90-9F linux/dtlk.h
|
||||
0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
|
||||
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
|
||||
0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
||||
0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
||||
<mailto:luzmaximilian@gmail.com>
|
||||
0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
|
||||
<mailto:luzmaximilian@gmail.com>
|
||||
|
|
25
MAINTAINERS
25
MAINTAINERS
|
@ -5187,7 +5187,14 @@ DELL WMI NOTIFICATIONS DRIVER
|
|||
M: Matthew Garrett <mjg59@srcf.ucam.org>
|
||||
M: Pali Rohár <pali@kernel.org>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell/dell-wmi.c
|
||||
F: drivers/platform/x86/dell/dell-wmi-base.c
|
||||
|
||||
DELL WMI HARDWARE PRIVACY SUPPORT
|
||||
M: Perry Yuan <Perry.Yuan@dell.com>
|
||||
L: Dell.Client.Kernel@dell.com
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/dell/dell-wmi-privacy.c
|
||||
|
||||
DELTA ST MEDIA DRIVER
|
||||
M: Hugues Fruchet <hugues.fruchet@foss.st.com>
|
||||
|
@ -9397,6 +9404,11 @@ S: Maintained
|
|||
F: arch/x86/include/asm/intel_scu_ipc.h
|
||||
F: drivers/platform/x86/intel_scu_*
|
||||
|
||||
INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER
|
||||
M: Daniel Scally <djrscally@gmail.com>
|
||||
S: Maintained
|
||||
F: drivers/platform/x86/intel/int3472/
|
||||
|
||||
INTEL SPEED SELECT TECHNOLOGY
|
||||
M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
|
@ -9417,7 +9429,7 @@ F: include/linux/firmware/intel/stratix10-smc.h
|
|||
F: include/linux/firmware/intel/stratix10-svc-client.h
|
||||
|
||||
INTEL TELEMETRY DRIVER
|
||||
M: Rajneesh Bhardwaj <rajneesh.bhardwaj@linux.intel.com>
|
||||
M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com>
|
||||
M: "David E. Box" <david.e.box@linux.intel.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
|
@ -12209,7 +12221,7 @@ M: Maximilian Luz <luzmaximilian@gmail.com>
|
|||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
W: https://github.com/linux-surface/surface-aggregator-module
|
||||
C: irc://chat.freenode.net/##linux-surface
|
||||
C: irc://irc.libera.chat/linux-surface
|
||||
F: Documentation/driver-api/surface_aggregator/
|
||||
F: drivers/platform/surface/aggregator/
|
||||
F: drivers/platform/surface/surface_acpi_notify.c
|
||||
|
@ -18215,6 +18227,13 @@ W: http://thinkwiki.org/wiki/Ibm-acpi
|
|||
T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git
|
||||
F: drivers/platform/x86/thinkpad_acpi.c
|
||||
|
||||
THINKPAD LMI DRIVER
|
||||
M: Mark Pearson <markpearson@lenovo.com>
|
||||
L: platform-driver-x86@vger.kernel.org
|
||||
S: Maintained
|
||||
F: Documentation/ABI/testing/sysfs-class-firmware-attributes
|
||||
F: drivers/platform/x86/think-lmi.?
|
||||
|
||||
THUNDERBOLT DMA TRAFFIC TEST DRIVER
|
||||
M: Isaac Hazan <isaac.hazan@intel.com>
|
||||
L: linux-usb@vger.kernel.org
|
||||
|
|
|
@ -52,7 +52,7 @@ endif # PMIC_OPREGION
|
|||
|
||||
config TPS68470_PMIC_OPREGION
|
||||
bool "ACPI operation region support for TPS68470 PMIC"
|
||||
depends on MFD_TPS68470
|
||||
depends on INTEL_SKL_INT3472
|
||||
help
|
||||
This config adds ACPI operation region support for TI TPS68470 PMIC.
|
||||
TPS68470 device is an advanced power management unit that powers
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Chanwoo Choi <cw00.choi@samsung.com>
|
||||
// Krzysztof Kozlowski <krzk@kernel.org>
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
|
@ -673,7 +674,10 @@ static int max14577_muic_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, info);
|
||||
mutex_init(&info->mutex);
|
||||
|
||||
INIT_WORK(&info->irq_work, max14577_muic_irq_work);
|
||||
ret = devm_work_autocancel(&pdev->dev, &info->irq_work,
|
||||
max14577_muic_irq_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
switch (max14577->dev_type) {
|
||||
case MAXIM_DEVICE_TYPE_MAX77836:
|
||||
|
@ -766,15 +770,6 @@ static int max14577_muic_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int max14577_muic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max14577_muic_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_work_sync(&info->irq_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id max14577_muic_id[] = {
|
||||
{ "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, },
|
||||
{ "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, },
|
||||
|
@ -797,7 +792,6 @@ static struct platform_driver max14577_muic_driver = {
|
|||
.of_match_table = of_max14577_muic_dt_match,
|
||||
},
|
||||
.probe = max14577_muic_probe,
|
||||
.remove = max14577_muic_remove,
|
||||
.id_table = max14577_muic_id,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright (C) 2012 Samsung Electrnoics
|
||||
// Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
|
@ -1127,7 +1128,10 @@ static int max77693_muic_probe(struct platform_device *pdev)
|
|||
platform_set_drvdata(pdev, info);
|
||||
mutex_init(&info->mutex);
|
||||
|
||||
INIT_WORK(&info->irq_work, max77693_muic_irq_work);
|
||||
ret = devm_work_autocancel(&pdev->dev, &info->irq_work,
|
||||
max77693_muic_irq_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Support irq domain for MAX77693 MUIC device */
|
||||
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) {
|
||||
|
@ -1254,22 +1258,11 @@ static int max77693_muic_probe(struct platform_device *pdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int max77693_muic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max77693_muic_info *info = platform_get_drvdata(pdev);
|
||||
|
||||
cancel_work_sync(&info->irq_work);
|
||||
input_unregister_device(info->dock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max77693_muic_driver = {
|
||||
.driver = {
|
||||
.name = DEV_NAME,
|
||||
},
|
||||
.probe = max77693_muic_probe,
|
||||
.remove = max77693_muic_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(max77693_muic_driver);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Copyright (C) 2012 Samsung Electronics
|
||||
// Donggeun Kim <dg77.kim@samsung.com>
|
||||
|
||||
#include <linux/devm-helpers.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
|
@ -650,27 +651,30 @@ static int max8997_muic_probe(struct platform_device *pdev)
|
|||
mutex_init(&info->mutex);
|
||||
|
||||
INIT_WORK(&info->irq_work, max8997_muic_irq_work);
|
||||
ret = devm_work_autocancel(&pdev->dev, &info->irq_work,
|
||||
max8997_muic_irq_work);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) {
|
||||
struct max8997_muic_irq *muic_irq = &muic_irqs[i];
|
||||
unsigned int virq = 0;
|
||||
|
||||
virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq);
|
||||
if (!virq) {
|
||||
ret = -EINVAL;
|
||||
goto err_irq;
|
||||
}
|
||||
if (!virq)
|
||||
return -EINVAL;
|
||||
|
||||
muic_irq->virq = virq;
|
||||
|
||||
ret = request_threaded_irq(virq, NULL,
|
||||
max8997_muic_irq_handler,
|
||||
IRQF_NO_SUSPEND,
|
||||
muic_irq->name, info);
|
||||
ret = devm_request_threaded_irq(&pdev->dev, virq, NULL,
|
||||
max8997_muic_irq_handler,
|
||||
IRQF_NO_SUSPEND,
|
||||
muic_irq->name, info);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev,
|
||||
"failed: irq request (IRQ: %d, error :%d)\n",
|
||||
muic_irq->irq, ret);
|
||||
goto err_irq;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -678,14 +682,13 @@ static int max8997_muic_probe(struct platform_device *pdev)
|
|||
info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable);
|
||||
if (IS_ERR(info->edev)) {
|
||||
dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
|
||||
ret = PTR_ERR(info->edev);
|
||||
goto err_irq;
|
||||
return PTR_ERR(info->edev);
|
||||
}
|
||||
|
||||
ret = devm_extcon_dev_register(&pdev->dev, info->edev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register extcon device\n");
|
||||
goto err_irq;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (pdata && pdata->muic_pdata) {
|
||||
|
@ -756,23 +759,6 @@ static int max8997_muic_probe(struct platform_device *pdev)
|
|||
delay_jiffies);
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
while (--i >= 0)
|
||||
free_irq(muic_irqs[i].virq, info);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max8997_muic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max8997_muic_info *info = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(muic_irqs); i++)
|
||||
free_irq(muic_irqs[i].virq, info);
|
||||
cancel_work_sync(&info->irq_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver max8997_muic_driver = {
|
||||
|
@ -780,7 +766,6 @@ static struct platform_driver max8997_muic_driver = {
|
|||
.name = DEV_NAME,
|
||||
},
|
||||
.probe = max8997_muic_probe,
|
||||
.remove = max8997_muic_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(max8997_muic_driver);
|
||||
|
|
|
@ -1367,7 +1367,7 @@ config GPIO_TPS65912
|
|||
|
||||
config GPIO_TPS68470
|
||||
bool "TPS68470 GPIO"
|
||||
depends on MFD_TPS68470
|
||||
depends on INTEL_SKL_INT3472
|
||||
help
|
||||
Select this option to enable GPIO driver for the TPS68470
|
||||
chip family.
|
||||
|
|
|
@ -339,8 +339,6 @@ static int crystalcove_gpio_probe(struct platform_device *pdev)
|
|||
if (!cg)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, cg);
|
||||
|
||||
mutex_init(&cg->buslock);
|
||||
cg->chip.label = KBUILD_MODNAME;
|
||||
cg->chip.direction_input = crystalcove_gpio_dir_in;
|
||||
|
@ -372,13 +370,7 @@ static int crystalcove_gpio_probe(struct platform_device *pdev)
|
|||
return retval;
|
||||
}
|
||||
|
||||
retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg);
|
||||
if (retval) {
|
||||
dev_warn(&pdev->dev, "add gpio chip error: %d\n", retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg);
|
||||
}
|
||||
|
||||
static struct platform_driver crystalcove_gpio_driver = {
|
||||
|
|
|
@ -99,19 +99,14 @@ struct wcove_gpio {
|
|||
bool set_irq_mask;
|
||||
};
|
||||
|
||||
static inline int to_reg(int gpio, enum ctrl_register reg_type)
|
||||
static inline int to_reg(int gpio, enum ctrl_register type)
|
||||
{
|
||||
unsigned int reg;
|
||||
unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE;
|
||||
|
||||
if (gpio >= WCOVE_GPIO_NUM)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (reg_type == CTRL_IN)
|
||||
reg = GPIO_IN_CTRL_BASE + gpio;
|
||||
else
|
||||
reg = GPIO_OUT_CTRL_BASE + gpio;
|
||||
|
||||
return reg;
|
||||
return reg + gpio;
|
||||
}
|
||||
|
||||
static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask)
|
||||
|
@ -129,7 +124,7 @@ static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask)
|
|||
return reg;
|
||||
}
|
||||
|
||||
static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio)
|
||||
static void wcove_update_irq_mask(struct wcove_gpio *wg, irq_hw_number_t gpio)
|
||||
{
|
||||
unsigned int mask, reg = to_ireg(gpio, IRQ_MASK, &mask);
|
||||
|
||||
|
@ -139,13 +134,10 @@ static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio)
|
|||
regmap_clear_bits(wg->regmap, reg, mask);
|
||||
}
|
||||
|
||||
static void wcove_update_irq_ctrl(struct wcove_gpio *wg, int gpio)
|
||||
static void wcove_update_irq_ctrl(struct wcove_gpio *wg, irq_hw_number_t gpio)
|
||||
{
|
||||
int reg = to_reg(gpio, CTRL_IN);
|
||||
|
||||
if (reg < 0)
|
||||
return;
|
||||
|
||||
regmap_update_bits(wg->regmap, reg, CTLI_INTCNT_BE, wg->intcnt);
|
||||
}
|
||||
|
||||
|
@ -248,8 +240,9 @@ static int wcove_irq_type(struct irq_data *data, unsigned int type)
|
|||
{
|
||||
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
|
||||
struct wcove_gpio *wg = gpiochip_get_data(chip);
|
||||
irq_hw_number_t gpio = irqd_to_hwirq(data);
|
||||
|
||||
if (data->hwirq >= WCOVE_GPIO_NUM)
|
||||
if (gpio >= WCOVE_GPIO_NUM)
|
||||
return 0;
|
||||
|
||||
switch (type) {
|
||||
|
@ -286,7 +279,7 @@ static void wcove_bus_sync_unlock(struct irq_data *data)
|
|||
{
|
||||
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
|
||||
struct wcove_gpio *wg = gpiochip_get_data(chip);
|
||||
int gpio = data->hwirq;
|
||||
irq_hw_number_t gpio = irqd_to_hwirq(data);
|
||||
|
||||
if (wg->update & UPDATE_IRQ_TYPE)
|
||||
wcove_update_irq_ctrl(wg, gpio);
|
||||
|
@ -301,8 +294,9 @@ static void wcove_irq_unmask(struct irq_data *data)
|
|||
{
|
||||
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
|
||||
struct wcove_gpio *wg = gpiochip_get_data(chip);
|
||||
irq_hw_number_t gpio = irqd_to_hwirq(data);
|
||||
|
||||
if (data->hwirq >= WCOVE_GPIO_NUM)
|
||||
if (gpio >= WCOVE_GPIO_NUM)
|
||||
return;
|
||||
|
||||
wg->set_irq_mask = false;
|
||||
|
@ -313,8 +307,9 @@ static void wcove_irq_mask(struct irq_data *data)
|
|||
{
|
||||
struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
|
||||
struct wcove_gpio *wg = gpiochip_get_data(chip);
|
||||
irq_hw_number_t gpio = irqd_to_hwirq(data);
|
||||
|
||||
if (data->hwirq >= WCOVE_GPIO_NUM)
|
||||
if (gpio >= WCOVE_GPIO_NUM)
|
||||
return;
|
||||
|
||||
wg->set_irq_mask = true;
|
||||
|
@ -369,8 +364,7 @@ static irqreturn_t wcove_gpio_irq_handler(int irq, void *data)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void wcove_gpio_dbg_show(struct seq_file *s,
|
||||
struct gpio_chip *chip)
|
||||
static void wcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip)
|
||||
{
|
||||
unsigned int ctlo, ctli, irq_mask, irq_status;
|
||||
struct wcove_gpio *wg = gpiochip_get_data(chip);
|
||||
|
@ -379,10 +373,15 @@ static void wcove_gpio_dbg_show(struct seq_file *s,
|
|||
for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) {
|
||||
ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo);
|
||||
ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_IN), &ctli);
|
||||
if (ret) {
|
||||
dev_err(wg->dev, "Failed to read registers: CTRL out/in\n");
|
||||
break;
|
||||
}
|
||||
|
||||
ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask);
|
||||
ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status);
|
||||
if (ret) {
|
||||
pr_err("Failed to read registers: ctrl out/in or irq status/mask\n");
|
||||
dev_err(wg->dev, "Failed to read registers: IRQ status/mask\n");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin)
|
|||
return gpiochip_get_desc(chip, pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and
|
||||
* hold a refcount to the GPIO device.
|
||||
* @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1")
|
||||
* @pin: ACPI GPIO pin number (0-based, controller-relative)
|
||||
* @label: Label to pass to gpiod_request()
|
||||
*
|
||||
* This function is a simple pass-through to acpi_get_gpiod(), except that
|
||||
* as it is intended for use outside of the GPIO layer (in a similar fashion to
|
||||
* gpiod_get_index() for example) it also holds a reference to the GPIO device.
|
||||
*/
|
||||
struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label)
|
||||
{
|
||||
struct gpio_desc *gpio;
|
||||
int ret;
|
||||
|
||||
gpio = acpi_get_gpiod(path, pin);
|
||||
if (IS_ERR(gpio))
|
||||
return gpio;
|
||||
|
||||
ret = gpiod_request(gpio, label);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return gpio;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod);
|
||||
|
||||
static irqreturn_t acpi_gpio_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct acpi_gpio_event *event = data;
|
||||
|
@ -168,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource);
|
||||
|
||||
/**
|
||||
* acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO
|
||||
* I/O resource or return False if not.
|
||||
* @ares: Pointer to the ACPI resource to fetch
|
||||
* @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer
|
||||
*/
|
||||
bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
|
||||
struct acpi_resource_gpio **agpio)
|
||||
{
|
||||
struct acpi_resource_gpio *gpio;
|
||||
|
||||
if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
|
||||
return false;
|
||||
|
||||
gpio = &ares->data.gpio;
|
||||
if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO)
|
||||
return false;
|
||||
|
||||
*agpio = gpio;
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource);
|
||||
|
||||
static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio,
|
||||
struct acpi_gpio_event *event)
|
||||
{
|
||||
|
|
|
@ -178,51 +178,6 @@ static const unsigned long goodix_irq_flags[] = {
|
|||
IRQ_TYPE_LEVEL_HIGH,
|
||||
};
|
||||
|
||||
/*
|
||||
* Those tablets have their coordinates origin at the bottom right
|
||||
* of the tablet, as if rotated 180 degrees
|
||||
*/
|
||||
static const struct dmi_system_id rotated_screen[] = {
|
||||
#if defined(CONFIG_DMI) && defined(CONFIG_X86)
|
||||
{
|
||||
.ident = "Teclast X89",
|
||||
.matches = {
|
||||
/* tPAD is too generic, also match on bios date */
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "tPAD"),
|
||||
DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "Teclast X98 Pro",
|
||||
.matches = {
|
||||
/*
|
||||
* Only match BIOS date, because the manufacturers
|
||||
* BIOS does not report the board name at all
|
||||
* (sometimes)...
|
||||
*/
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"),
|
||||
DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"),
|
||||
},
|
||||
},
|
||||
{
|
||||
.ident = "WinBook TW100",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TW100")
|
||||
}
|
||||
},
|
||||
{
|
||||
.ident = "WinBook TW700",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TW700")
|
||||
},
|
||||
},
|
||||
#endif
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct dmi_system_id nine_bytes_report[] = {
|
||||
#if defined(CONFIG_DMI) && defined(CONFIG_X86)
|
||||
{
|
||||
|
@ -1123,13 +1078,6 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
|
|||
ABS_MT_POSITION_Y, ts->prop.max_y);
|
||||
}
|
||||
|
||||
if (dmi_check_system(rotated_screen)) {
|
||||
ts->prop.invert_x = true;
|
||||
ts->prop.invert_y = true;
|
||||
dev_dbg(&ts->client->dev,
|
||||
"Applying '180 degrees rotated screen' quirk\n");
|
||||
}
|
||||
|
||||
if (dmi_check_system(nine_bytes_report)) {
|
||||
ts->contact_size = 9;
|
||||
|
||||
|
|
|
@ -1499,24 +1499,6 @@ config MFD_TPS65217
|
|||
This driver can also be built as a module. If so, the module
|
||||
will be called tps65217.
|
||||
|
||||
config MFD_TPS68470
|
||||
bool "TI TPS68470 Power Management / LED chips"
|
||||
depends on ACPI && PCI && I2C=y
|
||||
depends on I2C_DESIGNWARE_PLATFORM=y
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
help
|
||||
If you say yes here you get support for the TPS68470 series of
|
||||
Power Management / LED chips.
|
||||
|
||||
These include voltage regulators, LEDs and other features
|
||||
that are often used in portable devices.
|
||||
|
||||
This option is a bool as it provides an ACPI operation
|
||||
region, which must be available before any of the devices
|
||||
using this are probed. This option also configures the
|
||||
designware-i2c driver to be built-in, for the same reason.
|
||||
|
||||
config MFD_TI_LP873X
|
||||
tristate "TI LP873X Power Management IC"
|
||||
depends on I2C
|
||||
|
|
|
@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o
|
|||
obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o
|
||||
obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o
|
||||
obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o
|
||||
obj-$(CONFIG_MFD_TPS68470) += tps68470.o
|
||||
obj-$(CONFIG_MFD_TPS80031) += tps80031.o
|
||||
obj-$(CONFIG_MENELAUS) += menelaus.o
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* TPS68470 chip Parent driver
|
||||
*
|
||||
* Copyright (C) 2017 Intel Corporation
|
||||
*
|
||||
* Authors:
|
||||
* Rajmohan Mani <rajmohan.mani@intel.com>
|
||||
* Tianshu Qiu <tian.shu.qiu@intel.com>
|
||||
* Jian Xu Zheng <jian.xu.zheng@intel.com>
|
||||
* Yuning Pu <yuning.pu@intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tps68470.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
static const struct mfd_cell tps68470s[] = {
|
||||
{ .name = "tps68470-gpio" },
|
||||
{ .name = "tps68470_pmic_opregion" },
|
||||
};
|
||||
|
||||
static const struct regmap_config tps68470_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = TPS68470_REG_MAX,
|
||||
};
|
||||
|
||||
static int tps68470_chip_init(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
unsigned int version;
|
||||
int ret;
|
||||
|
||||
/* Force software reset */
|
||||
ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(regmap, TPS68470_REG_REVID, &version);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read revision register: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "TPS68470 REVID: 0x%x\n", version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tps68470_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
struct regmap *regmap;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(dev, "devm_regmap_init_i2c Error %ld\n",
|
||||
PTR_ERR(regmap));
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, regmap);
|
||||
|
||||
ret = tps68470_chip_init(dev, regmap);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "TPS68470 Init Error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s,
|
||||
ARRAY_SIZE(tps68470s), NULL, 0, NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id tps68470_acpi_ids[] = {
|
||||
{"INT3472"},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct i2c_driver tps68470_driver = {
|
||||
.driver = {
|
||||
.name = "tps68470",
|
||||
.acpi_match_table = tps68470_acpi_ids,
|
||||
},
|
||||
.probe_new = tps68470_probe,
|
||||
};
|
||||
builtin_i2c_driver(tps68470_driver);
|
|
@ -1,5 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
|
||||
menuconfig SURFACE_AGGREGATOR
|
||||
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0+
|
||||
# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
|
||||
# For include/trace/define_trace.h to include trace.h
|
||||
CFLAGS_core.o = -I$(src)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Surface System Aggregator Module bus and device integration.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Surface System Aggregator Module bus and device integration.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_BUS_H
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Main SSAM/SSH controller structure and functionality.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the
|
||||
* given event and free its entry if the reference count reaches zero.
|
||||
* @nf: The notifier system reference.
|
||||
* @reg: The registry used to enable/disable the event.
|
||||
* @id: The event ID.
|
||||
*
|
||||
* Decrements the reference-/activation-count of the specified event, freeing
|
||||
* its entry if it reaches zero.
|
||||
*
|
||||
* Note: ``nf->lock`` must be held when calling this function.
|
||||
*/
|
||||
static void ssam_nf_refcount_dec_free(struct ssam_nf *nf,
|
||||
struct ssam_event_registry reg,
|
||||
struct ssam_event_id id)
|
||||
{
|
||||
struct ssam_nf_refcount_entry *entry;
|
||||
|
||||
lockdep_assert_held(&nf->lock);
|
||||
|
||||
entry = ssam_nf_refcount_dec(nf, reg, id);
|
||||
if (entry && entry->refcount == 0)
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* ssam_nf_refcount_empty() - Test if the notification system has any
|
||||
* enabled/active events.
|
||||
|
@ -2122,14 +2147,123 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
|
|||
|
||||
/* -- Top-level event registry interface. ----------------------------------- */
|
||||
|
||||
/**
|
||||
* ssam_nf_refcount_enable() - Enable event for reference count entry if it has
|
||||
* not already been enabled.
|
||||
* @ctrl: The controller to enable the event on.
|
||||
* @entry: The reference count entry for the event to be enabled.
|
||||
* @flags: The flags used for enabling the event on the EC.
|
||||
*
|
||||
* Enable the event associated with the given reference count entry if the
|
||||
* reference count equals one, i.e. the event has not previously been enabled.
|
||||
* If the event has already been enabled (i.e. reference count not equal to
|
||||
* one), check that the flags used for enabling match and warn about this if
|
||||
* they do not.
|
||||
*
|
||||
* This does not modify the reference count itself, which is done with
|
||||
* ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
|
||||
*
|
||||
* Note: ``nf->lock`` must be held when calling this function.
|
||||
*
|
||||
* Return: Returns zero on success. If the event is enabled by this call,
|
||||
* returns the status of the event-enable EC command.
|
||||
*/
|
||||
static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
|
||||
struct ssam_nf_refcount_entry *entry, u8 flags)
|
||||
{
|
||||
const struct ssam_event_registry reg = entry->key.reg;
|
||||
const struct ssam_event_id id = entry->key.id;
|
||||
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
||||
int status;
|
||||
|
||||
lockdep_assert_held(&nf->lock);
|
||||
|
||||
ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
||||
reg.target_category, id.target_category, id.instance, entry->refcount);
|
||||
|
||||
if (entry->refcount == 1) {
|
||||
status = ssam_ssh_event_enable(ctrl, reg, id, flags);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
entry->flags = flags;
|
||||
|
||||
} else if (entry->flags != flags) {
|
||||
ssam_warn(ctrl,
|
||||
"inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
||||
flags, entry->flags, reg.target_category, id.target_category,
|
||||
id.instance);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is
|
||||
* no longer in use and free the corresponding entry.
|
||||
* @ctrl: The controller to disable the event on.
|
||||
* @entry: The reference count entry for the event to be disabled.
|
||||
* @flags: The flags used for enabling the event on the EC.
|
||||
*
|
||||
* If the reference count equals zero, i.e. the event is no longer requested by
|
||||
* any client, the event will be disabled and the corresponding reference count
|
||||
* entry freed. The reference count entry must not be used any more after a
|
||||
* call to this function.
|
||||
*
|
||||
* Also checks if the flags used for disabling the event match the flags used
|
||||
* for enabling the event and warns if they do not (regardless of reference
|
||||
* count).
|
||||
*
|
||||
* This does not modify the reference count itself, which is done with
|
||||
* ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
|
||||
*
|
||||
* Note: ``nf->lock`` must be held when calling this function.
|
||||
*
|
||||
* Return: Returns zero on success. If the event is disabled by this call,
|
||||
* returns the status of the event-enable EC command.
|
||||
*/
|
||||
static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
|
||||
struct ssam_nf_refcount_entry *entry, u8 flags)
|
||||
{
|
||||
const struct ssam_event_registry reg = entry->key.reg;
|
||||
const struct ssam_event_id id = entry->key.id;
|
||||
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
||||
int status = 0;
|
||||
|
||||
lockdep_assert_held(&nf->lock);
|
||||
|
||||
ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
||||
reg.target_category, id.target_category, id.instance, entry->refcount);
|
||||
|
||||
if (entry->flags != flags) {
|
||||
ssam_warn(ctrl,
|
||||
"inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
||||
flags, entry->flags, reg.target_category, id.target_category,
|
||||
id.instance);
|
||||
}
|
||||
|
||||
if (entry->refcount == 0) {
|
||||
status = ssam_ssh_event_disable(ctrl, reg, id, flags);
|
||||
kfree(entry);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* ssam_notifier_register() - Register an event notifier.
|
||||
* @ctrl: The controller to register the notifier on.
|
||||
* @n: The event notifier to register.
|
||||
*
|
||||
* Register an event notifier and increment the usage counter of the
|
||||
* associated SAM event. If the event was previously not enabled, it will be
|
||||
* enabled during this call.
|
||||
* Register an event notifier. Increment the usage counter of the associated
|
||||
* SAM event if the notifier is not marked as an observer. If the event is not
|
||||
* marked as an observer and is currently not enabled, it will be enabled
|
||||
* during this call. If the notifier is marked as an observer, no attempt will
|
||||
* be made at enabling any event and no reference count will be modified.
|
||||
*
|
||||
* Notifiers marked as observers do not need to be associated with one specific
|
||||
* event, i.e. as long as no event matching is performed, only the event target
|
||||
* category needs to be set.
|
||||
*
|
||||
* Return: Returns zero on success, %-ENOSPC if there have already been
|
||||
* %INT_MAX notifiers for the event ID/type associated with the notifier block
|
||||
|
@ -2138,11 +2272,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
|
|||
* for the specific associated event, returns the status of the event-enable
|
||||
* EC-command.
|
||||
*/
|
||||
int ssam_notifier_register(struct ssam_controller *ctrl,
|
||||
struct ssam_event_notifier *n)
|
||||
int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
|
||||
{
|
||||
u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
|
||||
struct ssam_nf_refcount_entry *entry;
|
||||
struct ssam_nf_refcount_entry *entry = NULL;
|
||||
struct ssam_nf_head *nf_head;
|
||||
struct ssam_nf *nf;
|
||||
int status;
|
||||
|
@ -2155,44 +2288,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
|
|||
|
||||
mutex_lock(&nf->lock);
|
||||
|
||||
entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
|
||||
if (IS_ERR(entry)) {
|
||||
mutex_unlock(&nf->lock);
|
||||
return PTR_ERR(entry);
|
||||
if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
|
||||
entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
|
||||
if (IS_ERR(entry)) {
|
||||
mutex_unlock(&nf->lock);
|
||||
return PTR_ERR(entry);
|
||||
}
|
||||
}
|
||||
|
||||
ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
||||
n->event.reg.target_category, n->event.id.target_category,
|
||||
n->event.id.instance, entry->refcount);
|
||||
|
||||
status = ssam_nfblk_insert(nf_head, &n->base);
|
||||
if (status) {
|
||||
entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
||||
if (entry->refcount == 0)
|
||||
kfree(entry);
|
||||
if (entry)
|
||||
ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
|
||||
|
||||
mutex_unlock(&nf->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (entry->refcount == 1) {
|
||||
status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id,
|
||||
n->event.flags);
|
||||
if (entry) {
|
||||
status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags);
|
||||
if (status) {
|
||||
ssam_nfblk_remove(&n->base);
|
||||
kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
|
||||
ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
|
||||
mutex_unlock(&nf->lock);
|
||||
synchronize_srcu(&nf_head->srcu);
|
||||
return status;
|
||||
}
|
||||
|
||||
entry->flags = n->event.flags;
|
||||
|
||||
} else if (entry->flags != n->event.flags) {
|
||||
ssam_warn(ctrl,
|
||||
"inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
||||
n->event.flags, entry->flags, n->event.reg.target_category,
|
||||
n->event.id.target_category, n->event.id.instance);
|
||||
}
|
||||
|
||||
mutex_unlock(&nf->lock);
|
||||
|
@ -2205,17 +2326,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register);
|
|||
* @ctrl: The controller the notifier has been registered on.
|
||||
* @n: The event notifier to unregister.
|
||||
*
|
||||
* Unregister an event notifier and decrement the usage counter of the
|
||||
* associated SAM event. If the usage counter reaches zero, the event will be
|
||||
* disabled.
|
||||
* Unregister an event notifier. Decrement the usage counter of the associated
|
||||
* SAM event if the notifier is not marked as an observer. If the usage counter
|
||||
* reaches zero, the event will be disabled.
|
||||
*
|
||||
* Return: Returns zero on success, %-ENOENT if the given notifier block has
|
||||
* not been registered on the controller. If the given notifier block was the
|
||||
* last one associated with its specific event, returns the status of the
|
||||
* event-disable EC-command.
|
||||
*/
|
||||
int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
||||
struct ssam_event_notifier *n)
|
||||
int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
|
||||
{
|
||||
u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
|
||||
struct ssam_nf_refcount_entry *entry;
|
||||
|
@ -2236,33 +2356,24 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
|||
return -ENOENT;
|
||||
}
|
||||
|
||||
entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
||||
if (WARN_ON(!entry)) {
|
||||
/*
|
||||
* If this does not return an entry, there's a logic error
|
||||
* somewhere: The notifier block is registered, but the event
|
||||
* refcount entry is not there. Remove the notifier block
|
||||
* anyways.
|
||||
*/
|
||||
status = -ENOENT;
|
||||
goto remove;
|
||||
}
|
||||
/*
|
||||
* If this is an observer notifier, do not attempt to disable the
|
||||
* event, just remove it.
|
||||
*/
|
||||
if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
|
||||
entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
||||
if (WARN_ON(!entry)) {
|
||||
/*
|
||||
* If this does not return an entry, there's a logic
|
||||
* error somewhere: The notifier block is registered,
|
||||
* but the event refcount entry is not there. Remove
|
||||
* the notifier block anyways.
|
||||
*/
|
||||
status = -ENOENT;
|
||||
goto remove;
|
||||
}
|
||||
|
||||
ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
||||
n->event.reg.target_category, n->event.id.target_category,
|
||||
n->event.id.instance, entry->refcount);
|
||||
|
||||
if (entry->flags != n->event.flags) {
|
||||
ssam_warn(ctrl,
|
||||
"inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
||||
n->event.flags, entry->flags, n->event.reg.target_category,
|
||||
n->event.id.target_category, n->event.id.instance);
|
||||
}
|
||||
|
||||
if (entry->refcount == 0) {
|
||||
status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id,
|
||||
n->event.flags);
|
||||
kfree(entry);
|
||||
status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags);
|
||||
}
|
||||
|
||||
remove:
|
||||
|
@ -2274,6 +2385,105 @@ remove:
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
|
||||
|
||||
/**
|
||||
* ssam_controller_event_enable() - Enable the specified event.
|
||||
* @ctrl: The controller to enable the event for.
|
||||
* @reg: The event registry to use for enabling the event.
|
||||
* @id: The event ID specifying the event to be enabled.
|
||||
* @flags: The SAM event flags used for enabling the event.
|
||||
*
|
||||
* Increment the event reference count of the specified event. If the event has
|
||||
* not been enabled previously, it will be enabled by this call.
|
||||
*
|
||||
* Note: In general, ssam_notifier_register() with a non-observer notifier
|
||||
* should be preferred for enabling/disabling events, as this will guarantee
|
||||
* proper ordering and event forwarding in case of errors during event
|
||||
* enabling/disabling.
|
||||
*
|
||||
* Return: Returns zero on success, %-ENOSPC if the reference count for the
|
||||
* specified event has reached its maximum, %-ENOMEM if the corresponding event
|
||||
* entry could not be allocated. If this is the first time that this event has
|
||||
* been enabled (i.e. the reference count was incremented from zero to one by
|
||||
* this call), returns the status of the event-enable EC-command.
|
||||
*/
|
||||
int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
||||
struct ssam_event_registry reg,
|
||||
struct ssam_event_id id, u8 flags)
|
||||
{
|
||||
u16 rqid = ssh_tc_to_rqid(id.target_category);
|
||||
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
||||
struct ssam_nf_refcount_entry *entry;
|
||||
int status;
|
||||
|
||||
if (!ssh_rqid_is_event(rqid))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&nf->lock);
|
||||
|
||||
entry = ssam_nf_refcount_inc(nf, reg, id);
|
||||
if (IS_ERR(entry)) {
|
||||
mutex_unlock(&nf->lock);
|
||||
return PTR_ERR(entry);
|
||||
}
|
||||
|
||||
status = ssam_nf_refcount_enable(ctrl, entry, flags);
|
||||
if (status) {
|
||||
ssam_nf_refcount_dec_free(nf, reg, id);
|
||||
mutex_unlock(&nf->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
mutex_unlock(&nf->lock);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
|
||||
|
||||
/**
|
||||
* ssam_controller_event_disable() - Disable the specified event.
|
||||
* @ctrl: The controller to disable the event for.
|
||||
* @reg: The event registry to use for disabling the event.
|
||||
* @id: The event ID specifying the event to be disabled.
|
||||
* @flags: The flags used when enabling the event.
|
||||
*
|
||||
* Decrement the reference count of the specified event. If the reference count
|
||||
* reaches zero, the event will be disabled.
|
||||
*
|
||||
* Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
|
||||
* non-observer notifier should be preferred for enabling/disabling events, as
|
||||
* this will guarantee proper ordering and event forwarding in case of errors
|
||||
* during event enabling/disabling.
|
||||
*
|
||||
* Return: Returns zero on success, %-ENOENT if the given event has not been
|
||||
* enabled on the controller. If the reference count of the event reaches zero
|
||||
* during this call, returns the status of the event-disable EC-command.
|
||||
*/
|
||||
int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
||||
struct ssam_event_registry reg,
|
||||
struct ssam_event_id id, u8 flags)
|
||||
{
|
||||
u16 rqid = ssh_tc_to_rqid(id.target_category);
|
||||
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
||||
struct ssam_nf_refcount_entry *entry;
|
||||
int status;
|
||||
|
||||
if (!ssh_rqid_is_event(rqid))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&nf->lock);
|
||||
|
||||
entry = ssam_nf_refcount_dec(nf, reg, id);
|
||||
if (!entry) {
|
||||
mutex_unlock(&nf->lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
status = ssam_nf_refcount_disable_free(ctrl, entry, flags);
|
||||
|
||||
mutex_unlock(&nf->lock);
|
||||
return status;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
|
||||
|
||||
/**
|
||||
* ssam_notifier_disable_registered() - Disable events for all registered
|
||||
* notifiers.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Main SSAM/SSH controller structure and functionality.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* Handles communication via requests as well as enabling, disabling, and
|
||||
* relaying of events.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH message builder functions.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH packet transport layer.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work)
|
|||
clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state);
|
||||
|
||||
atomic_dec(&ptl->pending.count);
|
||||
list_del(&p->pending_node);
|
||||
|
||||
list_add_tail(&p->pending_node, &claimed);
|
||||
list_move_tail(&p->pending_node, &claimed);
|
||||
}
|
||||
|
||||
spin_unlock(&ptl->pending.lock);
|
||||
|
@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl)
|
|||
smp_mb__before_atomic();
|
||||
clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state);
|
||||
|
||||
list_del(&p->queue_node);
|
||||
list_add_tail(&p->queue_node, &complete_q);
|
||||
list_move_tail(&p->queue_node, &complete_q);
|
||||
}
|
||||
spin_unlock(&ptl->queue.lock);
|
||||
|
||||
|
@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl)
|
|||
smp_mb__before_atomic();
|
||||
clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state);
|
||||
|
||||
list_del(&p->pending_node);
|
||||
list_add_tail(&p->pending_node, &complete_q);
|
||||
list_move_tail(&p->pending_node, &complete_q);
|
||||
}
|
||||
atomic_set(&ptl->pending.count, 0);
|
||||
spin_unlock(&ptl->pending.lock);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH packet transport layer.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH message parser.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH message parser.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH request transport layer.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
|
@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work)
|
|||
clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state);
|
||||
|
||||
atomic_dec(&rtl->pending.count);
|
||||
list_del(&r->node);
|
||||
|
||||
list_add_tail(&r->node, &claimed);
|
||||
list_move_tail(&r->node, &claimed);
|
||||
}
|
||||
spin_unlock(&rtl->pending.lock);
|
||||
|
||||
|
@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl)
|
|||
smp_mb__before_atomic();
|
||||
clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state);
|
||||
|
||||
list_del(&r->node);
|
||||
list_add_tail(&r->node, &claimed);
|
||||
list_move_tail(&r->node, &claimed);
|
||||
}
|
||||
spin_unlock(&rtl->queue.lock);
|
||||
|
||||
|
@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl)
|
|||
smp_mb__before_atomic();
|
||||
clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state);
|
||||
|
||||
list_del(&r->node);
|
||||
list_add_tail(&r->node, &claimed);
|
||||
list_move_tail(&r->node, &claimed);
|
||||
}
|
||||
spin_unlock(&rtl->pending.lock);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* SSH request transport layer.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*
|
||||
* Trace points for SSAM/SSH.
|
||||
*
|
||||
* Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
|
|
|
@ -3,29 +3,69 @@
|
|||
* Provides user-space access to the SSAM EC via the /dev/surface/aggregator
|
||||
* misc device. Intended for debugging and development.
|
||||
*
|
||||
* Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include <linux/surface_aggregator/cdev.h>
|
||||
#include <linux/surface_aggregator/controller.h>
|
||||
#include <linux/surface_aggregator/serial_hub.h>
|
||||
|
||||
#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
|
||||
|
||||
|
||||
/* -- Main structures. ------------------------------------------------------ */
|
||||
|
||||
enum ssam_cdev_device_state {
|
||||
SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),
|
||||
};
|
||||
|
||||
struct ssam_cdev {
|
||||
struct kref kref;
|
||||
struct rw_semaphore lock;
|
||||
|
||||
struct device *dev;
|
||||
struct ssam_controller *ctrl;
|
||||
struct miscdevice mdev;
|
||||
unsigned long flags;
|
||||
|
||||
struct rw_semaphore client_lock; /* Guards client list. */
|
||||
struct list_head client_list;
|
||||
};
|
||||
|
||||
struct ssam_cdev_client;
|
||||
|
||||
struct ssam_cdev_notifier {
|
||||
struct ssam_cdev_client *client;
|
||||
struct ssam_event_notifier nf;
|
||||
};
|
||||
|
||||
struct ssam_cdev_client {
|
||||
struct ssam_cdev *cdev;
|
||||
struct list_head node;
|
||||
|
||||
struct mutex notifier_lock; /* Guards notifier access for registration */
|
||||
struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];
|
||||
|
||||
struct mutex read_lock; /* Guards FIFO buffer read access */
|
||||
struct mutex write_lock; /* Guards FIFO buffer write access */
|
||||
DECLARE_KFIFO(buffer, u8, 4096);
|
||||
|
||||
wait_queue_head_t waitq;
|
||||
struct fasync_struct *fasync;
|
||||
};
|
||||
|
||||
static void __ssam_cdev_release(struct kref *kref)
|
||||
|
@ -47,24 +87,173 @@ static void ssam_cdev_put(struct ssam_cdev *cdev)
|
|||
kref_put(&cdev->kref, __ssam_cdev_release);
|
||||
}
|
||||
|
||||
static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct miscdevice *mdev = filp->private_data;
|
||||
struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
|
||||
|
||||
filp->private_data = ssam_cdev_get(cdev);
|
||||
return stream_open(inode, filp);
|
||||
}
|
||||
/* -- Notifier handling. ---------------------------------------------------- */
|
||||
|
||||
static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
|
||||
static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
|
||||
{
|
||||
ssam_cdev_put(filp->private_data);
|
||||
struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);
|
||||
struct ssam_cdev_client *client = cdev_nf->client;
|
||||
struct ssam_cdev_event event;
|
||||
size_t n = struct_size(&event, data, in->length);
|
||||
|
||||
/* Translate event. */
|
||||
event.target_category = in->target_category;
|
||||
event.target_id = in->target_id;
|
||||
event.command_id = in->command_id;
|
||||
event.instance_id = in->instance_id;
|
||||
event.length = in->length;
|
||||
|
||||
mutex_lock(&client->write_lock);
|
||||
|
||||
/* Make sure we have enough space. */
|
||||
if (kfifo_avail(&client->buffer) < n) {
|
||||
dev_warn(client->cdev->dev,
|
||||
"buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
|
||||
in->target_category, in->target_id, in->command_id, in->instance_id);
|
||||
mutex_unlock(&client->write_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy event header and payload. */
|
||||
kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));
|
||||
kfifo_in(&client->buffer, &in->data[0], in->length);
|
||||
|
||||
mutex_unlock(&client->write_lock);
|
||||
|
||||
/* Notify waiting readers. */
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
||||
wake_up_interruptible(&client->waitq);
|
||||
|
||||
/*
|
||||
* Don't mark events as handled, this is the job of a proper driver and
|
||||
* not the debugging interface.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
||||
static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)
|
||||
{
|
||||
const u16 rqid = ssh_tc_to_rqid(tc);
|
||||
const u16 event = ssh_rqid_to_event(rqid);
|
||||
struct ssam_cdev_notifier *nf;
|
||||
int status;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
/* Validate notifier target category. */
|
||||
if (!ssh_rqid_is_event(rqid))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&client->notifier_lock);
|
||||
|
||||
/* Check if the notifier has already been registered. */
|
||||
if (client->notifier[event]) {
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
return -EEXIST;
|
||||
}
|
||||
|
||||
/* Allocate new notifier. */
|
||||
nf = kzalloc(sizeof(*nf), GFP_KERNEL);
|
||||
if (!nf) {
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a dummy notifier with the minimal required fields for
|
||||
* observer registration. Note that we can skip fully specifying event
|
||||
* and registry here as we do not need any matching and use silent
|
||||
* registration, which does not enable the corresponding event.
|
||||
*/
|
||||
nf->client = client;
|
||||
nf->nf.base.fn = ssam_cdev_notifier;
|
||||
nf->nf.base.priority = priority;
|
||||
nf->nf.event.id.target_category = tc;
|
||||
nf->nf.event.mask = 0; /* Do not do any matching. */
|
||||
nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
|
||||
|
||||
/* Register notifier. */
|
||||
status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);
|
||||
if (status)
|
||||
kfree(nf);
|
||||
else
|
||||
client->notifier[event] = nf;
|
||||
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
|
||||
{
|
||||
const u16 rqid = ssh_tc_to_rqid(tc);
|
||||
const u16 event = ssh_rqid_to_event(rqid);
|
||||
int status;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
/* Validate notifier target category. */
|
||||
if (!ssh_rqid_is_event(rqid))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&client->notifier_lock);
|
||||
|
||||
/* Check if the notifier is currently registered. */
|
||||
if (!client->notifier[event]) {
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* Unregister and free notifier. */
|
||||
status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);
|
||||
kfree(client->notifier[event]);
|
||||
client->notifier[event] = NULL;
|
||||
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)
|
||||
{
|
||||
int i;
|
||||
|
||||
down_read(&client->cdev->lock);
|
||||
|
||||
/*
|
||||
* This function may be used during shutdown, thus we need to test for
|
||||
* cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.
|
||||
*/
|
||||
if (client->cdev->ctrl) {
|
||||
for (i = 0; i < SSH_NUM_EVENTS; i++)
|
||||
ssam_cdev_notifier_unregister(client, i + 1);
|
||||
|
||||
} else {
|
||||
int count = 0;
|
||||
|
||||
/*
|
||||
* Device has been shut down. Any notifier remaining is a bug,
|
||||
* so warn about that as this would otherwise hardly be
|
||||
* noticeable. Nevertheless, free them as well.
|
||||
*/
|
||||
mutex_lock(&client->notifier_lock);
|
||||
for (i = 0; i < SSH_NUM_EVENTS; i++) {
|
||||
count += !!(client->notifier[i]);
|
||||
kfree(client->notifier[i]);
|
||||
client->notifier[i] = NULL;
|
||||
}
|
||||
mutex_unlock(&client->notifier_lock);
|
||||
|
||||
WARN_ON(count > 0);
|
||||
}
|
||||
|
||||
up_read(&client->cdev->lock);
|
||||
}
|
||||
|
||||
|
||||
/* -- IOCTL functions. ------------------------------------------------------ */
|
||||
|
||||
static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)
|
||||
{
|
||||
struct ssam_cdev_request __user *r;
|
||||
struct ssam_cdev_request rqst;
|
||||
struct ssam_request spec = {};
|
||||
struct ssam_response rsp = {};
|
||||
|
@ -72,7 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|||
void __user *rspdata;
|
||||
int status = 0, ret = 0, tmp;
|
||||
|
||||
r = (struct ssam_cdev_request __user *)arg;
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
|
||||
if (ret)
|
||||
goto out;
|
||||
|
@ -152,7 +342,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|||
}
|
||||
|
||||
/* Perform request. */
|
||||
status = ssam_request_sync(cdev->ctrl, &spec, &rsp);
|
||||
status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp);
|
||||
if (status)
|
||||
goto out;
|
||||
|
||||
|
@ -177,48 +367,315 @@ out:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd,
|
||||
static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
|
||||
const struct ssam_cdev_notifier_desc __user *d)
|
||||
{
|
||||
struct ssam_cdev_notifier_desc desc;
|
||||
long ret;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);
|
||||
}
|
||||
|
||||
static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
|
||||
const struct ssam_cdev_notifier_desc __user *d)
|
||||
{
|
||||
struct ssam_cdev_notifier_desc desc;
|
||||
long ret;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return ssam_cdev_notifier_unregister(client, desc.target_category);
|
||||
}
|
||||
|
||||
static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
|
||||
const struct ssam_cdev_event_desc __user *d)
|
||||
{
|
||||
struct ssam_cdev_event_desc desc;
|
||||
struct ssam_event_registry reg;
|
||||
struct ssam_event_id id;
|
||||
long ret;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
/* Read descriptor from user-space. */
|
||||
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Translate descriptor. */
|
||||
reg.target_category = desc.reg.target_category;
|
||||
reg.target_id = desc.reg.target_id;
|
||||
reg.cid_enable = desc.reg.cid_enable;
|
||||
reg.cid_disable = desc.reg.cid_disable;
|
||||
|
||||
id.target_category = desc.id.target_category;
|
||||
id.instance = desc.id.instance;
|
||||
|
||||
/* Disable event. */
|
||||
return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags);
|
||||
}
|
||||
|
||||
static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
|
||||
const struct ssam_cdev_event_desc __user *d)
|
||||
{
|
||||
struct ssam_cdev_event_desc desc;
|
||||
struct ssam_event_registry reg;
|
||||
struct ssam_event_id id;
|
||||
long ret;
|
||||
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
/* Read descriptor from user-space. */
|
||||
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Translate descriptor. */
|
||||
reg.target_category = desc.reg.target_category;
|
||||
reg.target_id = desc.reg.target_id;
|
||||
reg.cid_enable = desc.reg.cid_enable;
|
||||
reg.cid_disable = desc.reg.cid_disable;
|
||||
|
||||
id.target_category = desc.id.target_category;
|
||||
id.instance = desc.id.instance;
|
||||
|
||||
/* Disable event. */
|
||||
return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags);
|
||||
}
|
||||
|
||||
|
||||
/* -- File operations. ------------------------------------------------------ */
|
||||
|
||||
static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct miscdevice *mdev = filp->private_data;
|
||||
struct ssam_cdev_client *client;
|
||||
struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
|
||||
|
||||
/* Initialize client */
|
||||
client = vzalloc(sizeof(*client));
|
||||
if (!client)
|
||||
return -ENOMEM;
|
||||
|
||||
client->cdev = ssam_cdev_get(cdev);
|
||||
|
||||
INIT_LIST_HEAD(&client->node);
|
||||
|
||||
mutex_init(&client->notifier_lock);
|
||||
|
||||
mutex_init(&client->read_lock);
|
||||
mutex_init(&client->write_lock);
|
||||
INIT_KFIFO(client->buffer);
|
||||
init_waitqueue_head(&client->waitq);
|
||||
|
||||
filp->private_data = client;
|
||||
|
||||
/* Attach client. */
|
||||
down_write(&cdev->client_lock);
|
||||
|
||||
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
||||
up_write(&cdev->client_lock);
|
||||
mutex_destroy(&client->write_lock);
|
||||
mutex_destroy(&client->read_lock);
|
||||
mutex_destroy(&client->notifier_lock);
|
||||
ssam_cdev_put(client->cdev);
|
||||
vfree(client);
|
||||
return -ENODEV;
|
||||
}
|
||||
list_add_tail(&client->node, &cdev->client_list);
|
||||
|
||||
up_write(&cdev->client_lock);
|
||||
|
||||
stream_open(inode, filp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct ssam_cdev_client *client = filp->private_data;
|
||||
|
||||
/* Force-unregister all remaining notifiers of this client. */
|
||||
ssam_cdev_notifier_unregister_all(client);
|
||||
|
||||
/* Detach client. */
|
||||
down_write(&client->cdev->client_lock);
|
||||
list_del(&client->node);
|
||||
up_write(&client->cdev->client_lock);
|
||||
|
||||
/* Free client. */
|
||||
mutex_destroy(&client->write_lock);
|
||||
mutex_destroy(&client->read_lock);
|
||||
|
||||
mutex_destroy(&client->notifier_lock);
|
||||
|
||||
ssam_cdev_put(client->cdev);
|
||||
vfree(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
lockdep_assert_held_read(&client->cdev->lock);
|
||||
|
||||
switch (cmd) {
|
||||
case SSAM_CDEV_REQUEST:
|
||||
return ssam_cdev_request(cdev, arg);
|
||||
return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
|
||||
|
||||
case SSAM_CDEV_NOTIF_REGISTER:
|
||||
return ssam_cdev_notif_register(client,
|
||||
(struct ssam_cdev_notifier_desc __user *)arg);
|
||||
|
||||
case SSAM_CDEV_NOTIF_UNREGISTER:
|
||||
return ssam_cdev_notif_unregister(client,
|
||||
(struct ssam_cdev_notifier_desc __user *)arg);
|
||||
|
||||
case SSAM_CDEV_EVENT_ENABLE:
|
||||
return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg);
|
||||
|
||||
case SSAM_CDEV_EVENT_DISABLE:
|
||||
return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg);
|
||||
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct ssam_cdev *cdev = file->private_data;
|
||||
struct ssam_cdev_client *client = file->private_data;
|
||||
long status;
|
||||
|
||||
/* Ensure that controller is valid for as long as we need it. */
|
||||
if (down_read_killable(&client->cdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {
|
||||
up_read(&client->cdev->lock);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
status = __ssam_cdev_device_ioctl(client, cmd, arg);
|
||||
|
||||
up_read(&client->cdev->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
|
||||
{
|
||||
struct ssam_cdev_client *client = file->private_data;
|
||||
struct ssam_cdev *cdev = client->cdev;
|
||||
unsigned int copied;
|
||||
int status = 0;
|
||||
|
||||
if (down_read_killable(&cdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
if (!cdev->ctrl) {
|
||||
/* Make sure we're not shut down. */
|
||||
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
||||
up_read(&cdev->lock);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
status = __ssam_cdev_device_ioctl(cdev, cmd, arg);
|
||||
do {
|
||||
/* Check availability, wait if necessary. */
|
||||
if (kfifo_is_empty(&client->buffer)) {
|
||||
up_read(&cdev->lock);
|
||||
|
||||
if (file->f_flags & O_NONBLOCK)
|
||||
return -EAGAIN;
|
||||
|
||||
status = wait_event_interruptible(client->waitq,
|
||||
!kfifo_is_empty(&client->buffer) ||
|
||||
test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,
|
||||
&cdev->flags));
|
||||
if (status < 0)
|
||||
return status;
|
||||
|
||||
if (down_read_killable(&cdev->lock))
|
||||
return -ERESTARTSYS;
|
||||
|
||||
/* Need to check that we're not shut down again. */
|
||||
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
||||
up_read(&cdev->lock);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to read from FIFO. */
|
||||
if (mutex_lock_interruptible(&client->read_lock)) {
|
||||
up_read(&cdev->lock);
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
status = kfifo_to_user(&client->buffer, buf, count, &copied);
|
||||
mutex_unlock(&client->read_lock);
|
||||
|
||||
if (status < 0) {
|
||||
up_read(&cdev->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* We might not have gotten anything, check this here. */
|
||||
if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
|
||||
up_read(&cdev->lock);
|
||||
return -EAGAIN;
|
||||
}
|
||||
} while (copied == 0);
|
||||
|
||||
up_read(&cdev->lock);
|
||||
return status;
|
||||
return copied;
|
||||
}
|
||||
|
||||
static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)
|
||||
{
|
||||
struct ssam_cdev_client *client = file->private_data;
|
||||
__poll_t events = 0;
|
||||
|
||||
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))
|
||||
return EPOLLHUP | EPOLLERR;
|
||||
|
||||
poll_wait(file, &client->waitq, pt);
|
||||
|
||||
if (!kfifo_is_empty(&client->buffer))
|
||||
events |= EPOLLIN | EPOLLRDNORM;
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
static int ssam_cdev_fasync(int fd, struct file *file, int on)
|
||||
{
|
||||
struct ssam_cdev_client *client = file->private_data;
|
||||
|
||||
return fasync_helper(fd, file, on, &client->fasync);
|
||||
}
|
||||
|
||||
static const struct file_operations ssam_controller_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = ssam_cdev_device_open,
|
||||
.release = ssam_cdev_device_release,
|
||||
.read = ssam_cdev_read,
|
||||
.poll = ssam_cdev_poll,
|
||||
.fasync = ssam_cdev_fasync,
|
||||
.unlocked_ioctl = ssam_cdev_device_ioctl,
|
||||
.compat_ioctl = ssam_cdev_device_ioctl,
|
||||
.llseek = noop_llseek,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
|
||||
/* -- Device and driver setup ----------------------------------------------- */
|
||||
|
||||
static int ssam_dbg_device_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ssam_controller *ctrl;
|
||||
|
@ -236,6 +693,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|||
kref_init(&cdev->kref);
|
||||
init_rwsem(&cdev->lock);
|
||||
cdev->ctrl = ctrl;
|
||||
cdev->dev = &pdev->dev;
|
||||
|
||||
cdev->mdev.parent = &pdev->dev;
|
||||
cdev->mdev.minor = MISC_DYNAMIC_MINOR;
|
||||
|
@ -243,6 +701,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|||
cdev->mdev.nodename = "surface/aggregator";
|
||||
cdev->mdev.fops = &ssam_controller_fops;
|
||||
|
||||
init_rwsem(&cdev->client_lock);
|
||||
INIT_LIST_HEAD(&cdev->client_list);
|
||||
|
||||
status = misc_register(&cdev->mdev);
|
||||
if (status) {
|
||||
kfree(cdev);
|
||||
|
@ -256,8 +717,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|||
static int ssam_dbg_device_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ssam_cdev *cdev = platform_get_drvdata(pdev);
|
||||
struct ssam_cdev_client *client;
|
||||
|
||||
misc_deregister(&cdev->mdev);
|
||||
/*
|
||||
* Mark device as shut-down. Prevent new clients from being added and
|
||||
* new operations from being executed.
|
||||
*/
|
||||
set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);
|
||||
|
||||
down_write(&cdev->client_lock);
|
||||
|
||||
/* Remove all notifiers registered by us. */
|
||||
list_for_each_entry(client, &cdev->client_list, node) {
|
||||
ssam_cdev_notifier_unregister_all(client);
|
||||
}
|
||||
|
||||
/* Wake up async clients. */
|
||||
list_for_each_entry(client, &cdev->client_list, node) {
|
||||
kill_fasync(&client->fasync, SIGIO, POLL_HUP);
|
||||
}
|
||||
|
||||
/* Wake up blocking clients. */
|
||||
list_for_each_entry(client, &cdev->client_list, node) {
|
||||
wake_up_interruptible(&client->waitq);
|
||||
}
|
||||
|
||||
up_write(&cdev->client_lock);
|
||||
|
||||
/*
|
||||
* The controller is only guaranteed to be valid for as long as the
|
||||
|
@ -266,8 +751,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev)
|
|||
*/
|
||||
down_write(&cdev->lock);
|
||||
cdev->ctrl = NULL;
|
||||
cdev->dev = NULL;
|
||||
up_write(&cdev->lock);
|
||||
|
||||
misc_deregister(&cdev->mdev);
|
||||
|
||||
ssam_cdev_put(cdev);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = {
|
|||
.parent = &ssam_node_hub_base,
|
||||
};
|
||||
|
||||
/* Devices for Surface Book 2. */
|
||||
static const struct software_node *ssam_node_group_sb2[] = {
|
||||
/*
|
||||
* Devices for 5th- and 6th-generations models:
|
||||
* - Surface Book 2,
|
||||
* - Surface Laptop 1 and 2,
|
||||
* - Surface Pro 5 and 6.
|
||||
*/
|
||||
static const struct software_node *ssam_node_group_gen5[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_tmp_pprof,
|
||||
NULL,
|
||||
|
@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Laptop 1. */
|
||||
static const struct software_node *ssam_node_group_sl1[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_tmp_pprof,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Laptop 2. */
|
||||
static const struct software_node *ssam_node_group_sl2[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_tmp_pprof,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Laptop 3 and 4. */
|
||||
static const struct software_node *ssam_node_group_sl3[] = {
|
||||
&ssam_node_root,
|
||||
|
@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 5. */
|
||||
static const struct software_node *ssam_node_group_sp5[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_tmp_pprof,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 6. */
|
||||
static const struct software_node *ssam_node_group_sp6[] = {
|
||||
&ssam_node_root,
|
||||
&ssam_node_tmp_pprof,
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Devices for Surface Pro 7 and Surface Pro 7+. */
|
||||
static const struct software_node *ssam_node_group_sp7[] = {
|
||||
&ssam_node_root,
|
||||
|
@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = {
|
|||
|
||||
static const struct acpi_device_id ssam_platform_hub_match[] = {
|
||||
/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
|
||||
{ "MSHW0081", (unsigned long)ssam_node_group_sp5 },
|
||||
{ "MSHW0081", (unsigned long)ssam_node_group_gen5 },
|
||||
|
||||
/* Surface Pro 6 (OMBR >= 0x10) */
|
||||
{ "MSHW0111", (unsigned long)ssam_node_group_sp6 },
|
||||
{ "MSHW0111", (unsigned long)ssam_node_group_gen5 },
|
||||
|
||||
/* Surface Pro 7 */
|
||||
{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
|
||||
|
@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|||
{ "MSHW0119", (unsigned long)ssam_node_group_sp7 },
|
||||
|
||||
/* Surface Book 2 */
|
||||
{ "MSHW0107", (unsigned long)ssam_node_group_sb2 },
|
||||
{ "MSHW0107", (unsigned long)ssam_node_group_gen5 },
|
||||
|
||||
/* Surface Book 3 */
|
||||
{ "MSHW0117", (unsigned long)ssam_node_group_sb3 },
|
||||
|
||||
/* Surface Laptop 1 */
|
||||
{ "MSHW0086", (unsigned long)ssam_node_group_sl1 },
|
||||
{ "MSHW0086", (unsigned long)ssam_node_group_gen5 },
|
||||
|
||||
/* Surface Laptop 2 */
|
||||
{ "MSHW0112", (unsigned long)ssam_node_group_sl2 },
|
||||
{ "MSHW0112", (unsigned long)ssam_node_group_gen5 },
|
||||
|
||||
/* Surface Laptop 3 (13", Intel) */
|
||||
{ "MSHW0114", (unsigned long)ssam_node_group_sl3 },
|
||||
|
|
|
@ -415,16 +415,17 @@ config HP_ACCEL
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called hp_accel.
|
||||
|
||||
config HP_WIRELESS
|
||||
tristate "HP wireless button"
|
||||
config WIRELESS_HOTKEY
|
||||
tristate "Wireless hotkey button"
|
||||
depends on ACPI
|
||||
depends on INPUT
|
||||
help
|
||||
This driver provides supports for new HP wireless button for Windows 8.
|
||||
This driver provides supports for the wireless buttons found on some AMD,
|
||||
HP, & Xioami laptops.
|
||||
On such systems the driver should load automatically (via ACPI alias).
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called hp-wireless.
|
||||
be called wireless-hotkey.
|
||||
|
||||
config HP_WMI
|
||||
tristate "HP WMI extras"
|
||||
|
@ -639,6 +640,19 @@ config THINKPAD_ACPI_HOTKEY_POLL
|
|||
If you are not sure, say Y here. The driver enables polling only if
|
||||
it is strictly necessary to do so.
|
||||
|
||||
config THINKPAD_LMI
|
||||
tristate "Lenovo WMI-based systems management driver"
|
||||
depends on ACPI_WMI
|
||||
select FW_ATTR_CLASS
|
||||
help
|
||||
This driver allows changing BIOS settings on Lenovo machines whose
|
||||
BIOS support the WMI interface.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called think-lmi.
|
||||
|
||||
source "drivers/platform/x86/intel/Kconfig"
|
||||
|
||||
config INTEL_ATOMISP2_LED
|
||||
tristate "Intel AtomISP2 camera LED driver"
|
||||
depends on GPIOLIB && LEDS_GPIO
|
||||
|
@ -673,30 +687,6 @@ config INTEL_ATOMISP2_PM
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel_atomisp2_pm.
|
||||
|
||||
config INTEL_CHT_INT33FE
|
||||
tristate "Intel Cherry Trail ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the INT33FE ACPI device found on
|
||||
some Intel Cherry Trail devices.
|
||||
|
||||
There are two kinds of INT33FE ACPI device possible: for hardware
|
||||
with USB Type-C and Micro-B connectors. This driver supports both.
|
||||
|
||||
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
|
||||
resources for Fuel Gauge Controller and (in the Type-C variant)
|
||||
FUSB302 USB Type-C Controller and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B
|
||||
device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
|
||||
for Type-C device.
|
||||
|
||||
config INTEL_HID_EVENT
|
||||
tristate "INTEL HID Event"
|
||||
depends on ACPI
|
||||
|
@ -1076,6 +1066,9 @@ config TOUCHSCREEN_DMI
|
|||
the OS-image for the device. This option supplies the missing info.
|
||||
Enable this for x86 tablets with Silead or Chipone touchscreens.
|
||||
|
||||
config FW_ATTR_CLASS
|
||||
tristate
|
||||
|
||||
config INTEL_IMR
|
||||
bool "Intel Isolated Memory Region support"
|
||||
depends on X86_INTEL_QUARK && IOSF_MBI
|
||||
|
|
|
@ -52,7 +52,6 @@ obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o
|
|||
|
||||
# Hewlett Packard
|
||||
obj-$(CONFIG_HP_ACCEL) += hp_accel.o
|
||||
obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o
|
||||
obj-$(CONFIG_HP_WMI) += hp-wmi.o
|
||||
obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o
|
||||
|
||||
|
@ -64,14 +63,13 @@ obj-$(CONFIG_IBM_RTL) += ibm_rtl.o
|
|||
obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o
|
||||
obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o
|
||||
obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o
|
||||
obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o
|
||||
|
||||
# Intel
|
||||
obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/
|
||||
|
||||
obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o
|
||||
obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
|
||||
intel_cht_int33fe-objs := intel_cht_int33fe_common.o \
|
||||
intel_cht_int33fe_typec.o \
|
||||
intel_cht_int33fe_microb.o
|
||||
obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o
|
||||
obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o
|
||||
obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o
|
||||
|
@ -112,9 +110,11 @@ obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o
|
|||
obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o
|
||||
|
||||
# Platform drivers
|
||||
obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o
|
||||
obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o
|
||||
obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o
|
||||
obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o
|
||||
obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o
|
||||
|
||||
# Intel uncore drivers
|
||||
obj-$(CONFIG_INTEL_IPS) += intel_ips.o
|
||||
|
|
|
@ -110,11 +110,6 @@ static struct quirk_entry quirk_asus_forceals = {
|
|||
.wmi_force_als_set = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asus_vendor_backlight = {
|
||||
.wmi_backlight_power = true,
|
||||
.wmi_backlight_set_devstate = true,
|
||||
};
|
||||
|
||||
static struct quirk_entry quirk_asus_use_kbd_dock_devid = {
|
||||
.use_kbd_dock_devid = true,
|
||||
};
|
||||
|
@ -425,78 +420,6 @@ static const struct dmi_system_id asus_quirks[] = {
|
|||
},
|
||||
.driver_data = &quirk_asus_forceals,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA401IH",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA401IH"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA401II",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA401II"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA401IU",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA401IU"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA401IV",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA401IV"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA401IVC",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA401IVC"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA502II",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA502II"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA502IU",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA502IU"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "ASUSTeK COMPUTER INC. GA502IV",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "GA502IV"),
|
||||
},
|
||||
.driver_data = &quirk_asus_vendor_backlight,
|
||||
},
|
||||
{
|
||||
.callback = dmi_matched,
|
||||
.ident = "Asus Transformer T100TA / T100HA / T100CHI",
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
menuconfig X86_PLATFORM_DRIVERS_DELL
|
||||
bool "Dell X86 Platform Specific Device Drivers"
|
||||
default n
|
||||
depends on X86_PLATFORM_DEVICES
|
||||
help
|
||||
Say Y here to get to see options for device drivers for various
|
||||
|
@ -53,6 +52,7 @@ config DELL_LAPTOP
|
|||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
depends on ACPI_VIDEO || ACPI_VIDEO = n
|
||||
depends on RFKILL || RFKILL = n
|
||||
depends on DELL_WMI || DELL_WMI = n
|
||||
depends on SERIO_I8042
|
||||
depends on DELL_SMBIOS
|
||||
select POWER_SUPPLY
|
||||
|
@ -164,6 +164,14 @@ config DELL_WMI
|
|||
To compile this driver as a module, choose M here: the module will
|
||||
be called dell-wmi.
|
||||
|
||||
config DELL_WMI_PRIVACY
|
||||
bool "Dell WMI Hardware Privacy Support"
|
||||
depends on DELL_WMI
|
||||
depends on LEDS_TRIGGER_AUDIO
|
||||
help
|
||||
This option adds integration with the "Dell Hardware Privacy"
|
||||
feature of Dell laptops to the dell-wmi driver.
|
||||
|
||||
config DELL_WMI_AIO
|
||||
tristate "WMI Hotkeys for Dell All-In-One series"
|
||||
default m
|
||||
|
@ -197,6 +205,7 @@ config DELL_WMI_SYSMAN
|
|||
depends on ACPI_WMI
|
||||
depends on DMI
|
||||
select NLS
|
||||
select FW_ATTR_CLASS
|
||||
help
|
||||
This driver allows changing BIOS settings on many Dell machines from
|
||||
2018 and newer without the use of any additional software.
|
||||
|
|
|
@ -15,6 +15,8 @@ dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o
|
|||
dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o
|
||||
obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o
|
||||
obj-$(CONFIG_DELL_WMI) += dell-wmi.o
|
||||
dell-wmi-objs := dell-wmi-base.o
|
||||
dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o
|
||||
obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o
|
||||
obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o
|
||||
obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o
|
||||
|
|
|
@ -394,8 +394,7 @@ static int host_control_smi(void)
|
|||
|
||||
/* wait a few to see if it executed */
|
||||
num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING;
|
||||
while ((cmd_status = inb(PCAT_APM_STATUS_PORT))
|
||||
== ESM_STATUS_CMD_UNSUCCESSFUL) {
|
||||
while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) {
|
||||
num_ticks--;
|
||||
if (num_ticks == EXPIRED_TIMER)
|
||||
return -ETIME;
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#include "dell-rbtn.h"
|
||||
#include "dell-smbios.h"
|
||||
|
||||
#include "dell-wmi-privacy.h"
|
||||
|
||||
struct quirk_entry {
|
||||
bool touchpad_led;
|
||||
bool kbd_led_not_present;
|
||||
|
@ -90,6 +92,7 @@ static struct rfkill *wifi_rfkill;
|
|||
static struct rfkill *bluetooth_rfkill;
|
||||
static struct rfkill *wwan_rfkill;
|
||||
static bool force_rfkill;
|
||||
static bool micmute_led_registered;
|
||||
|
||||
module_param(force_rfkill, bool, 0444);
|
||||
MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models");
|
||||
|
@ -2205,11 +2208,13 @@ static int __init dell_init(void)
|
|||
dell_laptop_register_notifier(&dell_laptop_notifier);
|
||||
|
||||
if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) &&
|
||||
dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) {
|
||||
dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE) &&
|
||||
!dell_privacy_has_mic_mute()) {
|
||||
micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
|
||||
ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev);
|
||||
if (ret < 0)
|
||||
goto fail_led;
|
||||
micmute_led_registered = true;
|
||||
}
|
||||
|
||||
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
|
||||
|
@ -2257,7 +2262,8 @@ static int __init dell_init(void)
|
|||
fail_get_brightness:
|
||||
backlight_device_unregister(dell_backlight_device);
|
||||
fail_backlight:
|
||||
led_classdev_unregister(&micmute_led_cdev);
|
||||
if (micmute_led_registered)
|
||||
led_classdev_unregister(&micmute_led_cdev);
|
||||
fail_led:
|
||||
dell_cleanup_rfkill();
|
||||
fail_rfkill:
|
||||
|
@ -2278,7 +2284,8 @@ static void __exit dell_exit(void)
|
|||
touchpad_led_exit();
|
||||
kbd_led_exit();
|
||||
backlight_device_unregister(dell_backlight_device);
|
||||
led_classdev_unregister(&micmute_led_cdev);
|
||||
if (micmute_led_registered)
|
||||
led_classdev_unregister(&micmute_led_cdev);
|
||||
dell_cleanup_rfkill();
|
||||
if (platform_device) {
|
||||
platform_device_unregister(platform_device);
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <acpi/video.h>
|
||||
#include "dell-smbios.h"
|
||||
#include "dell-wmi-descriptor.h"
|
||||
#include "dell-wmi-privacy.h"
|
||||
|
||||
MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
|
||||
MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
|
||||
|
@ -427,7 +428,6 @@ static void dell_wmi_notify(struct wmi_device *wdev,
|
|||
|
||||
switch (buffer_entry[1]) {
|
||||
case 0x0000: /* One key pressed or event occurred */
|
||||
case 0x0012: /* Event with extended data occurred */
|
||||
if (len > 2)
|
||||
dell_wmi_process_key(wdev, buffer_entry[1],
|
||||
buffer_entry[2]);
|
||||
|
@ -439,6 +439,13 @@ static void dell_wmi_notify(struct wmi_device *wdev,
|
|||
dell_wmi_process_key(wdev, buffer_entry[1],
|
||||
buffer_entry[i]);
|
||||
break;
|
||||
case 0x0012:
|
||||
if ((len > 4) && dell_privacy_process_event(buffer_entry[1], buffer_entry[3],
|
||||
buffer_entry[4]))
|
||||
/* dell_privacy_process_event has handled the event */;
|
||||
else if (len > 2)
|
||||
dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2]);
|
||||
break;
|
||||
default: /* Unknown event */
|
||||
pr_info("Unknown WMI event type 0x%x\n",
|
||||
(int)buffer_entry[1]);
|
||||
|
@ -747,6 +754,10 @@ static int __init dell_wmi_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
err = dell_privacy_register_driver();
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return wmi_driver_register(&dell_wmi_driver);
|
||||
}
|
||||
late_initcall(dell_wmi_init);
|
||||
|
@ -757,6 +768,7 @@ static void __exit dell_wmi_exit(void)
|
|||
dell_wmi_events_set_enabled(false);
|
||||
|
||||
wmi_driver_unregister(&dell_wmi_driver);
|
||||
dell_privacy_unregister_driver();
|
||||
}
|
||||
module_exit(dell_wmi_exit);
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Dell privacy notification driver
|
||||
*
|
||||
* Copyright (C) 2021 Dell Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/input/sparse-keymap.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/wmi.h>
|
||||
|
||||
#include "dell-wmi-privacy.h"
|
||||
|
||||
#define DELL_PRIVACY_GUID "6932965F-1671-4CEB-B988-D3AB0A901919"
|
||||
#define MICROPHONE_STATUS BIT(0)
|
||||
#define CAMERA_STATUS BIT(1)
|
||||
#define DELL_PRIVACY_AUDIO_EVENT 0x1
|
||||
#define DELL_PRIVACY_CAMERA_EVENT 0x2
|
||||
#define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev)
|
||||
|
||||
/*
|
||||
* The wmi_list is used to store the privacy_priv struct with mutex protecting
|
||||
*/
|
||||
static LIST_HEAD(wmi_list);
|
||||
static DEFINE_MUTEX(list_mutex);
|
||||
|
||||
struct privacy_wmi_data {
|
||||
struct input_dev *input_dev;
|
||||
struct wmi_device *wdev;
|
||||
struct list_head list;
|
||||
struct led_classdev cdev;
|
||||
u32 features_present;
|
||||
u32 last_status;
|
||||
};
|
||||
|
||||
/* DELL Privacy Type */
|
||||
enum dell_hardware_privacy_type {
|
||||
DELL_PRIVACY_TYPE_AUDIO = 0,
|
||||
DELL_PRIVACY_TYPE_CAMERA,
|
||||
DELL_PRIVACY_TYPE_SCREEN,
|
||||
DELL_PRIVACY_TYPE_MAX,
|
||||
};
|
||||
|
||||
static const char * const privacy_types[DELL_PRIVACY_TYPE_MAX] = {
|
||||
[DELL_PRIVACY_TYPE_AUDIO] = "Microphone",
|
||||
[DELL_PRIVACY_TYPE_CAMERA] = "Camera Shutter",
|
||||
[DELL_PRIVACY_TYPE_SCREEN] = "ePrivacy Screen",
|
||||
};
|
||||
|
||||
/*
|
||||
* Keymap for WMI privacy events of type 0x0012
|
||||
*/
|
||||
static const struct key_entry dell_wmi_keymap_type_0012[] = {
|
||||
/* privacy mic mute */
|
||||
{ KE_KEY, 0x0001, { KEY_MICMUTE } },
|
||||
/* privacy camera mute */
|
||||
{ KE_SW, 0x0002, { SW_CAMERA_LENS_COVER } },
|
||||
{ KE_END, 0},
|
||||
};
|
||||
|
||||
bool dell_privacy_has_mic_mute(void)
|
||||
{
|
||||
struct privacy_wmi_data *priv;
|
||||
|
||||
mutex_lock(&list_mutex);
|
||||
priv = list_first_entry_or_null(&wmi_list,
|
||||
struct privacy_wmi_data,
|
||||
list);
|
||||
mutex_unlock(&list_mutex);
|
||||
|
||||
return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dell_privacy_has_mic_mute);
|
||||
|
||||
/*
|
||||
* The flow of privacy event:
|
||||
* 1) User presses key. HW does stuff with this key (timeout is started)
|
||||
* 2) WMI event is emitted from BIOS
|
||||
* 3) WMI event is received by dell-privacy
|
||||
* 4) KEY_MICMUTE emitted from dell-privacy
|
||||
* 5) Userland picks up key and modifies kcontrol for SW mute
|
||||
* 6) Codec kernel driver catches and calls ledtrig_audio_set which will call
|
||||
* led_set_brightness() on the LED registered by dell_privacy_leds_setup()
|
||||
* 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute activates.
|
||||
* If the EC is not notified then the HW mic mute will activate when the timeout
|
||||
* triggers, just a bit later than with the active ack.
|
||||
*/
|
||||
bool dell_privacy_process_event(int type, int code, int status)
|
||||
{
|
||||
struct privacy_wmi_data *priv;
|
||||
const struct key_entry *key;
|
||||
bool ret = false;
|
||||
|
||||
mutex_lock(&list_mutex);
|
||||
priv = list_first_entry_or_null(&wmi_list,
|
||||
struct privacy_wmi_data,
|
||||
list);
|
||||
if (!priv)
|
||||
goto error;
|
||||
|
||||
key = sparse_keymap_entry_from_scancode(priv->input_dev, (type << 16) | code);
|
||||
if (!key) {
|
||||
dev_warn(&priv->wdev->dev, "Unknown key with type 0x%04x and code 0x%04x pressed\n",
|
||||
type, code);
|
||||
goto error;
|
||||
}
|
||||
dev_dbg(&priv->wdev->dev, "Key with type 0x%04x and code 0x%04x pressed\n", type, code);
|
||||
|
||||
switch (code) {
|
||||
case DELL_PRIVACY_AUDIO_EVENT: /* Mic mute */
|
||||
case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */
|
||||
priv->last_status = status;
|
||||
sparse_keymap_report_entry(priv->input_dev, key, 1, true);
|
||||
ret = true;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(&priv->wdev->dev, "unknown event type 0x%04x 0x%04x\n", type, code);
|
||||
}
|
||||
|
||||
error:
|
||||
mutex_unlock(&list_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t dell_privacy_supported_type_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct privacy_wmi_data *priv = dev_get_drvdata(dev);
|
||||
enum dell_hardware_privacy_type type;
|
||||
u32 privacy_list;
|
||||
int len = 0;
|
||||
|
||||
privacy_list = priv->features_present;
|
||||
for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) {
|
||||
if (privacy_list & BIT(type))
|
||||
len += sysfs_emit_at(buf, len, "[%s] [supported]\n", privacy_types[type]);
|
||||
else
|
||||
len += sysfs_emit_at(buf, len, "[%s] [unsupported]\n", privacy_types[type]);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t dell_privacy_current_state_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct privacy_wmi_data *priv = dev_get_drvdata(dev);
|
||||
u32 privacy_supported = priv->features_present;
|
||||
enum dell_hardware_privacy_type type;
|
||||
u32 privacy_state = priv->last_status;
|
||||
int len = 0;
|
||||
|
||||
for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) {
|
||||
if (privacy_supported & BIT(type)) {
|
||||
if (privacy_state & BIT(type))
|
||||
len += sysfs_emit_at(buf, len, "[%s] [unmuted]\n", privacy_types[type]);
|
||||
else
|
||||
len += sysfs_emit_at(buf, len, "[%s] [muted]\n", privacy_types[type]);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RO(dell_privacy_supported_type);
|
||||
static DEVICE_ATTR_RO(dell_privacy_current_state);
|
||||
|
||||
static struct attribute *privacy_attributes[] = {
|
||||
&dev_attr_dell_privacy_supported_type.attr,
|
||||
&dev_attr_dell_privacy_current_state.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group privacy_attribute_group = {
|
||||
.attrs = privacy_attributes
|
||||
};
|
||||
|
||||
/*
|
||||
* Describes the Device State class exposed by BIOS which can be consumed by
|
||||
* various applications interested in knowing the Privacy feature capabilities.
|
||||
* class DeviceState
|
||||
* {
|
||||
* [key, read] string InstanceName;
|
||||
* [read] boolean ReadOnly;
|
||||
*
|
||||
* [WmiDataId(1), read] uint32 DevicesSupported;
|
||||
* 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen
|
||||
*
|
||||
* [WmiDataId(2), read] uint32 CurrentState;
|
||||
* 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen
|
||||
* };
|
||||
*/
|
||||
static int get_current_status(struct wmi_device *wdev)
|
||||
{
|
||||
struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev);
|
||||
union acpi_object *obj_present;
|
||||
u32 *buffer;
|
||||
int ret = 0;
|
||||
|
||||
if (!priv) {
|
||||
dev_err(&wdev->dev, "dell privacy priv is NULL\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/* check privacy support features and device states */
|
||||
obj_present = wmidev_block_query(wdev, 0);
|
||||
if (!obj_present) {
|
||||
dev_err(&wdev->dev, "failed to read Binary MOF\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (obj_present->type != ACPI_TYPE_BUFFER) {
|
||||
dev_err(&wdev->dev, "Binary MOF is not a buffer!\n");
|
||||
ret = -EIO;
|
||||
goto obj_free;
|
||||
}
|
||||
/* Although it's not technically a failure, this would lead to
|
||||
* unexpected behavior
|
||||
*/
|
||||
if (obj_present->buffer.length != 8) {
|
||||
dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n",
|
||||
obj_present->buffer.length);
|
||||
ret = -EINVAL;
|
||||
goto obj_free;
|
||||
}
|
||||
buffer = (u32 *)obj_present->buffer.pointer;
|
||||
priv->features_present = buffer[0];
|
||||
priv->last_status = buffer[1];
|
||||
|
||||
obj_free:
|
||||
kfree(obj_present);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct privacy_wmi_data *priv = led_to_priv(led_cdev);
|
||||
static char *acpi_method = (char *)"ECAK";
|
||||
acpi_status status;
|
||||
acpi_handle handle;
|
||||
|
||||
handle = ec_get_handle();
|
||||
if (!handle)
|
||||
return -EIO;
|
||||
|
||||
if (!acpi_has_method(handle, acpi_method))
|
||||
return -EIO;
|
||||
|
||||
status = acpi_evaluate_object(handle, acpi_method, NULL, NULL);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
dev_err(&priv->wdev->dev, "Error setting privacy EC ack value: %s\n",
|
||||
acpi_format_exception(status));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pressing the mute key activates a time delayed circuit to physically cut
|
||||
* off the mute. The LED is in the same circuit, so it reflects the true
|
||||
* state of the HW mute. The reason for the EC "ack" is so that software
|
||||
* can first invoke a SW mute before the HW circuit is cut off. Without SW
|
||||
* cutting this off first does not affect the time delayed muting or status
|
||||
* of the LED but there is a possibility of a "popping" noise.
|
||||
*
|
||||
* If the EC receives the SW ack, the circuit will be activated before the
|
||||
* delay completed.
|
||||
*
|
||||
* Exposing as an LED device allows the codec drivers notification path to
|
||||
* EC ACK to work
|
||||
*/
|
||||
static int dell_privacy_leds_setup(struct device *dev)
|
||||
{
|
||||
struct privacy_wmi_data *priv = dev_get_drvdata(dev);
|
||||
|
||||
priv->cdev.name = "dell-privacy::micmute";
|
||||
priv->cdev.max_brightness = 1;
|
||||
priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set;
|
||||
priv->cdev.default_trigger = "audio-micmute";
|
||||
priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
|
||||
return devm_led_classdev_register(dev, &priv->cdev);
|
||||
}
|
||||
|
||||
static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
struct privacy_wmi_data *priv;
|
||||
struct key_entry *keymap;
|
||||
int ret, i;
|
||||
|
||||
ret = wmi_has_guid(DELL_PRIVACY_GUID);
|
||||
if (!ret)
|
||||
pr_debug("Unable to detect available Dell privacy devices!\n");
|
||||
|
||||
priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
dev_set_drvdata(&wdev->dev, priv);
|
||||
priv->wdev = wdev;
|
||||
/* create evdev passing interface */
|
||||
priv->input_dev = devm_input_allocate_device(&wdev->dev);
|
||||
if (!priv->input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
/* remap the wmi keymap event to new keymap */
|
||||
keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012),
|
||||
sizeof(struct key_entry), GFP_KERNEL);
|
||||
if (!keymap)
|
||||
return -ENOMEM;
|
||||
|
||||
/* remap the keymap code with Dell privacy key type 0x12 as prefix
|
||||
* KEY_MICMUTE scancode will be reported as 0x120001
|
||||
*/
|
||||
for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) {
|
||||
keymap[i] = dell_wmi_keymap_type_0012[i];
|
||||
keymap[i].code |= (0x0012 << 16);
|
||||
}
|
||||
ret = sparse_keymap_setup(priv->input_dev, keymap, NULL);
|
||||
kfree(keymap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
priv->input_dev->dev.parent = &wdev->dev;
|
||||
priv->input_dev->name = "Dell Privacy Driver";
|
||||
priv->input_dev->id.bustype = BUS_HOST;
|
||||
|
||||
ret = input_register_device(priv->input_dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = get_current_status(priv->wdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_device_add_group(&wdev->dev, &privacy_attribute_group);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) {
|
||||
ret = dell_privacy_leds_setup(&priv->wdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
mutex_lock(&list_mutex);
|
||||
list_add_tail(&priv->list, &wmi_list);
|
||||
mutex_unlock(&list_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dell_privacy_wmi_remove(struct wmi_device *wdev)
|
||||
{
|
||||
struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev);
|
||||
|
||||
mutex_lock(&list_mutex);
|
||||
list_del(&priv->list);
|
||||
mutex_unlock(&list_mutex);
|
||||
}
|
||||
|
||||
static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = {
|
||||
{ .guid_string = DELL_PRIVACY_GUID },
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct wmi_driver dell_privacy_wmi_driver = {
|
||||
.driver = {
|
||||
.name = "dell-privacy",
|
||||
},
|
||||
.probe = dell_privacy_wmi_probe,
|
||||
.remove = dell_privacy_wmi_remove,
|
||||
.id_table = dell_wmi_privacy_wmi_id_table,
|
||||
};
|
||||
|
||||
int dell_privacy_register_driver(void)
|
||||
{
|
||||
return wmi_driver_register(&dell_privacy_wmi_driver);
|
||||
}
|
||||
|
||||
void dell_privacy_unregister_driver(void)
|
||||
{
|
||||
wmi_driver_unregister(&dell_privacy_wmi_driver);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Dell privacy notification driver
|
||||
*
|
||||
* Copyright (C) 2021 Dell Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef _DELL_PRIVACY_WMI_H_
|
||||
#define _DELL_PRIVACY_WMI_H_
|
||||
|
||||
#if IS_ENABLED(CONFIG_DELL_WMI_PRIVACY)
|
||||
bool dell_privacy_has_mic_mute(void);
|
||||
bool dell_privacy_process_event(int type, int code, int status);
|
||||
int dell_privacy_register_driver(void);
|
||||
void dell_privacy_unregister_driver(void);
|
||||
#else /* CONFIG_DELL_PRIVACY */
|
||||
static inline bool dell_privacy_has_mic_mute(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool dell_privacy_process_event(int type, int code, int status)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int dell_privacy_register_driver(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dell_privacy_unregister_driver(void)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_DELL_PRIVACY */
|
||||
#endif
|
|
@ -152,12 +152,15 @@ static ssize_t curr_val##_store(struct kobject *kobj, \
|
|||
return ret ? ret : count; \
|
||||
}
|
||||
|
||||
#define check_property_type(attr, prop, valuetype) \
|
||||
(attr##_obj[prop].type != valuetype)
|
||||
|
||||
union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string);
|
||||
int get_instance_count(const char *guid_string);
|
||||
void strlcpy_attr(char *dest, char *src);
|
||||
|
||||
int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj);
|
||||
struct kobject *attr_name_kobj, u32 enum_property_count);
|
||||
int alloc_enum_data(void);
|
||||
void exit_enum_attributes(void);
|
||||
|
||||
|
|
|
@ -132,39 +132,68 @@ int alloc_enum_data(void)
|
|||
* @enumeration_obj: ACPI object with enumeration data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
* @enum_property_count: Total properties count under enumeration type
|
||||
*/
|
||||
int populate_enum_data(union acpi_object *enumeration_obj, int instance_id,
|
||||
struct kobject *attr_name_kobj)
|
||||
struct kobject *attr_name_kobj, u32 enum_property_count)
|
||||
{
|
||||
int i, next_obj, value_modifier_count, possible_values_count;
|
||||
|
||||
wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
if (check_property_type(enumeration, ATTR_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name,
|
||||
enumeration_obj[ATTR_NAME].string.pointer);
|
||||
if (check_property_type(enumeration, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code,
|
||||
enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
if (check_property_type(enumeration, DISPLAY_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name,
|
||||
enumeration_obj[DISPLAY_NAME].string.pointer);
|
||||
if (check_property_type(enumeration, DEFAULT_VAL, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value,
|
||||
enumeration_obj[DEFAULT_VAL].string.pointer);
|
||||
if (check_property_type(enumeration, MODIFIER, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier,
|
||||
enumeration_obj[MODIFIER].string.pointer);
|
||||
|
||||
next_obj = MODIFIER + 1;
|
||||
|
||||
value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer;
|
||||
if (next_obj >= enum_property_count)
|
||||
return -EINVAL;
|
||||
|
||||
if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
value_modifier_count = (uintptr_t)enumeration_obj[next_obj++].string.pointer;
|
||||
|
||||
for (i = 0; i < value_modifier_count; i++) {
|
||||
if (next_obj >= enum_property_count)
|
||||
return -EINVAL;
|
||||
if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier,
|
||||
enumeration_obj[++next_obj].string.pointer);
|
||||
enumeration_obj[next_obj++].string.pointer);
|
||||
strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";");
|
||||
}
|
||||
|
||||
possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer;
|
||||
if (next_obj >= enum_property_count)
|
||||
return -EINVAL;
|
||||
|
||||
if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
possible_values_count = (uintptr_t) enumeration_obj[next_obj++].string.pointer;
|
||||
|
||||
for (i = 0; i < possible_values_count; i++) {
|
||||
if (next_obj >= enum_property_count)
|
||||
return -EINVAL;
|
||||
if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strcat(wmi_priv.enumeration_data[instance_id].possible_values,
|
||||
enumeration_obj[++next_obj].string.pointer);
|
||||
enumeration_obj[next_obj++].string.pointer);
|
||||
strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";");
|
||||
}
|
||||
|
||||
|
|
|
@ -141,20 +141,36 @@ int populate_int_data(union acpi_object *integer_obj, int instance_id,
|
|||
struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
if (check_property_type(integer, ATTR_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name,
|
||||
integer_obj[ATTR_NAME].string.pointer);
|
||||
if (check_property_type(integer, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code,
|
||||
integer_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
if (check_property_type(integer, DISPLAY_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].display_name,
|
||||
integer_obj[DISPLAY_NAME].string.pointer);
|
||||
if (check_property_type(integer, DEFAULT_VAL, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.integer_data[instance_id].default_value =
|
||||
(uintptr_t)integer_obj[DEFAULT_VAL].string.pointer;
|
||||
if (check_property_type(integer, MODIFIER, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier,
|
||||
integer_obj[MODIFIER].string.pointer);
|
||||
if (check_property_type(integer, MIN_VALUE, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.integer_data[instance_id].min_value =
|
||||
(uintptr_t)integer_obj[MIN_VALUE].string.pointer;
|
||||
if (check_property_type(integer, MAX_VALUE, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.integer_data[instance_id].max_value =
|
||||
(uintptr_t)integer_obj[MAX_VALUE].string.pointer;
|
||||
if (check_property_type(integer, SCALAR_INCR, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.integer_data[instance_id].scalar_increment =
|
||||
(uintptr_t)integer_obj[SCALAR_INCR].string.pointer;
|
||||
|
||||
|
|
|
@ -159,10 +159,16 @@ int alloc_po_data(void)
|
|||
int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
if (check_property_type(po, ATTR_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name,
|
||||
po_obj[ATTR_NAME].string.pointer);
|
||||
if (check_property_type(po, MIN_PASS_LEN, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.po_data[instance_id].min_password_length =
|
||||
(uintptr_t)po_obj[MIN_PASS_LEN].string.pointer;
|
||||
if (check_property_type(po, MAX_PASS_LEN, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.po_data[instance_id].max_password_length =
|
||||
(uintptr_t) po_obj[MAX_PASS_LEN].string.pointer;
|
||||
|
||||
|
|
|
@ -95,9 +95,9 @@ int set_new_password(const char *password_type, const char *new)
|
|||
|
||||
print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size);
|
||||
ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size);
|
||||
/* clear current_password here and use user input from wmi_priv.current_password */
|
||||
/* on success copy the new password to current password */
|
||||
if (!ret)
|
||||
memset(current_password, 0, MAX_BUFF);
|
||||
strscpy(current_password, new, MAX_BUFF);
|
||||
/* explain to user the detailed failure reason */
|
||||
else if (ret == -EOPNOTSUPP)
|
||||
dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n");
|
||||
|
|
|
@ -118,24 +118,38 @@ int alloc_str_data(void)
|
|||
|
||||
/**
|
||||
* populate_str_data() - Populate all properties of an instance under string attribute
|
||||
* @str_obj: ACPI object with integer data
|
||||
* @str_obj: ACPI object with string data
|
||||
* @instance_id: The instance to enumerate
|
||||
* @attr_name_kobj: The parent kernel object
|
||||
*/
|
||||
int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj)
|
||||
{
|
||||
wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj;
|
||||
if (check_property_type(str, ATTR_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name,
|
||||
str_obj[ATTR_NAME].string.pointer);
|
||||
if (check_property_type(str, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code,
|
||||
str_obj[DISPL_NAME_LANG_CODE].string.pointer);
|
||||
if (check_property_type(str, DISPLAY_NAME, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].display_name,
|
||||
str_obj[DISPLAY_NAME].string.pointer);
|
||||
if (check_property_type(str, DEFAULT_VAL, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].default_value,
|
||||
str_obj[DEFAULT_VAL].string.pointer);
|
||||
if (check_property_type(str, MODIFIER, ACPI_TYPE_STRING))
|
||||
return -EINVAL;
|
||||
strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier,
|
||||
str_obj[MODIFIER].string.pointer);
|
||||
if (check_property_type(str, MIN_LEN, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer;
|
||||
if (check_property_type(str, MAX_LEN, ACPI_TYPE_INTEGER))
|
||||
return -EINVAL;
|
||||
wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer;
|
||||
|
||||
return sysfs_create_group(attr_name_kobj, &str_attr_group);
|
||||
|
|
|
@ -13,14 +13,11 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/wmi.h>
|
||||
#include "dell-wmi-sysman.h"
|
||||
#include "../../firmware_attributes_class.h"
|
||||
|
||||
#define MAX_TYPES 4
|
||||
#include <linux/nls.h>
|
||||
|
||||
static struct class firmware_attributes_class = {
|
||||
.name = "firmware-attributes",
|
||||
};
|
||||
|
||||
struct wmi_sysman_priv wmi_priv = {
|
||||
.mutex = __MUTEX_INITIALIZER(wmi_priv.mutex),
|
||||
};
|
||||
|
@ -28,6 +25,7 @@ struct wmi_sysman_priv wmi_priv = {
|
|||
/* reset bios to defaults */
|
||||
static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"};
|
||||
static int reset_option = -1;
|
||||
static struct class *fw_attr_class;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -481,7 +479,8 @@ static int init_bios_attributes(int attr_type, const char *guid)
|
|||
/* enumerate all of this attribute */
|
||||
switch (attr_type) {
|
||||
case ENUM:
|
||||
retval = populate_enum_data(elements, instance_id, attr_name_kobj);
|
||||
retval = populate_enum_data(elements, instance_id, attr_name_kobj,
|
||||
obj->package.count);
|
||||
break;
|
||||
case INT:
|
||||
retval = populate_int_data(elements, instance_id, attr_name_kobj);
|
||||
|
@ -541,11 +540,11 @@ static int __init sysman_init(void)
|
|||
goto err_exit_bios_attr_pass_interface;
|
||||
}
|
||||
|
||||
ret = class_register(&firmware_attributes_class);
|
||||
ret = fw_attributes_class_get(&fw_attr_class);
|
||||
if (ret)
|
||||
goto err_exit_bios_attr_pass_interface;
|
||||
|
||||
wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0),
|
||||
wmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
|
||||
NULL, "%s", DRIVER_NAME);
|
||||
if (IS_ERR(wmi_priv.class_dev)) {
|
||||
ret = PTR_ERR(wmi_priv.class_dev);
|
||||
|
@ -602,10 +601,10 @@ err_release_attributes_data:
|
|||
release_attributes_data();
|
||||
|
||||
err_destroy_classdev:
|
||||
device_destroy(&firmware_attributes_class, MKDEV(0, 0));
|
||||
device_destroy(fw_attr_class, MKDEV(0, 0));
|
||||
|
||||
err_unregister_class:
|
||||
class_unregister(&firmware_attributes_class);
|
||||
fw_attributes_class_put();
|
||||
|
||||
err_exit_bios_attr_pass_interface:
|
||||
exit_bios_attr_pass_interface();
|
||||
|
@ -619,8 +618,8 @@ err_exit_bios_attr_set_interface:
|
|||
static void __exit sysman_exit(void)
|
||||
{
|
||||
release_attributes_data();
|
||||
device_destroy(&firmware_attributes_class, MKDEV(0, 0));
|
||||
class_unregister(&firmware_attributes_class);
|
||||
device_destroy(fw_attr_class, MKDEV(0, 0));
|
||||
fw_attributes_class_put();
|
||||
exit_bios_attr_set_interface();
|
||||
exit_bios_attr_pass_interface();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
/* Firmware attributes class helper module */
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/device/class.h>
|
||||
#include <linux/module.h>
|
||||
#include "firmware_attributes_class.h"
|
||||
|
||||
static DEFINE_MUTEX(fw_attr_lock);
|
||||
static int fw_attr_inuse;
|
||||
|
||||
static struct class firmware_attributes_class = {
|
||||
.name = "firmware-attributes",
|
||||
};
|
||||
|
||||
int fw_attributes_class_get(struct class **fw_attr_class)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&fw_attr_lock);
|
||||
if (!fw_attr_inuse) { /*first time class is being used*/
|
||||
err = class_register(&firmware_attributes_class);
|
||||
if (err) {
|
||||
mutex_unlock(&fw_attr_lock);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
fw_attr_inuse++;
|
||||
*fw_attr_class = &firmware_attributes_class;
|
||||
mutex_unlock(&fw_attr_lock);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fw_attributes_class_get);
|
||||
|
||||
int fw_attributes_class_put(void)
|
||||
{
|
||||
mutex_lock(&fw_attr_lock);
|
||||
if (!fw_attr_inuse) {
|
||||
mutex_unlock(&fw_attr_lock);
|
||||
return -EINVAL;
|
||||
}
|
||||
fw_attr_inuse--;
|
||||
if (!fw_attr_inuse) /* No more consumers */
|
||||
class_unregister(&firmware_attributes_class);
|
||||
mutex_unlock(&fw_attr_lock);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fw_attributes_class_put);
|
||||
|
||||
MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -0,0 +1,11 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
/* Firmware attributes class helper module */
|
||||
|
||||
#ifndef FW_ATTR_CLASS_H
|
||||
#define FW_ATTR_CLASS_H
|
||||
|
||||
int fw_attributes_class_get(struct class **fw_attr_class);
|
||||
int fw_attributes_class_put(void);
|
||||
|
||||
#endif /* FW_ATTR_CLASS_H */
|
|
@ -462,7 +462,7 @@ static struct attribute *hdaps_attributes[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group hdaps_attribute_group = {
|
||||
static const struct attribute_group hdaps_attribute_group = {
|
||||
.attrs = hdaps_attributes,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Airplane mode button for HP & Xiaomi laptops
|
||||
*
|
||||
* Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <acpi/acpi_bus.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alex Hung");
|
||||
MODULE_ALIAS("acpi*:HPQ6001:*");
|
||||
MODULE_ALIAS("acpi*:WSTADEF:*");
|
||||
MODULE_ALIAS("acpi*:AMDI0051:*");
|
||||
|
||||
static struct input_dev *hpwl_input_dev;
|
||||
|
||||
static const struct acpi_device_id hpwl_ids[] = {
|
||||
{"HPQ6001", 0},
|
||||
{"WSTADEF", 0},
|
||||
{"AMDI0051", 0},
|
||||
{"", 0},
|
||||
};
|
||||
|
||||
static int hp_wireless_input_setup(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
hpwl_input_dev = input_allocate_device();
|
||||
if (!hpwl_input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
hpwl_input_dev->name = "HP Wireless hotkeys";
|
||||
hpwl_input_dev->phys = "hpq6001/input0";
|
||||
hpwl_input_dev->id.bustype = BUS_HOST;
|
||||
hpwl_input_dev->evbit[0] = BIT(EV_KEY);
|
||||
set_bit(KEY_RFKILL, hpwl_input_dev->keybit);
|
||||
|
||||
err = input_register_device(hpwl_input_dev);
|
||||
if (err)
|
||||
goto err_free_dev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_dev:
|
||||
input_free_device(hpwl_input_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void hp_wireless_input_destroy(void)
|
||||
{
|
||||
input_unregister_device(hpwl_input_dev);
|
||||
}
|
||||
|
||||
static void hpwl_notify(struct acpi_device *acpi_dev, u32 event)
|
||||
{
|
||||
if (event != 0x80) {
|
||||
pr_info("Received unknown event (0x%x)\n", event);
|
||||
return;
|
||||
}
|
||||
|
||||
input_report_key(hpwl_input_dev, KEY_RFKILL, 1);
|
||||
input_sync(hpwl_input_dev);
|
||||
input_report_key(hpwl_input_dev, KEY_RFKILL, 0);
|
||||
input_sync(hpwl_input_dev);
|
||||
}
|
||||
|
||||
static int hpwl_add(struct acpi_device *device)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = hp_wireless_input_setup();
|
||||
if (err)
|
||||
pr_err("Failed to setup hp wireless hotkeys\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int hpwl_remove(struct acpi_device *device)
|
||||
{
|
||||
hp_wireless_input_destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_driver hpwl_driver = {
|
||||
.name = "hp-wireless",
|
||||
.owner = THIS_MODULE,
|
||||
.ids = hpwl_ids,
|
||||
.ops = {
|
||||
.add = hpwl_add,
|
||||
.remove = hpwl_remove,
|
||||
.notify = hpwl_notify,
|
||||
},
|
||||
};
|
||||
|
||||
module_acpi_driver(hpwl_driver);
|
|
@ -1408,6 +1408,18 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
|
|||
case 6:
|
||||
ideapad_input_report(priv, bit);
|
||||
break;
|
||||
case 10:
|
||||
/*
|
||||
* This event gets send on a Yoga 300-11IBR when the EC
|
||||
* believes that the device has changed between laptop/
|
||||
* tent/stand/tablet mode. The EC relies on getting
|
||||
* angle info from 2 accelerometers through a special
|
||||
* windows service calling a DSM on the DUAL250E ACPI-
|
||||
* device. Linux does not do this, making the laptop/
|
||||
* tent/stand/tablet mode info unreliable, so we simply
|
||||
* ignore these events.
|
||||
*/
|
||||
break;
|
||||
case 9:
|
||||
ideapad_sync_rfk_state(priv);
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
#
|
||||
# Intel x86 Platform Specific Drivers
|
||||
#
|
||||
|
||||
menuconfig X86_PLATFORM_DRIVERS_INTEL
|
||||
bool "Intel x86 Platform Specific Device Drivers"
|
||||
default y
|
||||
help
|
||||
Say Y here to get to see options for device drivers for
|
||||
various Intel x86 platforms, including vendor-specific
|
||||
drivers. This option alone does not add any kernel code.
|
||||
|
||||
If you say N, all options in this submenu will be skipped
|
||||
and disabled.
|
||||
|
||||
if X86_PLATFORM_DRIVERS_INTEL
|
||||
|
||||
source "drivers/platform/x86/intel/int33fe/Kconfig"
|
||||
source "drivers/platform/x86/intel/int3472/Kconfig"
|
||||
|
||||
endif # X86_PLATFORM_DRIVERS_INTEL
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for drivers/platform/x86/intel
|
||||
# Intel x86 Platform-Specific Drivers
|
||||
#
|
||||
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/
|
||||
obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/
|
|
@ -0,0 +1,24 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config INTEL_CHT_INT33FE
|
||||
tristate "Intel Cherry Trail ACPI INT33FE Driver"
|
||||
depends on X86 && ACPI && I2C && REGULATOR
|
||||
depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m)
|
||||
depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m)
|
||||
depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m)
|
||||
help
|
||||
This driver add support for the INT33FE ACPI device found on
|
||||
some Intel Cherry Trail devices.
|
||||
|
||||
There are two kinds of INT33FE ACPI device possible: for hardware
|
||||
with USB Type-C and Micro-B connectors. This driver supports both.
|
||||
|
||||
The INT33FE ACPI device has a CRS table with I2cSerialBusV2
|
||||
resources for Fuel Gauge Controller and (in the Type-C variant)
|
||||
FUSB302 USB Type-C Controller and PI3USB30532 USB switch.
|
||||
This driver instantiates i2c-clients for these, so that standard
|
||||
i2c drivers for these chips can bind to the them.
|
||||
|
||||
If you enable this driver it is advised to also select
|
||||
CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B
|
||||
device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
|
||||
for Type-C device.
|
|
@ -0,0 +1,5 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o
|
||||
intel_cht_int33fe-objs := intel_cht_int33fe_common.o \
|
||||
intel_cht_int33fe_typec.o \
|
||||
intel_cht_int33fe_microb.o
|
|
@ -168,8 +168,8 @@ static int cht_int33fe_setup_dp(struct cht_int33fe_data *data)
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Then the DP child device node */
|
||||
data->dp = device_get_named_child_node(&pdev->dev, "DD02");
|
||||
/* Then the DP-2 child device node */
|
||||
data->dp = device_get_named_child_node(&pdev->dev, "DD04");
|
||||
pci_dev_put(pdev);
|
||||
if (!data->dp)
|
||||
return -ENODEV;
|
|
@ -0,0 +1,30 @@
|
|||
config INTEL_SKL_INT3472
|
||||
tristate "Intel SkyLake ACPI INT3472 Driver"
|
||||
depends on ACPI
|
||||
depends on COMMON_CLK
|
||||
depends on I2C
|
||||
depends on GPIOLIB
|
||||
depends on REGULATOR
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This driver adds power controller support for the Intel SkyCam
|
||||
devices found on the Intel SkyLake platforms.
|
||||
|
||||
The INT3472 is a camera power controller, a logical device found on
|
||||
Intel Skylake-based systems that can map to different hardware
|
||||
devices depending on the platform. On machines designed for Chrome OS
|
||||
it maps to a TPS68470 camera PMIC. On machines designed for Windows,
|
||||
it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a
|
||||
set of discrete GPIOs and power gates.
|
||||
|
||||
If your device was designed for Chrome OS, this driver will provide
|
||||
an ACPI OpRegion, which must be available before any of the devices
|
||||
using it are probed. For this reason, you should select Y if your
|
||||
device was designed for ChromeOS. For the same reason the
|
||||
I2C_DESIGNWARE_PLATFORM option must be set to Y too.
|
||||
|
||||
Say Y or M here if you have a SkyLake device designed for use
|
||||
with Windows or ChromeOS. Say N here if you are not sure.
|
||||
|
||||
The module will be named "intel-skl-int3472".
|
|
@ -0,0 +1,5 @@
|
|||
obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o
|
||||
intel_skl_int3472-objs := intel_skl_int3472_common.o \
|
||||
intel_skl_int3472_discrete.o \
|
||||
intel_skl_int3472_tps68470.o \
|
||||
intel_skl_int3472_clk_and_regulator.o
|
|
@ -0,0 +1,207 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Author: Dan Scally <djrscally@gmail.com> */
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "intel_skl_int3472_common.h"
|
||||
|
||||
/*
|
||||
* The regulators have to have .ops to be valid, but the only ops we actually
|
||||
* support are .enable and .disable which are handled via .ena_gpiod. Pass an
|
||||
* empty struct to clear the check without lying about capabilities.
|
||||
*/
|
||||
static const struct regulator_ops int3472_gpio_regulator_ops;
|
||||
|
||||
static int skl_int3472_clk_prepare(struct clk_hw *hw)
|
||||
{
|
||||
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
|
||||
|
||||
gpiod_set_value_cansleep(clk->ena_gpio, 1);
|
||||
gpiod_set_value_cansleep(clk->led_gpio, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void skl_int3472_clk_unprepare(struct clk_hw *hw)
|
||||
{
|
||||
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
|
||||
|
||||
gpiod_set_value_cansleep(clk->ena_gpio, 0);
|
||||
gpiod_set_value_cansleep(clk->led_gpio, 0);
|
||||
}
|
||||
|
||||
static int skl_int3472_clk_enable(struct clk_hw *hw)
|
||||
{
|
||||
/*
|
||||
* We're just turning a GPIO on to enable the clock, which operation
|
||||
* has the potential to sleep. Given .enable() cannot sleep, but
|
||||
* .prepare() can, we toggle the GPIO in .prepare() instead. Thus,
|
||||
* nothing to do here.
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void skl_int3472_clk_disable(struct clk_hw *hw)
|
||||
{
|
||||
/* Likewise, nothing to do here... */
|
||||
}
|
||||
|
||||
static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
unsigned int freq;
|
||||
|
||||
obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB");
|
||||
if (IS_ERR(obj))
|
||||
return 0; /* report rate as 0 on error */
|
||||
|
||||
if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) {
|
||||
dev_err(int3472->dev, "The buffer is too small\n");
|
||||
kfree(obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET);
|
||||
|
||||
kfree(obj);
|
||||
return freq;
|
||||
}
|
||||
|
||||
static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct int3472_gpio_clock *clk = to_int3472_clk(hw);
|
||||
|
||||
return clk->frequency;
|
||||
}
|
||||
|
||||
static const struct clk_ops skl_int3472_clock_ops = {
|
||||
.prepare = skl_int3472_clk_prepare,
|
||||
.unprepare = skl_int3472_clk_unprepare,
|
||||
.enable = skl_int3472_clk_enable,
|
||||
.disable = skl_int3472_clk_disable,
|
||||
.recalc_rate = skl_int3472_clk_recalc_rate,
|
||||
};
|
||||
|
||||
int skl_int3472_register_clock(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
struct clk_init_data init = {
|
||||
.ops = &skl_int3472_clock_ops,
|
||||
.flags = CLK_GET_RATE_NOCACHE,
|
||||
};
|
||||
int ret;
|
||||
|
||||
init.name = kasprintf(GFP_KERNEL, "%s-clk",
|
||||
acpi_dev_name(int3472->adev));
|
||||
if (!init.name)
|
||||
return -ENOMEM;
|
||||
|
||||
int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472);
|
||||
|
||||
int3472->clock.clk_hw.init = &init;
|
||||
int3472->clock.clk = clk_register(&int3472->adev->dev,
|
||||
&int3472->clock.clk_hw);
|
||||
if (IS_ERR(int3472->clock.clk)) {
|
||||
ret = PTR_ERR(int3472->clock.clk);
|
||||
goto out_free_init_name;
|
||||
}
|
||||
|
||||
int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL,
|
||||
int3472->sensor_name);
|
||||
if (!int3472->clock.cl) {
|
||||
ret = -ENOMEM;
|
||||
goto err_unregister_clk;
|
||||
}
|
||||
|
||||
kfree(init.name);
|
||||
return 0;
|
||||
|
||||
err_unregister_clk:
|
||||
clk_unregister(int3472->clock.clk);
|
||||
out_free_init_name:
|
||||
kfree(init.name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
clkdev_drop(int3472->clock.cl);
|
||||
clk_unregister(int3472->clock.clk);
|
||||
}
|
||||
|
||||
int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
|
||||
struct acpi_resource_gpio *agpio)
|
||||
{
|
||||
const struct int3472_sensor_config *sensor_config;
|
||||
char *path = agpio->resource_source.string_ptr;
|
||||
struct regulator_consumer_supply supply_map;
|
||||
struct regulator_init_data init_data = { };
|
||||
struct regulator_config cfg = { };
|
||||
int ret;
|
||||
|
||||
sensor_config = int3472->sensor_config;
|
||||
if (IS_ERR(sensor_config)) {
|
||||
dev_err(int3472->dev, "No sensor module config\n");
|
||||
return PTR_ERR(sensor_config);
|
||||
}
|
||||
|
||||
if (!sensor_config->supply_map.supply) {
|
||||
dev_err(int3472->dev, "No supply name defined\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
|
||||
init_data.num_consumer_supplies = 1;
|
||||
supply_map = sensor_config->supply_map;
|
||||
supply_map.dev_name = int3472->sensor_name;
|
||||
init_data.consumer_supplies = &supply_map;
|
||||
|
||||
snprintf(int3472->regulator.regulator_name,
|
||||
sizeof(int3472->regulator.regulator_name), "%s-regulator",
|
||||
acpi_dev_name(int3472->adev));
|
||||
snprintf(int3472->regulator.supply_name,
|
||||
GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0");
|
||||
|
||||
int3472->regulator.rdesc = INT3472_REGULATOR(
|
||||
int3472->regulator.regulator_name,
|
||||
int3472->regulator.supply_name,
|
||||
&int3472_gpio_regulator_ops);
|
||||
|
||||
int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0],
|
||||
"int3472,regulator");
|
||||
if (IS_ERR(int3472->regulator.gpio)) {
|
||||
dev_err(int3472->dev, "Failed to get regulator GPIO line\n");
|
||||
return PTR_ERR(int3472->regulator.gpio);
|
||||
}
|
||||
|
||||
cfg.dev = &int3472->adev->dev;
|
||||
cfg.init_data = &init_data;
|
||||
cfg.ena_gpiod = int3472->regulator.gpio;
|
||||
|
||||
int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc,
|
||||
&cfg);
|
||||
if (IS_ERR(int3472->regulator.rdev)) {
|
||||
ret = PTR_ERR(int3472->regulator.rdev);
|
||||
goto err_free_gpio;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_gpio:
|
||||
gpiod_put(int3472->regulator.gpio);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
regulator_unregister(int3472->regulator.rdev);
|
||||
gpiod_put(int3472->regulator.gpio);
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Author: Dan Scally <djrscally@gmail.com> */
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "intel_skl_int3472_common.h"
|
||||
|
||||
union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id)
|
||||
{
|
||||
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_handle handle = adev->handle;
|
||||
union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
status = acpi_evaluate_object(handle, id, NULL, &buffer);
|
||||
if (ACPI_FAILURE(status))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
obj = buffer.pointer;
|
||||
if (!obj)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id);
|
||||
kfree(obj);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
int ret;
|
||||
|
||||
obj = skl_int3472_get_acpi_buffer(adev, "CLDB");
|
||||
if (IS_ERR(obj))
|
||||
return PTR_ERR(obj);
|
||||
|
||||
if (obj->buffer.length > sizeof(*cldb)) {
|
||||
acpi_handle_err(adev->handle, "The CLDB buffer is too large\n");
|
||||
ret = -EINVAL;
|
||||
goto out_free_obj;
|
||||
}
|
||||
|
||||
memcpy(cldb, obj->buffer.pointer, obj->buffer.length);
|
||||
ret = 0;
|
||||
|
||||
out_free_obj:
|
||||
kfree(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct acpi_device_id int3472_device_id[] = {
|
||||
{ "INT3472", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, int3472_device_id);
|
||||
|
||||
static struct platform_driver int3472_discrete = {
|
||||
.driver = {
|
||||
.name = "int3472-discrete",
|
||||
.acpi_match_table = int3472_device_id,
|
||||
},
|
||||
.probe = skl_int3472_discrete_probe,
|
||||
.remove = skl_int3472_discrete_remove,
|
||||
};
|
||||
|
||||
static struct i2c_driver int3472_tps68470 = {
|
||||
.driver = {
|
||||
.name = "int3472-tps68470",
|
||||
.acpi_match_table = int3472_device_id,
|
||||
},
|
||||
.probe_new = skl_int3472_tps68470_probe,
|
||||
};
|
||||
|
||||
static int skl_int3472_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = platform_driver_register(&int3472_discrete);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470);
|
||||
if (ret)
|
||||
platform_driver_unregister(&int3472_discrete);
|
||||
|
||||
return ret;
|
||||
}
|
||||
module_init(skl_int3472_init);
|
||||
|
||||
static void skl_int3472_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&int3472_discrete);
|
||||
i2c_del_driver(&int3472_tps68470);
|
||||
}
|
||||
module_exit(skl_int3472_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver");
|
||||
MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,122 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Author: Dan Scally <djrscally@gmail.com> */
|
||||
|
||||
#ifndef _INTEL_SKL_INT3472_H
|
||||
#define _INTEL_SKL_INT3472_H
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */
|
||||
#ifndef I2C_DEV_NAME_FORMAT
|
||||
#define I2C_DEV_NAME_FORMAT "i2c-%s"
|
||||
#endif
|
||||
|
||||
/* PMIC GPIO Types */
|
||||
#define INT3472_GPIO_TYPE_RESET 0x00
|
||||
#define INT3472_GPIO_TYPE_POWERDOWN 0x01
|
||||
#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b
|
||||
#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c
|
||||
#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d
|
||||
|
||||
#define INT3472_PDEV_MAX_NAME_LEN 23
|
||||
#define INT3472_MAX_SENSOR_GPIOS 3
|
||||
|
||||
#define GPIO_REGULATOR_NAME_LENGTH 21
|
||||
#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9
|
||||
|
||||
#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86
|
||||
|
||||
#define INT3472_REGULATOR(_name, _supply, _ops) \
|
||||
(const struct regulator_desc) { \
|
||||
.name = _name, \
|
||||
.supply_name = _supply, \
|
||||
.type = REGULATOR_VOLTAGE, \
|
||||
.ops = _ops, \
|
||||
.owner = THIS_MODULE, \
|
||||
}
|
||||
|
||||
#define to_int3472_clk(hw) \
|
||||
container_of(hw, struct int3472_gpio_clock, clk_hw)
|
||||
|
||||
#define to_int3472_device(clk) \
|
||||
container_of(clk, struct int3472_discrete_device, clock)
|
||||
|
||||
struct acpi_device;
|
||||
struct i2c_client;
|
||||
struct platform_device;
|
||||
|
||||
struct int3472_cldb {
|
||||
u8 version;
|
||||
/*
|
||||
* control logic type
|
||||
* 0: UNKNOWN
|
||||
* 1: DISCRETE(CRD-D)
|
||||
* 2: PMIC TPS68470
|
||||
* 3: PMIC uP6641
|
||||
*/
|
||||
u8 control_logic_type;
|
||||
u8 control_logic_id;
|
||||
u8 sensor_card_sku;
|
||||
u8 reserved[28];
|
||||
};
|
||||
|
||||
struct int3472_gpio_function_remap {
|
||||
const char *documented;
|
||||
const char *actual;
|
||||
};
|
||||
|
||||
struct int3472_sensor_config {
|
||||
const char *sensor_module_name;
|
||||
struct regulator_consumer_supply supply_map;
|
||||
const struct int3472_gpio_function_remap *function_maps;
|
||||
};
|
||||
|
||||
struct int3472_discrete_device {
|
||||
struct acpi_device *adev;
|
||||
struct device *dev;
|
||||
struct acpi_device *sensor;
|
||||
const char *sensor_name;
|
||||
|
||||
const struct int3472_sensor_config *sensor_config;
|
||||
|
||||
struct int3472_gpio_regulator {
|
||||
char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
|
||||
char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH];
|
||||
struct gpio_desc *gpio;
|
||||
struct regulator_dev *rdev;
|
||||
struct regulator_desc rdesc;
|
||||
} regulator;
|
||||
|
||||
struct int3472_gpio_clock {
|
||||
struct clk *clk;
|
||||
struct clk_hw clk_hw;
|
||||
struct clk_lookup *cl;
|
||||
struct gpio_desc *ena_gpio;
|
||||
struct gpio_desc *led_gpio;
|
||||
u32 frequency;
|
||||
} clock;
|
||||
|
||||
unsigned int ngpios; /* how many GPIOs have we seen */
|
||||
unsigned int n_sensor_gpios; /* how many have we mapped to sensor */
|
||||
struct gpiod_lookup_table gpios;
|
||||
};
|
||||
|
||||
int skl_int3472_discrete_probe(struct platform_device *pdev);
|
||||
int skl_int3472_discrete_remove(struct platform_device *pdev);
|
||||
int skl_int3472_tps68470_probe(struct i2c_client *client);
|
||||
union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev,
|
||||
char *id);
|
||||
int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb);
|
||||
|
||||
int skl_int3472_register_clock(struct int3472_discrete_device *int3472);
|
||||
void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472);
|
||||
|
||||
int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
|
||||
struct acpi_resource_gpio *agpio);
|
||||
void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,413 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Author: Dan Scally <djrscally@gmail.com> */
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/clkdev.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio/machine.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/overflow.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/uuid.h>
|
||||
|
||||
#include "intel_skl_int3472_common.h"
|
||||
|
||||
/*
|
||||
* 79234640-9e10-4fea-a5c1-b5aa8b19756f
|
||||
* This _DSM GUID returns information about the GPIO lines mapped to a
|
||||
* discrete INT3472 device. Function number 1 returns a count of the GPIO
|
||||
* lines that are mapped. Subsequent functions return 32 bit ints encoding
|
||||
* information about the GPIO line, including its purpose.
|
||||
*/
|
||||
static const guid_t int3472_gpio_guid =
|
||||
GUID_INIT(0x79234640, 0x9e10, 0x4fea,
|
||||
0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f);
|
||||
|
||||
/*
|
||||
* 822ace8f-2814-4174-a56b-5f029fe079ee
|
||||
* This _DSM GUID returns a string from the sensor device, which acts as a
|
||||
* module identifier.
|
||||
*/
|
||||
static const guid_t cio2_sensor_module_guid =
|
||||
GUID_INIT(0x822ace8f, 0x2814, 0x4174,
|
||||
0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee);
|
||||
|
||||
/*
|
||||
* Here follows platform specific mapping information that we can pass to
|
||||
* the functions mapping resources to the sensors. Where the sensors have
|
||||
* a power enable pin defined in DSDT we need to provide a supply name so
|
||||
* the sensor drivers can find the regulator. The device name will be derived
|
||||
* from the sensor's ACPI device within the code. Optionally, we can provide a
|
||||
* NULL terminated array of function name mappings to deal with any platform
|
||||
* specific deviations from the documented behaviour of GPIOs.
|
||||
*
|
||||
* Map a GPIO function name to NULL to prevent the driver from mapping that
|
||||
* GPIO at all.
|
||||
*/
|
||||
|
||||
static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = {
|
||||
{ "reset", NULL },
|
||||
{ "powerdown", "reset" },
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct int3472_sensor_config int3472_sensor_configs[] = {
|
||||
/* Lenovo Miix 510-12ISK - OV2680, Front */
|
||||
{ "GNDF140809R", { 0 }, ov2680_gpio_function_remaps },
|
||||
/* Lenovo Miix 510-12ISK - OV5648, Rear */
|
||||
{ "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL },
|
||||
/* Surface Go 1&2 - OV5693, Front */
|
||||
{ "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL },
|
||||
};
|
||||
|
||||
static const struct int3472_sensor_config *
|
||||
skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
union acpi_object *obj;
|
||||
unsigned int i;
|
||||
|
||||
obj = acpi_evaluate_dsm_typed(int3472->sensor->handle,
|
||||
&cio2_sensor_module_guid, 0x00,
|
||||
0x01, NULL, ACPI_TYPE_STRING);
|
||||
|
||||
if (!obj) {
|
||||
dev_err(int3472->dev,
|
||||
"Failed to get sensor module string from _DSM\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
if (obj->string.type != ACPI_TYPE_STRING) {
|
||||
dev_err(int3472->dev,
|
||||
"Sensor _DSM returned a non-string value\n");
|
||||
|
||||
ACPI_FREE(obj);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) {
|
||||
if (!strcmp(int3472_sensor_configs[i].sensor_module_name,
|
||||
obj->string.pointer))
|
||||
break;
|
||||
}
|
||||
|
||||
ACPI_FREE(obj);
|
||||
|
||||
if (i >= ARRAY_SIZE(int3472_sensor_configs))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return &int3472_sensor_configs[i];
|
||||
}
|
||||
|
||||
static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472,
|
||||
struct acpi_resource_gpio *agpio,
|
||||
const char *func, u32 polarity)
|
||||
{
|
||||
const struct int3472_sensor_config *sensor_config;
|
||||
char *path = agpio->resource_source.string_ptr;
|
||||
struct gpiod_lookup *table_entry;
|
||||
struct acpi_device *adev;
|
||||
acpi_handle handle;
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) {
|
||||
dev_warn(int3472->dev, "Too many GPIOs mapped\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sensor_config = int3472->sensor_config;
|
||||
if (!IS_ERR(sensor_config) && sensor_config->function_maps) {
|
||||
const struct int3472_gpio_function_remap *remap;
|
||||
|
||||
for (remap = sensor_config->function_maps; remap->documented; remap++) {
|
||||
if (!strcmp(func, remap->documented)) {
|
||||
func = remap->actual;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Functions mapped to NULL should not be mapped to the sensor */
|
||||
if (!func)
|
||||
return 0;
|
||||
|
||||
status = acpi_get_handle(NULL, path, &handle);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EINVAL;
|
||||
|
||||
ret = acpi_bus_get_device(handle, &adev);
|
||||
if (ret)
|
||||
return -ENODEV;
|
||||
|
||||
table_entry = &int3472->gpios.table[int3472->n_sensor_gpios];
|
||||
table_entry->key = acpi_dev_name(adev);
|
||||
table_entry->chip_hwnum = agpio->pin_table[0];
|
||||
table_entry->con_id = func;
|
||||
table_entry->idx = 0;
|
||||
table_entry->flags = polarity;
|
||||
|
||||
int3472->n_sensor_gpios++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472,
|
||||
struct acpi_resource_gpio *agpio, u8 type)
|
||||
{
|
||||
char *path = agpio->resource_source.string_ptr;
|
||||
u16 pin = agpio->pin_table[0];
|
||||
struct gpio_desc *gpio;
|
||||
|
||||
switch (type) {
|
||||
case INT3472_GPIO_TYPE_CLK_ENABLE:
|
||||
gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable");
|
||||
if (IS_ERR(gpio))
|
||||
return (PTR_ERR(gpio));
|
||||
|
||||
int3472->clock.ena_gpio = gpio;
|
||||
break;
|
||||
case INT3472_GPIO_TYPE_PRIVACY_LED:
|
||||
gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led");
|
||||
if (IS_ERR(gpio))
|
||||
return (PTR_ERR(gpio));
|
||||
|
||||
int3472->clock.led_gpio = gpio;
|
||||
break;
|
||||
default:
|
||||
dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor
|
||||
* @ares: A pointer to a &struct acpi_resource
|
||||
* @data: A pointer to a &struct int3472_discrete_device
|
||||
*
|
||||
* This function handles GPIO resources that are against an INT3472
|
||||
* ACPI device, by checking the value of the corresponding _DSM entry.
|
||||
* This will return a 32bit int, where the lowest byte represents the
|
||||
* function of the GPIO pin:
|
||||
*
|
||||
* 0x00 Reset
|
||||
* 0x01 Power down
|
||||
* 0x0b Power enable
|
||||
* 0x0c Clock enable
|
||||
* 0x0d Privacy LED
|
||||
*
|
||||
* There are some known platform specific quirks where that does not quite
|
||||
* hold up; for example where a pin with type 0x01 (Power down) is mapped to
|
||||
* a sensor pin that performs a reset function or entries in _CRS and _DSM that
|
||||
* do not actually correspond to a physical connection. These will be handled
|
||||
* by the mapping sub-functions.
|
||||
*
|
||||
* GPIOs will either be mapped directly to the sensor device or else used
|
||||
* to create clocks and regulators via the usual frameworks.
|
||||
*
|
||||
* Return:
|
||||
* * 1 - To continue the loop
|
||||
* * 0 - When all resources found are handled properly.
|
||||
* * -EINVAL - If the resource is not a GPIO IO resource
|
||||
* * -ENODEV - If the resource has no corresponding _DSM entry
|
||||
* * -Other - Errors propagated from one of the sub-functions.
|
||||
*/
|
||||
static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
|
||||
void *data)
|
||||
{
|
||||
struct int3472_discrete_device *int3472 = data;
|
||||
struct acpi_resource_gpio *agpio;
|
||||
union acpi_object *obj;
|
||||
const char *err_msg;
|
||||
int ret;
|
||||
u8 type;
|
||||
|
||||
if (!acpi_gpio_get_io_resource(ares, &agpio))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* ngpios + 2 because the index of this _DSM function is 1-based and
|
||||
* the first function is just a count.
|
||||
*/
|
||||
obj = acpi_evaluate_dsm_typed(int3472->adev->handle,
|
||||
&int3472_gpio_guid, 0x00,
|
||||
int3472->ngpios + 2,
|
||||
NULL, ACPI_TYPE_INTEGER);
|
||||
|
||||
if (!obj) {
|
||||
dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n",
|
||||
agpio->pin_table[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
type = obj->integer.value & 0xff;
|
||||
|
||||
switch (type) {
|
||||
case INT3472_GPIO_TYPE_RESET:
|
||||
ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset",
|
||||
GPIO_ACTIVE_LOW);
|
||||
if (ret)
|
||||
err_msg = "Failed to map reset pin to sensor\n";
|
||||
|
||||
break;
|
||||
case INT3472_GPIO_TYPE_POWERDOWN:
|
||||
ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown",
|
||||
GPIO_ACTIVE_LOW);
|
||||
if (ret)
|
||||
err_msg = "Failed to map powerdown pin to sensor\n";
|
||||
|
||||
break;
|
||||
case INT3472_GPIO_TYPE_CLK_ENABLE:
|
||||
case INT3472_GPIO_TYPE_PRIVACY_LED:
|
||||
ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type);
|
||||
if (ret)
|
||||
err_msg = "Failed to map GPIO to clock\n";
|
||||
|
||||
break;
|
||||
case INT3472_GPIO_TYPE_POWER_ENABLE:
|
||||
ret = skl_int3472_register_regulator(int3472, agpio);
|
||||
if (ret)
|
||||
err_msg = "Failed to map regulator to sensor\n";
|
||||
|
||||
break;
|
||||
default:
|
||||
dev_warn(int3472->dev,
|
||||
"GPIO type 0x%02x unknown; the sensor may not work\n",
|
||||
type);
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
int3472->ngpios++;
|
||||
ACPI_FREE(obj);
|
||||
|
||||
if (ret < 0)
|
||||
return dev_err_probe(int3472->dev, ret, err_msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
|
||||
{
|
||||
LIST_HEAD(resource_list);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* No error check, because not having a sensor config is not necessarily
|
||||
* a failure mode.
|
||||
*/
|
||||
int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472);
|
||||
|
||||
ret = acpi_dev_get_resources(int3472->adev, &resource_list,
|
||||
skl_int3472_handle_gpio_resources,
|
||||
int3472);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
acpi_dev_free_resource_list(&resource_list);
|
||||
|
||||
/*
|
||||
* If we find no clock enable GPIO pin then the privacy LED won't work.
|
||||
* We've never seen that situation, but it's possible. Warn the user so
|
||||
* it's clear what's happened.
|
||||
*/
|
||||
if (int3472->clock.ena_gpio) {
|
||||
ret = skl_int3472_register_clock(int3472);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
if (int3472->clock.led_gpio)
|
||||
dev_warn(int3472->dev,
|
||||
"No clk GPIO. The privacy LED won't work\n");
|
||||
}
|
||||
|
||||
int3472->gpios.dev_id = int3472->sensor_name;
|
||||
gpiod_add_lookup_table(&int3472->gpios);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int skl_int3472_discrete_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
|
||||
struct int3472_discrete_device *int3472;
|
||||
struct int3472_cldb cldb;
|
||||
int ret;
|
||||
|
||||
ret = skl_int3472_fill_cldb(adev, &cldb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Couldn't fill CLDB structure\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cldb.control_logic_type != 1) {
|
||||
dev_err(&pdev->dev, "Unsupported control logic type %u\n",
|
||||
cldb.control_logic_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Max num GPIOs we've seen plus a terminator */
|
||||
int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table,
|
||||
INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL);
|
||||
if (!int3472)
|
||||
return -ENOMEM;
|
||||
|
||||
int3472->adev = adev;
|
||||
int3472->dev = &pdev->dev;
|
||||
platform_set_drvdata(pdev, int3472);
|
||||
|
||||
int3472->sensor = acpi_dev_get_first_consumer_dev(adev);
|
||||
if (!int3472->sensor) {
|
||||
dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL,
|
||||
I2C_DEV_NAME_FORMAT,
|
||||
acpi_dev_name(int3472->sensor));
|
||||
if (!int3472->sensor_name) {
|
||||
ret = -ENOMEM;
|
||||
goto err_put_sensor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialising this list means we can call gpiod_remove_lookup_table()
|
||||
* in failure paths without issue.
|
||||
*/
|
||||
INIT_LIST_HEAD(&int3472->gpios.list);
|
||||
|
||||
ret = skl_int3472_parse_crs(int3472);
|
||||
if (ret) {
|
||||
skl_int3472_discrete_remove(pdev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_put_sensor:
|
||||
acpi_dev_put(int3472->sensor);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int skl_int3472_discrete_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev);
|
||||
|
||||
gpiod_remove_lookup_table(&int3472->gpios);
|
||||
|
||||
if (int3472->clock.ena_gpio)
|
||||
skl_int3472_unregister_clock(int3472);
|
||||
|
||||
gpiod_put(int3472->clock.ena_gpio);
|
||||
gpiod_put(int3472->clock.led_gpio);
|
||||
|
||||
skl_int3472_unregister_regulator(int3472);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Author: Dan Scally <djrscally@gmail.com> */
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tps68470.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include "intel_skl_int3472_common.h"
|
||||
|
||||
#define DESIGNED_FOR_CHROMEOS 1
|
||||
#define DESIGNED_FOR_WINDOWS 2
|
||||
|
||||
static const struct mfd_cell tps68470_cros[] = {
|
||||
{ .name = "tps68470-gpio" },
|
||||
{ .name = "tps68470_pmic_opregion" },
|
||||
};
|
||||
|
||||
static const struct mfd_cell tps68470_win[] = {
|
||||
{ .name = "tps68470-gpio" },
|
||||
{ .name = "tps68470-clk" },
|
||||
{ .name = "tps68470-regulator" },
|
||||
};
|
||||
|
||||
static const struct regmap_config tps68470_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = TPS68470_REG_MAX,
|
||||
};
|
||||
|
||||
static int tps68470_chip_init(struct device *dev, struct regmap *regmap)
|
||||
{
|
||||
unsigned int version;
|
||||
int ret;
|
||||
|
||||
/* Force software reset */
|
||||
ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_read(regmap, TPS68470_REG_REVID, &version);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to read revision register: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "TPS68470 REVID: 0x%02x\n", version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for
|
||||
* @adev: A pointer to a &struct acpi_device
|
||||
*
|
||||
* Check CLDB buffer against the PMIC's adev. If present, then we check
|
||||
* the value of control_logic_type field and follow one of the
|
||||
* following scenarios:
|
||||
*
|
||||
* 1. No CLDB - likely ACPI tables designed for ChromeOS. We
|
||||
* create platform devices for the GPIOs and OpRegion drivers.
|
||||
*
|
||||
* 2. CLDB, with control_logic_type = 2 - probably ACPI tables
|
||||
* made for Windows 2-in-1 platforms. Register pdevs for GPIO,
|
||||
* Clock and Regulator drivers to bind to.
|
||||
*
|
||||
* 3. Any other value in control_logic_type, we should never have
|
||||
* gotten to this point; fail probe and return.
|
||||
*
|
||||
* Return:
|
||||
* * 1 Device intended for ChromeOS
|
||||
* * 2 Device intended for Windows
|
||||
* * -EINVAL Where @adev has an object named CLDB but it does not conform to
|
||||
* our expectations
|
||||
*/
|
||||
static int skl_int3472_tps68470_calc_type(struct acpi_device *adev)
|
||||
{
|
||||
struct int3472_cldb cldb = { 0 };
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* A CLDB buffer that exists, but which does not match our expectations
|
||||
* should trigger an error so we don't blindly continue.
|
||||
*/
|
||||
ret = skl_int3472_fill_cldb(adev, &cldb);
|
||||
if (ret && ret != -ENODEV)
|
||||
return ret;
|
||||
|
||||
if (ret)
|
||||
return DESIGNED_FOR_CHROMEOS;
|
||||
|
||||
if (cldb.control_logic_type != 2)
|
||||
return -EINVAL;
|
||||
|
||||
return DESIGNED_FOR_WINDOWS;
|
||||
}
|
||||
|
||||
int skl_int3472_tps68470_probe(struct i2c_client *client)
|
||||
{
|
||||
struct acpi_device *adev = ACPI_COMPANION(&client->dev);
|
||||
struct regmap *regmap;
|
||||
int device_type;
|
||||
int ret;
|
||||
|
||||
regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config);
|
||||
if (IS_ERR(regmap)) {
|
||||
dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap));
|
||||
return PTR_ERR(regmap);
|
||||
}
|
||||
|
||||
i2c_set_clientdata(client, regmap);
|
||||
|
||||
ret = tps68470_chip_init(&client->dev, regmap);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "TPS68470 init error %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_type = skl_int3472_tps68470_calc_type(adev);
|
||||
switch (device_type) {
|
||||
case DESIGNED_FOR_WINDOWS:
|
||||
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
|
||||
tps68470_win, ARRAY_SIZE(tps68470_win),
|
||||
NULL, 0, NULL);
|
||||
break;
|
||||
case DESIGNED_FOR_CHROMEOS:
|
||||
ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
|
||||
tps68470_cros, ARRAY_SIZE(tps68470_cros),
|
||||
NULL, 0, NULL);
|
||||
break;
|
||||
default:
|
||||
dev_err(&client->dev, "Failed to add MFD devices\n");
|
||||
return device_type;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -829,7 +829,7 @@ static u16 calc_avg_temp(struct ips_driver *ips, u16 *array)
|
|||
|
||||
static u16 read_mgtv(struct ips_driver *ips)
|
||||
{
|
||||
u16 ret;
|
||||
u16 __maybe_unused ret;
|
||||
u64 slope, offset;
|
||||
u64 val;
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@ static struct attribute *pmt_crashlog_attrs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group pmt_crashlog_group = {
|
||||
static const struct attribute_group pmt_crashlog_group = {
|
||||
.attrs = pmt_crashlog_attrs,
|
||||
};
|
||||
|
||||
|
|
|
@ -281,10 +281,69 @@ static int isst_if_get_platform_info(void __user *argp)
|
|||
struct isst_if_cpu_info {
|
||||
/* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
|
||||
int bus_info[2];
|
||||
struct pci_dev *pci_dev[2];
|
||||
int punit_cpu_id;
|
||||
int numa_node;
|
||||
};
|
||||
|
||||
static struct isst_if_cpu_info *isst_cpu_info;
|
||||
#define ISST_MAX_PCI_DOMAINS 8
|
||||
|
||||
static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
|
||||
{
|
||||
struct pci_dev *matched_pci_dev = NULL;
|
||||
struct pci_dev *pci_dev = NULL;
|
||||
int no_matches = 0;
|
||||
int i, bus_number;
|
||||
|
||||
if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
|
||||
cpu >= num_possible_cpus())
|
||||
return NULL;
|
||||
|
||||
bus_number = isst_cpu_info[cpu].bus_info[bus_no];
|
||||
if (bus_number < 0)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) {
|
||||
struct pci_dev *_pci_dev;
|
||||
int node;
|
||||
|
||||
_pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn));
|
||||
if (!_pci_dev)
|
||||
continue;
|
||||
|
||||
++no_matches;
|
||||
if (!matched_pci_dev)
|
||||
matched_pci_dev = _pci_dev;
|
||||
|
||||
node = dev_to_node(&_pci_dev->dev);
|
||||
if (node == NUMA_NO_NODE) {
|
||||
pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n",
|
||||
cpu, bus_no, dev, fn);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node == isst_cpu_info[cpu].numa_node) {
|
||||
pci_dev = _pci_dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If there is no numa matched pci_dev, then there can be following cases:
|
||||
* 1. CONFIG_NUMA is not defined: In this case if there is only single device
|
||||
* match, then we don't need numa information. Simply return last match.
|
||||
* Othewise return NULL.
|
||||
* 2. NUMA information is not exposed via _SEG method. In this case it is similar
|
||||
* to case 1.
|
||||
* 3. Numa information doesn't match with CPU numa node and more than one match
|
||||
* return NULL.
|
||||
*/
|
||||
if (!pci_dev && no_matches == 1)
|
||||
pci_dev = matched_pci_dev;
|
||||
|
||||
return pci_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* isst_if_get_pci_dev() - Get the PCI device instance for a CPU
|
||||
|
@ -300,17 +359,18 @@ static struct isst_if_cpu_info *isst_cpu_info;
|
|||
*/
|
||||
struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
|
||||
{
|
||||
int bus_number;
|
||||
struct pci_dev *pci_dev;
|
||||
|
||||
if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
|
||||
cpu >= num_possible_cpus())
|
||||
return NULL;
|
||||
|
||||
bus_number = isst_cpu_info[cpu].bus_info[bus_no];
|
||||
if (bus_number < 0)
|
||||
return NULL;
|
||||
pci_dev = isst_cpu_info[cpu].pci_dev[bus_no];
|
||||
|
||||
return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn));
|
||||
if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn))
|
||||
return pci_dev;
|
||||
|
||||
return _isst_if_get_pci_dev(cpu, bus_no, dev, fn);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
|
||||
|
||||
|
@ -327,6 +387,8 @@ static int isst_if_cpu_online(unsigned int cpu)
|
|||
} else {
|
||||
isst_cpu_info[cpu].bus_info[0] = data & 0xff;
|
||||
isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
|
||||
isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1);
|
||||
isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1);
|
||||
}
|
||||
|
||||
ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
|
||||
|
@ -335,6 +397,7 @@ static int isst_if_cpu_online(unsigned int cpu)
|
|||
return ret;
|
||||
}
|
||||
isst_cpu_info[cpu].punit_cpu_id = data;
|
||||
isst_cpu_info[cpu].numa_node = cpu_to_node(cpu);
|
||||
|
||||
isst_restore_msr_local(cpu);
|
||||
|
||||
|
|
|
@ -388,7 +388,7 @@ MODULE_PARM_DESC(force,
|
|||
"Disable the DMI check and forces the driver to be loaded");
|
||||
|
||||
static bool debug;
|
||||
module_param(debug, bool, S_IRUGO | S_IWUSR);
|
||||
module_param(debug, bool, 0644);
|
||||
MODULE_PARM_DESC(debug, "Debug enabled or not");
|
||||
|
||||
static int sabi_command(struct samsung_laptop *samsung, u16 command,
|
||||
|
@ -705,7 +705,7 @@ static ssize_t set_performance_level(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO,
|
||||
static DEVICE_ATTR(performance_level, 0644,
|
||||
get_performance_level, set_performance_level);
|
||||
|
||||
static int read_battery_life_extender(struct samsung_laptop *samsung)
|
||||
|
@ -774,7 +774,7 @@ static ssize_t set_battery_life_extender(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO,
|
||||
static DEVICE_ATTR(battery_life_extender, 0644,
|
||||
get_battery_life_extender, set_battery_life_extender);
|
||||
|
||||
static int read_usb_charge(struct samsung_laptop *samsung)
|
||||
|
@ -843,7 +843,7 @@ static ssize_t set_usb_charge(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO,
|
||||
static DEVICE_ATTR(usb_charge, 0644,
|
||||
get_usb_charge, set_usb_charge);
|
||||
|
||||
static int read_lid_handling(struct samsung_laptop *samsung)
|
||||
|
@ -908,7 +908,7 @@ static ssize_t set_lid_handling(struct device *dev,
|
|||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO,
|
||||
static DEVICE_ATTR(lid_handling, 0644,
|
||||
get_lid_handling, set_lid_handling);
|
||||
|
||||
static struct attribute *platform_attributes[] = {
|
||||
|
@ -1291,24 +1291,17 @@ static void samsung_debugfs_init(struct samsung_laptop *samsung)
|
|||
samsung->debug.sdiag_wrapper.data = samsung->sdiag;
|
||||
samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag);
|
||||
|
||||
debugfs_create_u16("command", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.command);
|
||||
debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d0);
|
||||
debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d1);
|
||||
debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d2);
|
||||
debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data.d3);
|
||||
debugfs_create_blob("data", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.data_wrapper);
|
||||
debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root,
|
||||
debugfs_create_u16("command", 0644, root, &samsung->debug.command);
|
||||
debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0);
|
||||
debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1);
|
||||
debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2);
|
||||
debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3);
|
||||
debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper);
|
||||
debugfs_create_blob("f0000_segment", 0400, root,
|
||||
&samsung->debug.f0000_wrapper);
|
||||
debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung,
|
||||
debugfs_create_file("call", 0444, root, samsung,
|
||||
&samsung_laptop_call_fops);
|
||||
debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root,
|
||||
&samsung->debug.sdiag_wrapper);
|
||||
debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper);
|
||||
}
|
||||
|
||||
static void samsung_sabi_exit(struct samsung_laptop *samsung)
|
||||
|
|
|
@ -156,7 +156,7 @@ static struct attribute *tc1100_attributes[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group tc1100_attribute_group = {
|
||||
static const struct attribute_group tc1100_attribute_group = {
|
||||
.attrs = tc1100_attributes,
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,904 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Think LMI BIOS configuration driver
|
||||
*
|
||||
* Copyright(C) 2019-2021 Lenovo
|
||||
*
|
||||
* Original code from Thinkpad-wmi project https://github.com/iksaif/thinkpad-wmi
|
||||
* Copyright(C) 2017 Corentin Chary <corentin.chary@gmail.com>
|
||||
* Distributed under the GPL-2.0 license
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/wmi.h>
|
||||
#include "firmware_attributes_class.h"
|
||||
#include "think-lmi.h"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_BiosSetting
|
||||
* Description:
|
||||
* Get item name and settings for current LMI instance.
|
||||
* Type:
|
||||
* Query
|
||||
* Returns:
|
||||
* "Item,Value"
|
||||
* Example:
|
||||
* "WakeOnLAN,Enable"
|
||||
*/
|
||||
#define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SetBiosSetting
|
||||
* Description:
|
||||
* Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting
|
||||
* class. To save the settings, use the Lenovo_SaveBiosSetting class.
|
||||
* BIOS settings and values are case sensitive.
|
||||
* After making changes to the BIOS settings, you must reboot the computer
|
||||
* before the changes will take effect.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Item,Value,Password,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "WakeOnLAN,Disable,pa55w0rd,ascii,us;"
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SaveBiosSettings
|
||||
* Description:
|
||||
* Save any pending changes in settings.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Password,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "pa55w0rd,ascii,us;"
|
||||
*/
|
||||
#define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_BiosPasswordSettings
|
||||
* Description:
|
||||
* Return BIOS Password settings
|
||||
* Type:
|
||||
* Query
|
||||
* Returns:
|
||||
* PasswordMode, PasswordState, MinLength, MaxLength,
|
||||
* SupportedEncoding, SupportedKeyboard
|
||||
*/
|
||||
#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_SetBiosPassword
|
||||
* Description:
|
||||
* Change a specific password.
|
||||
* - BIOS settings cannot be changed at the same boot as power-on
|
||||
* passwords (POP) and hard disk passwords (HDP). If you want to change
|
||||
* BIOS settings and POP or HDP, you must reboot the system after changing
|
||||
* one of them.
|
||||
* - A password cannot be set using this method when one does not already
|
||||
* exist. Passwords can only be updated or cleared.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;"
|
||||
* Example:
|
||||
* "pop,pa55w0rd,newpa55w0rd,ascii,us;”
|
||||
*/
|
||||
#define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7"
|
||||
|
||||
/*
|
||||
* Name:
|
||||
* Lenovo_GetBiosSelections
|
||||
* Description:
|
||||
* Return a list of valid settings for a given item.
|
||||
* Type:
|
||||
* Method
|
||||
* Arguments:
|
||||
* "Item"
|
||||
* Returns:
|
||||
* "Value1,Value2,Value3,..."
|
||||
* Example:
|
||||
* -> "FlashOverLAN"
|
||||
* <- "Enabled,Disabled"
|
||||
*/
|
||||
#define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B"
|
||||
|
||||
#define TLMI_POP_PWD (1 << 0)
|
||||
#define TLMI_PAP_PWD (1 << 1)
|
||||
#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj)
|
||||
#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj)
|
||||
|
||||
static const struct tlmi_err_codes tlmi_errs[] = {
|
||||
{"Success", 0},
|
||||
{"Not Supported", -EOPNOTSUPP},
|
||||
{"Invalid Parameter", -EINVAL},
|
||||
{"Access Denied", -EACCES},
|
||||
{"System Busy", -EBUSY},
|
||||
};
|
||||
|
||||
static const char * const encoding_options[] = {
|
||||
[TLMI_ENCODING_ASCII] = "ascii",
|
||||
[TLMI_ENCODING_SCANCODE] = "scancode",
|
||||
};
|
||||
static struct think_lmi tlmi_priv;
|
||||
static struct class *fw_attr_class;
|
||||
|
||||
/* ------ Utility functions ------------*/
|
||||
/* Convert BIOS WMI error string to suitable error code */
|
||||
static int tlmi_errstr_to_err(const char *errstr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) {
|
||||
if (!strcmp(tlmi_errs[i].err_str, errstr))
|
||||
return tlmi_errs[i].err_code;
|
||||
}
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/* Extract error string from WMI return buffer */
|
||||
static int tlmi_extract_error(const struct acpi_buffer *output)
|
||||
{
|
||||
const union acpi_object *obj;
|
||||
|
||||
obj = output->pointer;
|
||||
if (!obj)
|
||||
return -ENOMEM;
|
||||
if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
|
||||
return -EIO;
|
||||
|
||||
return tlmi_errstr_to_err(obj->string.pointer);
|
||||
}
|
||||
|
||||
/* Utility function to execute WMI call to BIOS */
|
||||
static int tlmi_simple_call(const char *guid, const char *arg)
|
||||
{
|
||||
const struct acpi_buffer input = { strlen(arg), (char *)arg };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
int i, err;
|
||||
|
||||
/*
|
||||
* Duplicated call required to match BIOS workaround for behavior
|
||||
* seen when WMI accessed via scripting on other OS.
|
||||
*/
|
||||
for (i = 0; i < 2; i++) {
|
||||
/* (re)initialize output buffer to default state */
|
||||
output.length = ACPI_ALLOCATE_BUFFER;
|
||||
output.pointer = NULL;
|
||||
|
||||
status = wmi_evaluate_method(guid, 0, 0, &input, &output);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
kfree(output.pointer);
|
||||
return -EIO;
|
||||
}
|
||||
err = tlmi_extract_error(&output);
|
||||
kfree(output.pointer);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Extract output string from WMI return buffer */
|
||||
static int tlmi_extract_output_string(const struct acpi_buffer *output,
|
||||
char **string)
|
||||
{
|
||||
const union acpi_object *obj;
|
||||
char *s;
|
||||
|
||||
obj = output->pointer;
|
||||
if (!obj)
|
||||
return -ENOMEM;
|
||||
if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer)
|
||||
return -EIO;
|
||||
|
||||
s = kstrdup(obj->string.pointer, GFP_KERNEL);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
*string = s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------ Core interface functions ------------*/
|
||||
|
||||
/* Get password settings from BIOS */
|
||||
static int tlmi_get_pwd_settings(struct tlmi_pwdcfg *pwdcfg)
|
||||
{
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
const union acpi_object *obj;
|
||||
acpi_status status;
|
||||
|
||||
if (!tlmi_priv.can_get_password_settings)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0,
|
||||
&output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return -EIO;
|
||||
|
||||
obj = output.pointer;
|
||||
if (!obj)
|
||||
return -ENOMEM;
|
||||
if (obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer) {
|
||||
kfree(obj);
|
||||
return -EIO;
|
||||
}
|
||||
/*
|
||||
* The size of thinkpad_wmi_pcfg on ThinkStation is larger than ThinkPad.
|
||||
* To make the driver compatible on different brands, we permit it to get
|
||||
* the data in below case.
|
||||
*/
|
||||
if (obj->buffer.length < sizeof(struct tlmi_pwdcfg)) {
|
||||
pr_warn("Unknown pwdcfg buffer length %d\n", obj->buffer.length);
|
||||
kfree(obj);
|
||||
return -EIO;
|
||||
}
|
||||
memcpy(pwdcfg, obj->buffer.pointer, sizeof(struct tlmi_pwdcfg));
|
||||
kfree(obj);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tlmi_save_bios_settings(const char *password)
|
||||
{
|
||||
return tlmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID,
|
||||
password);
|
||||
}
|
||||
|
||||
static int tlmi_setting(int item, char **value, const char *guid_string)
|
||||
{
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
status = wmi_query_block(guid_string, item, &output);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
kfree(output.pointer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = tlmi_extract_output_string(&output, value);
|
||||
kfree(output.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tlmi_get_bios_selections(const char *item, char **value)
|
||||
{
|
||||
const struct acpi_buffer input = { strlen(item), (char *)item };
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
acpi_status status;
|
||||
int ret;
|
||||
|
||||
status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID,
|
||||
0, 0, &input, &output);
|
||||
|
||||
if (ACPI_FAILURE(status)) {
|
||||
kfree(output.pointer);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = tlmi_extract_output_string(&output, value);
|
||||
kfree(output.pointer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---- Authentication sysfs --------------------------------------------------------- */
|
||||
static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", setting->valid);
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled);
|
||||
|
||||
static ssize_t current_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
size_t pwdlen;
|
||||
char *p;
|
||||
|
||||
pwdlen = strlen(buf);
|
||||
/* pwdlen == 0 is allowed to clear the password */
|
||||
if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen)))
|
||||
return -EINVAL;
|
||||
|
||||
strscpy(setting->password, buf, setting->maxlen);
|
||||
/* Strip out CR if one is present, setting password won't work if it is present */
|
||||
p = strchrnul(setting->password, '\n');
|
||||
*p = '\0';
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_current_password = __ATTR_WO(current_password);
|
||||
|
||||
static ssize_t new_password_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
char *auth_str, *new_pwd, *p;
|
||||
size_t pwdlen;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (!tlmi_priv.can_set_bios_password)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
new_pwd = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_pwd)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present, setting password won't work if it is present */
|
||||
p = strchrnul(new_pwd, '\n');
|
||||
*p = '\0';
|
||||
|
||||
pwdlen = strlen(new_pwd);
|
||||
/* pwdlen == 0 is allowed to clear the password */
|
||||
if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s,%s;",
|
||||
setting->pwd_type, setting->password, new_pwd,
|
||||
encoding_options[setting->encoding], setting->kbdlang);
|
||||
if (!auth_str) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str);
|
||||
kfree(auth_str);
|
||||
out:
|
||||
kfree(new_pwd);
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_new_password = __ATTR_WO(new_password);
|
||||
|
||||
static ssize_t min_password_length_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", setting->minlen);
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_min_pass_length = __ATTR_RO(min_password_length);
|
||||
|
||||
static ssize_t max_password_length_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", setting->maxlen);
|
||||
}
|
||||
static struct kobj_attribute auth_max_pass_length = __ATTR_RO(max_password_length);
|
||||
|
||||
static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return sysfs_emit(buf, "password\n");
|
||||
}
|
||||
static struct kobj_attribute auth_mechanism = __ATTR_RO(mechanism);
|
||||
|
||||
static ssize_t encoding_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", encoding_options[setting->encoding]);
|
||||
}
|
||||
|
||||
static ssize_t encoding_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
int i;
|
||||
|
||||
/* Scan for a matching profile */
|
||||
i = sysfs_match_string(encoding_options, buf);
|
||||
if (i < 0)
|
||||
return -EINVAL;
|
||||
|
||||
setting->encoding = i;
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_encoding = __ATTR_RW(encoding);
|
||||
|
||||
static ssize_t kbdlang_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", setting->kbdlang);
|
||||
}
|
||||
|
||||
static ssize_t kbdlang_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
int length;
|
||||
|
||||
/* Calculate length till '\n' or terminating 0 */
|
||||
length = strchrnul(buf, '\n') - buf;
|
||||
if (!length || length >= TLMI_LANG_MAXLEN)
|
||||
return -EINVAL;
|
||||
|
||||
memcpy(setting->kbdlang, buf, length);
|
||||
setting->kbdlang[length] = '\0';
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute auth_kbdlang = __ATTR_RW(kbdlang);
|
||||
|
||||
static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", setting->role);
|
||||
}
|
||||
static struct kobj_attribute auth_role = __ATTR_RO(role);
|
||||
|
||||
static struct attribute *auth_attrs[] = {
|
||||
&auth_is_pass_set.attr,
|
||||
&auth_min_pass_length.attr,
|
||||
&auth_max_pass_length.attr,
|
||||
&auth_current_password.attr,
|
||||
&auth_new_password.attr,
|
||||
&auth_role.attr,
|
||||
&auth_mechanism.attr,
|
||||
&auth_encoding.attr,
|
||||
&auth_kbdlang.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group auth_attr_group = {
|
||||
.attrs = auth_attrs,
|
||||
};
|
||||
|
||||
/* ---- Attributes sysfs --------------------------------------------------------- */
|
||||
static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
|
||||
return sysfs_emit(buf, "%s\n", setting->display_name);
|
||||
}
|
||||
|
||||
static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
char *item, *value;
|
||||
int ret;
|
||||
|
||||
ret = tlmi_setting(setting->index, &item, LENOVO_BIOS_SETTING_GUID);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* validate and split from `item,value` -> `value` */
|
||||
value = strpbrk(item, ",");
|
||||
if (!value || value == item || !strlen(value + 1))
|
||||
return -EINVAL;
|
||||
|
||||
ret = sysfs_emit(buf, "%s\n", value + 1);
|
||||
kfree(item);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
||||
{
|
||||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
|
||||
if (!tlmi_priv.can_get_bios_selections)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return sysfs_emit(buf, "%s\n", setting->possible_values);
|
||||
}
|
||||
|
||||
static ssize_t current_value_store(struct kobject *kobj,
|
||||
struct kobj_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
char *set_str = NULL, *new_setting = NULL;
|
||||
char *auth_str = NULL;
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
if (!tlmi_priv.can_set_bios_settings)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
new_setting = kstrdup(buf, GFP_KERNEL);
|
||||
if (!new_setting)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Strip out CR if one is present */
|
||||
p = strchrnul(new_setting, '\n');
|
||||
*p = '\0';
|
||||
|
||||
if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) {
|
||||
auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;",
|
||||
tlmi_priv.pwd_admin->password,
|
||||
encoding_options[tlmi_priv.pwd_admin->encoding],
|
||||
tlmi_priv.pwd_admin->kbdlang);
|
||||
if (!auth_str) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
if (auth_str)
|
||||
set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name,
|
||||
new_setting, auth_str);
|
||||
else
|
||||
set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name,
|
||||
new_setting);
|
||||
if (!set_str) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (auth_str)
|
||||
ret = tlmi_save_bios_settings(auth_str);
|
||||
else
|
||||
ret = tlmi_save_bios_settings("");
|
||||
|
||||
out:
|
||||
kfree(auth_str);
|
||||
kfree(set_str);
|
||||
kfree(new_setting);
|
||||
return ret ?: count;
|
||||
}
|
||||
|
||||
static struct kobj_attribute attr_displ_name = __ATTR_RO(display_name);
|
||||
|
||||
static struct kobj_attribute attr_possible_values = __ATTR_RO(possible_values);
|
||||
|
||||
static struct kobj_attribute attr_current_val = __ATTR_RW_MODE(current_value, 0600);
|
||||
|
||||
static struct attribute *tlmi_attrs[] = {
|
||||
&attr_displ_name.attr,
|
||||
&attr_current_val.attr,
|
||||
&attr_possible_values.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group tlmi_attr_group = {
|
||||
.attrs = tlmi_attrs,
|
||||
};
|
||||
|
||||
static ssize_t tlmi_attr_show(struct kobject *kobj, struct attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct kobj_attribute *kattr;
|
||||
|
||||
kattr = container_of(attr, struct kobj_attribute, attr);
|
||||
if (kattr->show)
|
||||
return kattr->show(kobj, kattr, buf);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static ssize_t tlmi_attr_store(struct kobject *kobj, struct attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct kobj_attribute *kattr;
|
||||
|
||||
kattr = container_of(attr, struct kobj_attribute, attr);
|
||||
if (kattr->store)
|
||||
return kattr->store(kobj, kattr, buf, count);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
static const struct sysfs_ops tlmi_kobj_sysfs_ops = {
|
||||
.show = tlmi_attr_show,
|
||||
.store = tlmi_attr_store,
|
||||
};
|
||||
|
||||
static void tlmi_attr_setting_release(struct kobject *kobj)
|
||||
{
|
||||
struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj);
|
||||
|
||||
kfree(setting->possible_values);
|
||||
kfree(setting);
|
||||
}
|
||||
|
||||
static void tlmi_pwd_setting_release(struct kobject *kobj)
|
||||
{
|
||||
struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj);
|
||||
|
||||
kfree(setting);
|
||||
}
|
||||
|
||||
static struct kobj_type tlmi_attr_setting_ktype = {
|
||||
.release = &tlmi_attr_setting_release,
|
||||
.sysfs_ops = &tlmi_kobj_sysfs_ops,
|
||||
};
|
||||
|
||||
static struct kobj_type tlmi_pwd_setting_ktype = {
|
||||
.release = &tlmi_pwd_setting_release,
|
||||
.sysfs_ops = &tlmi_kobj_sysfs_ops,
|
||||
};
|
||||
|
||||
/* ---- Initialisation --------------------------------------------------------- */
|
||||
static void tlmi_release_attr(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* Attribute structures */
|
||||
for (i = 0; i < TLMI_SETTINGS_COUNT; i++) {
|
||||
if (tlmi_priv.setting[i]) {
|
||||
sysfs_remove_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group);
|
||||
kobject_put(&tlmi_priv.setting[i]->kobj);
|
||||
}
|
||||
}
|
||||
kset_unregister(tlmi_priv.attribute_kset);
|
||||
|
||||
/* Authentication structures */
|
||||
sysfs_remove_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group);
|
||||
kobject_put(&tlmi_priv.pwd_admin->kobj);
|
||||
sysfs_remove_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group);
|
||||
kobject_put(&tlmi_priv.pwd_power->kobj);
|
||||
kset_unregister(tlmi_priv.authentication_kset);
|
||||
}
|
||||
|
||||
static int tlmi_sysfs_init(void)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
ret = fw_attributes_class_get(&fw_attr_class);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
tlmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0),
|
||||
NULL, "%s", "thinklmi");
|
||||
if (IS_ERR(tlmi_priv.class_dev)) {
|
||||
ret = PTR_ERR(tlmi_priv.class_dev);
|
||||
goto fail_class_created;
|
||||
}
|
||||
|
||||
tlmi_priv.attribute_kset = kset_create_and_add("attributes", NULL,
|
||||
&tlmi_priv.class_dev->kobj);
|
||||
if (!tlmi_priv.attribute_kset) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_device_created;
|
||||
}
|
||||
|
||||
for (i = 0; i < TLMI_SETTINGS_COUNT; i++) {
|
||||
/* Check if index is a valid setting - skip if it isn't */
|
||||
if (!tlmi_priv.setting[i])
|
||||
continue;
|
||||
|
||||
/* check for duplicate or reserved values */
|
||||
if (kset_find_obj(tlmi_priv.attribute_kset, tlmi_priv.setting[i]->display_name) ||
|
||||
!strcmp(tlmi_priv.setting[i]->display_name, "Reserved")) {
|
||||
pr_debug("duplicate or reserved attribute name found - %s\n",
|
||||
tlmi_priv.setting[i]->display_name);
|
||||
kfree(tlmi_priv.setting[i]->possible_values);
|
||||
kfree(tlmi_priv.setting[i]);
|
||||
tlmi_priv.setting[i] = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Build attribute */
|
||||
tlmi_priv.setting[i]->kobj.kset = tlmi_priv.attribute_kset;
|
||||
ret = kobject_init_and_add(&tlmi_priv.setting[i]->kobj, &tlmi_attr_setting_ktype,
|
||||
NULL, "%s", tlmi_priv.setting[i]->display_name);
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
|
||||
ret = sysfs_create_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group);
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
}
|
||||
|
||||
/* Create authentication entries */
|
||||
tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL,
|
||||
&tlmi_priv.class_dev->kobj);
|
||||
if (!tlmi_priv.authentication_kset) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_create_attr;
|
||||
}
|
||||
tlmi_priv.pwd_admin->kobj.kset = tlmi_priv.authentication_kset;
|
||||
ret = kobject_init_and_add(&tlmi_priv.pwd_admin->kobj, &tlmi_pwd_setting_ktype,
|
||||
NULL, "%s", "Admin");
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
|
||||
ret = sysfs_create_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group);
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
|
||||
tlmi_priv.pwd_power->kobj.kset = tlmi_priv.authentication_kset;
|
||||
ret = kobject_init_and_add(&tlmi_priv.pwd_power->kobj, &tlmi_pwd_setting_ktype,
|
||||
NULL, "%s", "System");
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
|
||||
ret = sysfs_create_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group);
|
||||
if (ret)
|
||||
goto fail_create_attr;
|
||||
|
||||
return ret;
|
||||
|
||||
fail_create_attr:
|
||||
tlmi_release_attr();
|
||||
fail_device_created:
|
||||
device_destroy(fw_attr_class, MKDEV(0, 0));
|
||||
fail_class_created:
|
||||
fw_attributes_class_put();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ---- Base Driver -------------------------------------------------------- */
|
||||
static int tlmi_analyze(void)
|
||||
{
|
||||
struct tlmi_pwdcfg pwdcfg;
|
||||
acpi_status status;
|
||||
int i, ret;
|
||||
|
||||
if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) &&
|
||||
wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID))
|
||||
tlmi_priv.can_set_bios_settings = true;
|
||||
|
||||
if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID))
|
||||
tlmi_priv.can_get_bios_selections = true;
|
||||
|
||||
if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID))
|
||||
tlmi_priv.can_set_bios_password = true;
|
||||
|
||||
if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID))
|
||||
tlmi_priv.can_get_password_settings = true;
|
||||
|
||||
/*
|
||||
* Try to find the number of valid settings of this machine
|
||||
* and use it to create sysfs attributes.
|
||||
*/
|
||||
for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) {
|
||||
struct tlmi_attr_setting *setting;
|
||||
char *item = NULL;
|
||||
char *p;
|
||||
|
||||
tlmi_priv.setting[i] = NULL;
|
||||
status = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID);
|
||||
if (ACPI_FAILURE(status))
|
||||
break;
|
||||
if (!item)
|
||||
break;
|
||||
if (!*item)
|
||||
continue;
|
||||
|
||||
/* It is not allowed to have '/' for file name. Convert it into '\'. */
|
||||
strreplace(item, '/', '\\');
|
||||
|
||||
/* Remove the value part */
|
||||
p = strchrnul(item, ',');
|
||||
*p = '\0';
|
||||
|
||||
/* Create a setting entry */
|
||||
setting = kzalloc(sizeof(*setting), GFP_KERNEL);
|
||||
if (!setting) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_clear_attr;
|
||||
}
|
||||
setting->index = i;
|
||||
strscpy(setting->display_name, item, TLMI_SETTINGS_MAXLEN);
|
||||
/* If BIOS selections supported, load those */
|
||||
if (tlmi_priv.can_get_bios_selections) {
|
||||
ret = tlmi_get_bios_selections(setting->display_name,
|
||||
&setting->possible_values);
|
||||
if (ret || !setting->possible_values)
|
||||
pr_info("Error retrieving possible values for %d : %s\n",
|
||||
i, setting->display_name);
|
||||
}
|
||||
tlmi_priv.setting[i] = setting;
|
||||
tlmi_priv.settings_count++;
|
||||
kfree(item);
|
||||
}
|
||||
|
||||
/* Create password setting structure */
|
||||
ret = tlmi_get_pwd_settings(&pwdcfg);
|
||||
if (ret)
|
||||
goto fail_clear_attr;
|
||||
|
||||
tlmi_priv.pwd_admin = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
|
||||
if (!tlmi_priv.pwd_admin) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_clear_attr;
|
||||
}
|
||||
strscpy(tlmi_priv.pwd_admin->kbdlang, "us", TLMI_LANG_MAXLEN);
|
||||
tlmi_priv.pwd_admin->encoding = TLMI_ENCODING_ASCII;
|
||||
tlmi_priv.pwd_admin->pwd_type = "pap";
|
||||
tlmi_priv.pwd_admin->role = "bios-admin";
|
||||
tlmi_priv.pwd_admin->minlen = pwdcfg.min_length;
|
||||
if (WARN_ON(pwdcfg.max_length >= TLMI_PWD_BUFSIZE))
|
||||
pwdcfg.max_length = TLMI_PWD_BUFSIZE - 1;
|
||||
tlmi_priv.pwd_admin->maxlen = pwdcfg.max_length;
|
||||
if (pwdcfg.password_state & TLMI_PAP_PWD)
|
||||
tlmi_priv.pwd_admin->valid = true;
|
||||
|
||||
tlmi_priv.pwd_power = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL);
|
||||
if (!tlmi_priv.pwd_power) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_clear_attr;
|
||||
}
|
||||
strscpy(tlmi_priv.pwd_power->kbdlang, "us", TLMI_LANG_MAXLEN);
|
||||
tlmi_priv.pwd_power->encoding = TLMI_ENCODING_ASCII;
|
||||
tlmi_priv.pwd_power->pwd_type = "pop";
|
||||
tlmi_priv.pwd_power->role = "power-on";
|
||||
tlmi_priv.pwd_power->minlen = pwdcfg.min_length;
|
||||
tlmi_priv.pwd_power->maxlen = pwdcfg.max_length;
|
||||
|
||||
if (pwdcfg.password_state & TLMI_POP_PWD)
|
||||
tlmi_priv.pwd_power->valid = true;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_clear_attr:
|
||||
for (i = 0; i < TLMI_SETTINGS_COUNT; ++i)
|
||||
kfree(tlmi_priv.setting[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tlmi_remove(struct wmi_device *wdev)
|
||||
{
|
||||
tlmi_release_attr();
|
||||
device_destroy(fw_attr_class, MKDEV(0, 0));
|
||||
fw_attributes_class_put();
|
||||
}
|
||||
|
||||
static int tlmi_probe(struct wmi_device *wdev, const void *context)
|
||||
{
|
||||
tlmi_analyze();
|
||||
return tlmi_sysfs_init();
|
||||
}
|
||||
|
||||
static const struct wmi_device_id tlmi_id_table[] = {
|
||||
{ .guid_string = LENOVO_BIOS_SETTING_GUID },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(wmi, tlmi_id_table);
|
||||
|
||||
static struct wmi_driver tlmi_driver = {
|
||||
.driver = {
|
||||
.name = "think-lmi",
|
||||
},
|
||||
.id_table = tlmi_id_table,
|
||||
.probe = tlmi_probe,
|
||||
.remove = tlmi_remove,
|
||||
};
|
||||
|
||||
MODULE_AUTHOR("Sugumaran L <slacshiminar@lenovo.com>");
|
||||
MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
|
||||
MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>");
|
||||
MODULE_DESCRIPTION("ThinkLMI Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
module_wmi_driver(tlmi_driver);
|
|
@ -0,0 +1,72 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#ifndef _THINK_LMI_H_
|
||||
#define _THINK_LMI_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define TLMI_SETTINGS_COUNT 256
|
||||
#define TLMI_SETTINGS_MAXLEN 512
|
||||
#define TLMI_PWD_BUFSIZE 129
|
||||
#define TLMI_LANG_MAXLEN 4
|
||||
|
||||
/* Possible error values */
|
||||
struct tlmi_err_codes {
|
||||
const char *err_str;
|
||||
int err_code;
|
||||
};
|
||||
|
||||
enum encoding_option {
|
||||
TLMI_ENCODING_ASCII,
|
||||
TLMI_ENCODING_SCANCODE,
|
||||
};
|
||||
|
||||
/* password configuration details */
|
||||
struct tlmi_pwdcfg {
|
||||
uint32_t password_mode;
|
||||
uint32_t password_state;
|
||||
uint32_t min_length;
|
||||
uint32_t max_length;
|
||||
uint32_t supported_encodings;
|
||||
uint32_t supported_keyboard;
|
||||
};
|
||||
|
||||
/* password setting details */
|
||||
struct tlmi_pwd_setting {
|
||||
struct kobject kobj;
|
||||
bool valid;
|
||||
char password[TLMI_PWD_BUFSIZE];
|
||||
const char *pwd_type;
|
||||
const char *role;
|
||||
int minlen;
|
||||
int maxlen;
|
||||
enum encoding_option encoding;
|
||||
char kbdlang[TLMI_LANG_MAXLEN];
|
||||
};
|
||||
|
||||
/* Attribute setting details */
|
||||
struct tlmi_attr_setting {
|
||||
struct kobject kobj;
|
||||
int index;
|
||||
char display_name[TLMI_SETTINGS_MAXLEN];
|
||||
char *possible_values;
|
||||
};
|
||||
|
||||
struct think_lmi {
|
||||
struct wmi_device *wmi_device;
|
||||
|
||||
int settings_count;
|
||||
bool can_set_bios_settings;
|
||||
bool can_get_bios_selections;
|
||||
bool can_set_bios_password;
|
||||
bool can_get_password_settings;
|
||||
|
||||
struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT];
|
||||
struct device *class_dev;
|
||||
struct kset *attribute_kset;
|
||||
struct kset *authentication_kset;
|
||||
struct tlmi_pwd_setting *pwd_admin;
|
||||
struct tlmi_pwd_setting *pwd_power;
|
||||
};
|
||||
|
||||
#endif /* !_THINK_LMI_H_ */
|
|
@ -7938,7 +7938,7 @@ static int volume_write(char *buf)
|
|||
continue;
|
||||
} else if (sscanf(cmd, "level %u", &l) == 1 &&
|
||||
l >= 0 && l <= TP_EC_VOLUME_MAX) {
|
||||
new_level = l;
|
||||
new_level = l;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2831,6 +2831,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev)
|
|||
|
||||
if (!dev->info_supported && !dev->system_event_supported) {
|
||||
pr_warn("No hotkey query interface found\n");
|
||||
error = -EINVAL;
|
||||
goto err_remove_filter;
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ static const struct attribute_group haps_attr_group = {
|
|||
*/
|
||||
static void toshiba_haps_notify(struct acpi_device *device, u32 event)
|
||||
{
|
||||
pr_debug("Received event: 0x%x", event);
|
||||
pr_debug("Received event: 0x%x\n", event);
|
||||
|
||||
acpi_bus_generate_netlink_event(device->pnp.device_class,
|
||||
dev_name(&device->dev),
|
||||
|
|
|
@ -299,6 +299,35 @@ static const struct ts_dmi_data estar_beauty_hd_data = {
|
|||
.properties = estar_beauty_hd_props,
|
||||
};
|
||||
|
||||
/* Generic props + data for upside-down mounted GDIX1001 touchscreens */
|
||||
static const struct property_entry gdix1001_upside_down_props[] = {
|
||||
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
|
||||
PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct ts_dmi_data gdix1001_00_upside_down_data = {
|
||||
.acpi_name = "GDIX1001:00",
|
||||
.properties = gdix1001_upside_down_props,
|
||||
};
|
||||
|
||||
static const struct ts_dmi_data gdix1001_01_upside_down_data = {
|
||||
.acpi_name = "GDIX1001:01",
|
||||
.properties = gdix1001_upside_down_props,
|
||||
};
|
||||
|
||||
static const struct property_entry glavey_tm800a550l_props[] = {
|
||||
PROPERTY_ENTRY_STRING("firmware-name", "gt912-glavey-tm800a550l.fw"),
|
||||
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-glavey-tm800a550l.cfg"),
|
||||
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct ts_dmi_data glavey_tm800a550l_data = {
|
||||
.acpi_name = "GDIX1001:00",
|
||||
.properties = glavey_tm800a550l_props,
|
||||
};
|
||||
|
||||
static const struct property_entry gp_electronic_t701_props[] = {
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-x", 960),
|
||||
PROPERTY_ENTRY_U32("touchscreen-size-y", 640),
|
||||
|
@ -942,7 +971,7 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
|
|||
},
|
||||
},
|
||||
{
|
||||
/* Chuwi Hi10 Prus (CWI597) */
|
||||
/* Chuwi Hi10 Pro (CWI529) */
|
||||
.driver_data = (void *)&chuwi_hi10_pro_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
|
||||
|
@ -1038,6 +1067,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"),
|
||||
},
|
||||
},
|
||||
{ /* Glavey TM800A550L */
|
||||
.driver_data = (void *)&glavey_tm800a550l_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
|
||||
/* Above strings are too generic, also match on BIOS version */
|
||||
DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* GP-electronic T701 */
|
||||
.driver_data = (void *)&gp_electronic_t701_data,
|
||||
|
@ -1330,6 +1368,24 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
|
|||
DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Teclast X89 (Android version / BIOS) */
|
||||
.driver_data = (void *)&gdix1001_00_upside_down_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "WISKY"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "3G062i"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Teclast X89 (Windows version / BIOS) */
|
||||
.driver_data = (void *)&gdix1001_01_upside_down_data,
|
||||
.matches = {
|
||||
/* tPAD is too generic, also match on bios date */
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "tPAD"),
|
||||
DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Teclast X98 Plus II */
|
||||
.driver_data = (void *)&teclast_x98plus2_data,
|
||||
|
@ -1338,6 +1394,19 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Teclast X98 Pro */
|
||||
.driver_data = (void *)&gdix1001_00_upside_down_data,
|
||||
.matches = {
|
||||
/*
|
||||
* Only match BIOS date, because the manufacturers
|
||||
* BIOS does not report the board name at all
|
||||
* (sometimes)...
|
||||
*/
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"),
|
||||
DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Trekstor Primebook C11 */
|
||||
.driver_data = (void *)&trekstor_primebook_c11_data,
|
||||
|
@ -1413,6 +1482,22 @@ const struct dmi_system_id touchscreen_dmi_table[] = {
|
|||
DMI_MATCH(DMI_PRODUCT_NAME, "VINGA Twizzle J116"),
|
||||
},
|
||||
},
|
||||
{
|
||||
/* "WinBook TW100" */
|
||||
.driver_data = (void *)&gdix1001_00_upside_down_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TW100")
|
||||
}
|
||||
},
|
||||
{
|
||||
/* WinBook TW700 */
|
||||
.driver_data = (void *)&gdix1001_00_upside_down_data,
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "TW700")
|
||||
},
|
||||
},
|
||||
{
|
||||
/* Yours Y8W81, same case and touchscreen as Chuwi Vi8 */
|
||||
.driver_data = (void *)&chuwi_vi8_data,
|
||||
|
|
|
@ -778,7 +778,7 @@ static struct attribute *base_attrs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group base_attr_group = {
|
||||
static const struct attribute_group base_attr_group = {
|
||||
.attrs = base_attrs
|
||||
};
|
||||
|
||||
|
@ -823,7 +823,7 @@ static struct attribute *hubless_base_attrs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group hubless_base_attr_group = {
|
||||
static const struct attribute_group hubless_base_attr_group = {
|
||||
.attrs = hubless_base_attrs
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* Airplane mode button for AMD, HP & Xiaomi laptops
|
||||
*
|
||||
* Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com>
|
||||
* Copyright (C) 2021 Advanced Micro Devices
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <acpi/acpi_bus.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Alex Hung");
|
||||
MODULE_ALIAS("acpi*:HPQ6001:*");
|
||||
MODULE_ALIAS("acpi*:WSTADEF:*");
|
||||
MODULE_ALIAS("acpi*:AMDI0051:*");
|
||||
|
||||
static struct input_dev *wl_input_dev;
|
||||
|
||||
static const struct acpi_device_id wl_ids[] = {
|
||||
{"HPQ6001", 0},
|
||||
{"WSTADEF", 0},
|
||||
{"AMDI0051", 0},
|
||||
{"", 0},
|
||||
};
|
||||
|
||||
static int wireless_input_setup(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
wl_input_dev = input_allocate_device();
|
||||
if (!wl_input_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
wl_input_dev->name = "Wireless hotkeys";
|
||||
wl_input_dev->phys = "hpq6001/input0";
|
||||
wl_input_dev->id.bustype = BUS_HOST;
|
||||
wl_input_dev->evbit[0] = BIT(EV_KEY);
|
||||
set_bit(KEY_RFKILL, wl_input_dev->keybit);
|
||||
|
||||
err = input_register_device(wl_input_dev);
|
||||
if (err)
|
||||
goto err_free_dev;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_dev:
|
||||
input_free_device(wl_input_dev);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void wireless_input_destroy(void)
|
||||
{
|
||||
input_unregister_device(wl_input_dev);
|
||||
}
|
||||
|
||||
static void wl_notify(struct acpi_device *acpi_dev, u32 event)
|
||||
{
|
||||
if (event != 0x80) {
|
||||
pr_info("Received unknown event (0x%x)\n", event);
|
||||
return;
|
||||
}
|
||||
|
||||
input_report_key(wl_input_dev, KEY_RFKILL, 1);
|
||||
input_sync(wl_input_dev);
|
||||
input_report_key(wl_input_dev, KEY_RFKILL, 0);
|
||||
input_sync(wl_input_dev);
|
||||
}
|
||||
|
||||
static int wl_add(struct acpi_device *device)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = wireless_input_setup();
|
||||
if (err)
|
||||
pr_err("Failed to setup hp wireless hotkeys\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int wl_remove(struct acpi_device *device)
|
||||
{
|
||||
wireless_input_destroy();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct acpi_driver wl_driver = {
|
||||
.name = "wireless-hotkey",
|
||||
.owner = THIS_MODULE,
|
||||
.ids = wl_ids,
|
||||
.ops = {
|
||||
.add = wl_add,
|
||||
.remove = wl_remove,
|
||||
.notify = wl_notify,
|
||||
},
|
||||
};
|
||||
|
||||
module_acpi_driver(wl_driver);
|
|
@ -1097,6 +1097,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c
|
|||
#if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB)
|
||||
bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
|
||||
struct acpi_resource_gpio **agpio);
|
||||
bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
|
||||
struct acpi_resource_gpio **agpio);
|
||||
int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index);
|
||||
#else
|
||||
static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
|
||||
|
@ -1104,6 +1106,11 @@ static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares,
|
|||
{
|
||||
return false;
|
||||
}
|
||||
static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares,
|
||||
struct acpi_resource_gpio **agpio)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev,
|
||||
const char *name, int index)
|
||||
{
|
||||
|
|
|
@ -51,4 +51,29 @@ static inline int devm_delayed_work_autocancel(struct device *dev,
|
|||
return devm_add_action(dev, devm_delayed_work_drop, w);
|
||||
}
|
||||
|
||||
static inline void devm_work_drop(void *res)
|
||||
{
|
||||
cancel_work_sync(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_work_autocancel - Resource-managed work allocation
|
||||
* @dev: Device which lifetime work is bound to
|
||||
* @w: Work to be added (and automatically cancelled)
|
||||
* @worker: Worker function
|
||||
*
|
||||
* Initialize work which is automatically cancelled when driver is detached.
|
||||
* A few drivers need to queue work which must be cancelled before driver
|
||||
* is detached to avoid accessing removed resources.
|
||||
* devm_work_autocancel() can be used to omit the explicit
|
||||
* cancelleation when driver is detached.
|
||||
*/
|
||||
static inline int devm_work_autocancel(struct device *dev,
|
||||
struct work_struct *w,
|
||||
work_func_t worker)
|
||||
{
|
||||
INIT_WORK(w, worker);
|
||||
return devm_add_action(dev, devm_work_drop, w);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev,
|
|||
const struct acpi_gpio_mapping *gpios);
|
||||
void devm_acpi_dev_remove_driver_gpios(struct device *dev);
|
||||
|
||||
struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label);
|
||||
|
||||
#else /* CONFIG_GPIOLIB && CONFIG_ACPI */
|
||||
|
||||
struct acpi_device;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* managing access and communication to and from the SSAM EC, as well as main
|
||||
* communication structures and definitions.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
|
||||
|
@ -795,6 +795,20 @@ enum ssam_event_mask {
|
|||
#define SSAM_EVENT_REGISTRY_REG \
|
||||
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02)
|
||||
|
||||
/**
|
||||
* enum ssam_event_notifier_flags - Flags for event notifiers.
|
||||
* @SSAM_EVENT_NOTIFIER_OBSERVER:
|
||||
* The corresponding notifier acts as observer. Registering a notifier
|
||||
* with this flag set will not attempt to enable any event. Equally,
|
||||
* unregistering will not attempt to disable any event. Note that a
|
||||
* notifier with this flag may not even correspond to a certain event at
|
||||
* all, only to a specific event target category. Event matching will not
|
||||
* be influenced by this flag.
|
||||
*/
|
||||
enum ssam_event_notifier_flags {
|
||||
SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0),
|
||||
};
|
||||
|
||||
/**
|
||||
* struct ssam_event_notifier - Notifier block for SSAM events.
|
||||
* @base: The base notifier block with callback function and priority.
|
||||
|
@ -803,6 +817,7 @@ enum ssam_event_mask {
|
|||
* @event.id: ID specifying the event.
|
||||
* @event.mask: Flags determining how events are matched to the notifier.
|
||||
* @event.flags: Flags used for enabling the event.
|
||||
* @flags: Notifier flags (see &enum ssam_event_notifier_flags).
|
||||
*/
|
||||
struct ssam_event_notifier {
|
||||
struct ssam_notifier_block base;
|
||||
|
@ -813,6 +828,8 @@ struct ssam_event_notifier {
|
|||
enum ssam_event_mask mask;
|
||||
u8 flags;
|
||||
} event;
|
||||
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
int ssam_notifier_register(struct ssam_controller *ctrl,
|
||||
|
@ -821,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
|
|||
int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
||||
struct ssam_event_notifier *n);
|
||||
|
||||
int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
||||
struct ssam_event_registry reg,
|
||||
struct ssam_event_id id, u8 flags);
|
||||
|
||||
int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
||||
struct ssam_event_registry reg,
|
||||
struct ssam_event_id id, u8 flags);
|
||||
|
||||
#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* Provides support for non-platform/non-ACPI SSAM clients via dedicated
|
||||
* subsystem.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Surface System Aggregator Module (SSAM). Provides the interface for basic
|
||||
* packet- and request-based communication with the SSAM EC via SSH.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* device. This device provides direct user-space access to the SSAM EC.
|
||||
* Intended for debugging and development.
|
||||
*
|
||||
* Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
* Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H
|
||||
|
@ -73,6 +73,75 @@ struct ssam_cdev_request {
|
|||
} response;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
|
||||
/**
|
||||
* struct ssam_cdev_notifier_desc - Notifier descriptor.
|
||||
* @priority: Priority value determining the order in which notifier
|
||||
* callbacks will be called. A higher value means higher
|
||||
* priority, i.e. the associated callback will be executed
|
||||
* earlier than other (lower priority) callbacks.
|
||||
* @target_category: The event target category for which this notifier should
|
||||
* receive events.
|
||||
*
|
||||
* Specifies the notifier that should be registered or unregistered,
|
||||
* specifically with which priority and for which target category of events.
|
||||
*/
|
||||
struct ssam_cdev_notifier_desc {
|
||||
__s32 priority;
|
||||
__u8 target_category;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
/**
|
||||
* struct ssam_cdev_event_desc - Event descriptor.
|
||||
* @reg: Registry via which the event will be enabled/disabled.
|
||||
* @reg.target_category: Target category for the event registry requests.
|
||||
* @reg.target_id: Target ID for the event registry requests.
|
||||
* @reg.cid_enable: Command ID for the event-enable request.
|
||||
* @reg.cid_disable: Command ID for the event-disable request.
|
||||
* @id: ID specifying the event.
|
||||
* @id.target_category: Target category of the event source.
|
||||
* @id.instance: Instance ID of the event source.
|
||||
* @flags: Flags used for enabling the event.
|
||||
*
|
||||
* Specifies which event should be enabled/disabled and how to do that.
|
||||
*/
|
||||
struct ssam_cdev_event_desc {
|
||||
struct {
|
||||
__u8 target_category;
|
||||
__u8 target_id;
|
||||
__u8 cid_enable;
|
||||
__u8 cid_disable;
|
||||
} reg;
|
||||
|
||||
struct {
|
||||
__u8 target_category;
|
||||
__u8 instance;
|
||||
} id;
|
||||
|
||||
__u8 flags;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
/**
|
||||
* struct ssam_cdev_event - SSAM event sent by the EC.
|
||||
* @target_category: Target category of the event source. See &enum ssam_ssh_tc.
|
||||
* @target_id: Target ID of the event source.
|
||||
* @command_id: Command ID of the event.
|
||||
* @instance_id: Instance ID of the event source.
|
||||
* @length: Length of the event payload in bytes.
|
||||
* @data: Event payload data.
|
||||
*/
|
||||
struct ssam_cdev_event {
|
||||
__u8 target_category;
|
||||
__u8 target_id;
|
||||
__u8 command_id;
|
||||
__u8 instance_id;
|
||||
__u16 length;
|
||||
__u8 data[];
|
||||
} __attribute__((__packed__));
|
||||
|
||||
#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
|
||||
#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
|
||||
#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
|
||||
#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc)
|
||||
#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc)
|
||||
|
||||
#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
|
||||
|
|
|
@ -15,7 +15,7 @@ struct process_cmd_struct {
|
|||
int arg;
|
||||
};
|
||||
|
||||
static const char *version_str = "v1.9";
|
||||
static const char *version_str = "v1.10";
|
||||
static const int supported_api_ver = 1;
|
||||
static struct isst_if_platform_info isst_platform_info;
|
||||
static char *progname;
|
||||
|
@ -106,6 +106,22 @@ int is_skx_based_platform(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int is_spr_platform(void)
|
||||
{
|
||||
if (cpu_model == 0x8F)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int is_icx_platform(void)
|
||||
{
|
||||
if (cpu_model == 0x6A || cpu_model == 0x6C)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int update_cpu_model(void)
|
||||
{
|
||||
unsigned int ebx, ecx, edx;
|
||||
|
|
|
@ -201,6 +201,7 @@ void isst_get_uncore_mem_freq(int cpu, int config_index,
|
|||
{
|
||||
unsigned int resp;
|
||||
int ret;
|
||||
|
||||
ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ,
|
||||
0, config_index, &resp);
|
||||
if (ret) {
|
||||
|
@ -209,6 +210,20 @@ void isst_get_uncore_mem_freq(int cpu, int config_index,
|
|||
}
|
||||
|
||||
ctdp_level->mem_freq = resp & GENMASK(7, 0);
|
||||
if (is_spr_platform()) {
|
||||
ctdp_level->mem_freq *= 200;
|
||||
} else if (is_icx_platform()) {
|
||||
if (ctdp_level->mem_freq < 7) {
|
||||
ctdp_level->mem_freq = (12 - ctdp_level->mem_freq) * 133.33 * 2 * 10;
|
||||
ctdp_level->mem_freq /= 10;
|
||||
if (ctdp_level->mem_freq % 10 > 5)
|
||||
ctdp_level->mem_freq++;
|
||||
} else {
|
||||
ctdp_level->mem_freq = 0;
|
||||
}
|
||||
} else {
|
||||
ctdp_level->mem_freq = 0;
|
||||
}
|
||||
debug_printf(
|
||||
"cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n",
|
||||
cpu, config_index, resp, ctdp_level->mem_freq);
|
||||
|
|
|
@ -446,7 +446,7 @@ void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level,
|
|||
if (ctdp_level->mem_freq) {
|
||||
snprintf(header, sizeof(header), "mem-frequency(MHz)");
|
||||
snprintf(value, sizeof(value), "%d",
|
||||
ctdp_level->mem_freq * DISP_FREQ_MULTIPLIER);
|
||||
ctdp_level->mem_freq);
|
||||
format_and_print(outf, level + 2, header, value);
|
||||
}
|
||||
|
||||
|
|
|
@ -257,5 +257,7 @@ extern int get_cpufreq_base_freq(int cpu);
|
|||
extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap);
|
||||
extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg);
|
||||
extern int is_skx_based_platform(void);
|
||||
extern int is_spr_platform(void);
|
||||
extern int is_icx_platform(void);
|
||||
extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl);
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue