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:
Linus Torvalds 2021-06-30 11:15:39 -07:00
commit 776ba3ad65
95 changed files with 4286 additions and 693 deletions

View File

@ -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

View File

@ -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
Dells 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]

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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,
};

View File

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

View File

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

View File

@ -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.

View File

@ -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 = {

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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;

View File

@ -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

View File

@ -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

View File

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

View File

@ -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"

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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>

View File

@ -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

View File

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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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 },

View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

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

View File

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

View File

@ -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);
}

View File

@ -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

View File

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

View File

@ -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, ";");
}

View File

@ -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;

View File

@ -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;

View File

@ -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");

View File

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

View File

@ -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();
}

View File

@ -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");

View File

@ -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 */

View File

@ -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,
};

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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/

View File

@ -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.

View File

@ -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

View File

@ -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;

View File

@ -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".

View File

@ -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

View File

@ -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);
}

View File

@ -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");

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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,
};

View File

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

View File

@ -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)

View File

@ -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,
};

View File

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

View File

@ -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_ */

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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),

View File

@ -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,

View File

@ -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
};

View File

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

View File

@ -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)
{

View File

@ -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

View File

@ -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;

View File

@ -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 */

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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;

View File

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

View File

@ -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);
}

View File

@ -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