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 ...
Unified · Split
| @@ -197,8 +197,24 @@ Description: · 196 unmodified lines | |||
| 197 | Drivers may emit a CHANGE uevent when a password is set or unset | 197 | Drivers may emit a CHANGE uevent when a password is set or unset |
| 198 | userspace may check it again. | 198 | userspace may check it again. |
| 199 | 199 | ||
| 200 | On Dell systems, if Admin password is set, then all BIOS attributes | 200 | On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes |
| 201 | require password validation. | 201 | require password validation. |
| 202 | On Lenovo systems if you change the Admin password the new password is not active until | ||
| 203 | the next boot. | ||
| 204 | |||
| 205 | Lenovo specific class extensions | ||
| 206 | ------------------------------ | ||
| 207 | |||
| 208 | On Lenovo systems the following additional settings are available: | ||
| 209 | |||
| 210 | lenovo_encoding: | ||
| 211 | The encoding method that is used. This can be either "ascii" | ||
| 212 | or "scancode". Default is set to "ascii" | ||
| 213 | |||
| 214 | lenovo_kbdlang: | ||
| 215 | The keyboard language method that is used. This is generally a | ||
| 216 | two char code (e.g. "us", "fr", "gr") and may vary per platform. | ||
| 217 | Default is set to "us" | ||
| 202 | 218 | ||
| 203 | What: /sys/class/firmware-attributes/*/attributes/pending_reboot | 219 | What: /sys/class/firmware-attributes/*/attributes/pending_reboot |
| 204 | Date: February 2021 | 220 | Date: February 2021 |
| @@ -0,0 +1,55 @@ | |||
| 1 | What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type | ||
| 2 | Date: Apr 2021 | ||
| 3 | KernelVersion: 5.13 | ||
| 4 | Contact: "perry.yuan@dell.com>" | ||
| 5 | Description: | ||
| 6 | Display which dell hardware level privacy devices are supported | ||
| 7 | “Dell Privacy” is a set of HW, FW, and SW features to enhance | ||
| 8 | Dell’s commitment to platform privacy for MIC, Camera, and | ||
| 9 | ePrivacy screens. | ||
| 10 | The supported hardware privacy devices are: | ||
| 11 | Attributes: | ||
| 12 | Microphone Mute: | ||
| 13 | Identifies the local microphone can be muted by hardware, no applications | ||
| 14 | is available to capture system mic sound | ||
| 15 | |||
| 16 | Camera Shutter: | ||
| 17 | Identifies camera shutter controlled by hardware, which is a micromechanical | ||
| 18 | shutter assembly that is built onto the camera module to block capturing images | ||
| 19 | from outside the laptop | ||
| 20 | |||
| 21 | supported: | ||
| 22 | The privacy device is supported by this system | ||
| 23 | |||
| 24 | unsupported: | ||
| 25 | The privacy device is not supported on this system | ||
| 26 | |||
| 27 | For example to check which privacy devices are supported: | ||
| 28 | |||
| 29 | # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type | ||
| 30 | [Microphone Mute] [supported] | ||
| 31 | [Camera Shutter] [supported] | ||
| 32 | [ePrivacy Screen] [unsupported] | ||
| 33 | |||
| 34 | What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state | ||
| 35 | Date: Apr 2021 | ||
| 36 | KernelVersion: 5.13 | ||
| 37 | Contact: "perry.yuan@dell.com>" | ||
| 38 | Description: | ||
| 39 | Allow user space to check current dell privacy device state. | ||
| 40 | Describes the Device State class exposed by BIOS which can be | ||
| 41 | consumed by various applications interested in knowing the Privacy | ||
| 42 | feature capabilities | ||
| 43 | Attributes: | ||
| 44 | muted: | ||
| 45 | Identifies the privacy device is turned off and cannot send stream to OS applications | ||
| 46 | |||
| 47 | unmuted: | ||
| 48 | Identifies the privacy device is turned on ,audio or camera driver can get | ||
| 49 | stream from mic and camera module to OS applications | ||
| 50 | |||
| 51 | For example to check all supported current privacy device states: | ||
| 52 | |||
| 53 | # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state | ||
| 54 | [Microphone] [unmuted] | ||
| 55 | [Camera Shutter] [unmuted] | ||
| @@ -1,9 +1,8 @@ | |||
| 1 | .. SPDX-License-Identifier: GPL-2.0+ | 1 | .. SPDX-License-Identifier: GPL-2.0+ |
| 2 | 2 | ||
| 3 | .. |u8| replace:: :c:type:`u8 <u8>` | ||
| 4 | .. |u16| replace:: :c:type:`u16 <u16>` | ||
| 5 | .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>` | 3 | .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>` |
| 6 | .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>` | 4 | .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>` |
| 5 | .. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event <ssam_cdev_event>` | ||
| 7 | 6 | ||
| 8 | ============================== | 7 | ============================== |
| 9 | User-Space EC Interface (cdev) | 8 | User-Space EC Interface (cdev) |
| @@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in · 13 unmodified lines | |||
| 23 | A small python library and scripts for accessing this interface can be found | 22 | A small python library and scripts for accessing this interface can be found |
| 24 | at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. | 23 | at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. |
| 25 | 24 | ||
| 25 | .. contents:: | ||
| 26 | |||
| 27 | |||
| 28 | Receiving Events | ||
| 29 | ================ | ||
| 30 | |||
| 31 | Events can be received by reading from the device-file. The are represented by | ||
| 32 | the |ssam_cdev_event| datatype. | ||
| 33 | |||
| 34 | Before events are available to be read, however, the desired notifiers must be | ||
| 35 | registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in | ||
| 36 | essence, callbacks, called when the EC sends an event. They are, in this | ||
| 37 | interface, associated with a specific target category and device-file-instance. | ||
| 38 | They forward any event of this category to the buffer of the corresponding | ||
| 39 | instance, from which it can then be read. | ||
| 40 | |||
| 41 | Notifiers themselves do not enable events on the EC. Thus, it may additionally | ||
| 42 | be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While | ||
| 43 | notifiers work per-client (i.e. per-device-file-instance), events are enabled | ||
| 44 | globally, for the EC and all of its clients (regardless of userspace or | ||
| 45 | non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` | ||
| 46 | IOCTLs take care of reference counting the events, such that an event is | ||
| 47 | enabled as long as there is a client that has requested it. | ||
| 48 | |||
| 49 | Note that enabled events are not automatically disabled once the client | ||
| 50 | instance is closed. Therefore any client process (or group of processes) should | ||
| 51 | balance their event enable calls with the corresponding event disable calls. It | ||
| 52 | is, however, perfectly valid to enable and disable events on different client | ||
| 53 | instances. For example, it is valid to set up notifiers and read events on | ||
| 54 | client instance ``A``, enable those events on instance ``B`` (note that these | ||
| 55 | will also be received by A since events are enabled/disabled globally), and | ||
| 56 | after no more events are desired, disable the previously enabled events via | ||
| 57 | instance ``C``. | ||
| 58 | |||
| 26 | 59 | ||
| 27 | Controller IOCTLs | 60 | Controller IOCTLs |
| 28 | ================= | 61 | ================= |
| @@ -45,9 +78,33 @@ The following IOCTLs are provided: · 16 unmodified lines | |||
| 45 | - ``REQUEST`` | 78 | - ``REQUEST`` |
| 46 | - Perform synchronous SAM request. | 79 | - Perform synchronous SAM request. |
| 47 | 80 | ||
| 81 | * - ``0xA5`` | ||
| 82 | - ``2`` | ||
| 83 | - ``W`` | ||
| 84 | - ``NOTIF_REGISTER`` | ||
| 85 | - Register event notifier. | ||
| 48 | 86 | ||
| 49 | ``REQUEST`` | 87 | * - ``0xA5`` |
| 50 | ----------- | 88 | - ``3`` |
| 89 | - ``W`` | ||
| 90 | - ``NOTIF_UNREGISTER`` | ||
| 91 | - Unregister event notifier. | ||
| 92 | |||
| 93 | * - ``0xA5`` | ||
| 94 | - ``4`` | ||
| 95 | - ``W`` | ||
| 96 | - ``EVENT_ENABLE`` | ||
| 97 | - Enable event source. | ||
| 98 | |||
| 99 | * - ``0xA5`` | ||
| 100 | - ``5`` | ||
| 101 | - ``W`` | ||
| 102 | - ``EVENT_DISABLE`` | ||
| 103 | - Disable event source. | ||
| 104 | |||
| 105 | |||
| 106 | ``SSAM_CDEV_REQUEST`` | ||
| 107 | --------------------- | ||
| 51 | 108 | ||
| 52 | Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. | 109 | Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. |
| 53 | 110 | ||
| @@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from · 28 unmodified lines | |||
| 82 | inside the IOCTL, but the request ``status`` member may still be negative in | 139 | inside the IOCTL, but the request ``status`` member may still be negative in |
| 83 | case the actual execution of the request failed after it has been submitted. | 140 | case the actual execution of the request failed after it has been submitted. |
| 84 | 141 | ||
| 85 | A full definition of the argument struct is provided below: | 142 | A full definition of the argument struct is provided below. |
| 143 | |||
| 144 | ``SSAM_CDEV_NOTIF_REGISTER`` | ||
| 145 | ---------------------------- | ||
| 146 | |||
| 147 | Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. | ||
| 148 | |||
| 149 | Register a notifier for the event target category specified in the given | ||
| 150 | notifier description with the specified priority. Notifiers registration is | ||
| 151 | required to receive events, but does not enable events themselves. After a | ||
| 152 | notifier for a specific target category has been registered, all events of that | ||
| 153 | category will be forwarded to the userspace client and can then be read from | ||
| 154 | the device file instance. Note that events may have to be enabled, e.g. via the | ||
| 155 | ``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. | ||
| 156 | |||
| 157 | Only one notifier can be registered per target category and client instance. If | ||
| 158 | a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. | ||
| 159 | |||
| 160 | Notifiers will automatically be removed when the device file instance is | ||
| 161 | closed. | ||
| 162 | |||
| 163 | ``SSAM_CDEV_NOTIF_UNREGISTER`` | ||
| 164 | ------------------------------ | ||
| 165 | |||
| 166 | Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. | ||
| 167 | |||
| 168 | Unregisters the notifier associated with the specified target category. The | ||
| 169 | priority field will be ignored by this IOCTL. If no notifier has been | ||
| 170 | registered for this client instance and the given category, this IOCTL will | ||
| 171 | fail with ``-ENOENT``. | ||
| 172 | |||
| 173 | ``SSAM_CDEV_EVENT_ENABLE`` | ||
| 174 | -------------------------- | ||
| 175 | |||
| 176 | Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. | ||
| 177 | |||
| 178 | Enable the event associated with the given event descriptor. | ||
| 179 | |||
| 180 | Note that this call will not register a notifier itself, it will only enable | ||
| 181 | events on the controller. If you want to receive events by reading from the | ||
| 182 | device file, you will need to register the corresponding notifier(s) on that | ||
| 183 | instance. | ||
| 184 | |||
| 185 | Events are not automatically disabled when the device file is closed. This must | ||
| 186 | be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. | ||
| 187 | |||
| 188 | ``SSAM_CDEV_EVENT_DISABLE`` | ||
| 189 | --------------------------- | ||
| 190 | |||
| 191 | Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. | ||
| 192 | |||
| 193 | Disable the event associated with the given event descriptor. | ||
| 194 | |||
| 195 | Note that this will not unregister any notifiers. Events may still be received | ||
| 196 | and forwarded to user-space after this call. The only safe way of stopping | ||
| 197 | events from being received is unregistering all previously registered | ||
| 198 | notifiers. | ||
| 199 | |||
| 200 | |||
| 201 | Structures and Enums | ||
| 202 | ==================== | ||
| 86 | 203 | ||
| 87 | .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h | 204 | .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h |
| @@ -325,7 +325,7 @@ Code Seq# Include File Comments · 324 unmodified lines | |||
| 325 | 0xA3 90-9F linux/dtlk.h | 325 | 0xA3 90-9F linux/dtlk.h |
| 326 | 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem | 326 | 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem |
| 327 | 0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org> | 327 | 0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org> |
| 328 | 0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator | 328 | 0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator |
| 329 | <mailto:luzmaximilian@gmail.com> | 329 | <mailto:luzmaximilian@gmail.com> |
| 330 | 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver | 330 | 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver |
| 331 | <mailto:luzmaximilian@gmail.com> | 331 | <mailto:luzmaximilian@gmail.com> |
| @@ -5187,7 +5187,14 @@ DELL WMI NOTIFICATIONS DRIVER · 5186 unmodified lines | |||
| 5187 | M: Matthew Garrett <mjg59@srcf.ucam.org> | 5187 | M: Matthew Garrett <mjg59@srcf.ucam.org> |
| 5188 | M: Pali Rohár <pali@kernel.org> | 5188 | M: Pali Rohár <pali@kernel.org> |
| 5189 | S: Maintained | 5189 | S: Maintained |
| 5190 | F: drivers/platform/x86/dell/dell-wmi.c | 5190 | F: drivers/platform/x86/dell/dell-wmi-base.c |
| 5191 | |||
| 5192 | DELL WMI HARDWARE PRIVACY SUPPORT | ||
| 5193 | M: Perry Yuan <Perry.Yuan@dell.com> | ||
| 5194 | L: Dell.Client.Kernel@dell.com | ||
| 5195 | L: platform-driver-x86@vger.kernel.org | ||
| 5196 | S: Maintained | ||
| 5197 | F: drivers/platform/x86/dell/dell-wmi-privacy.c | ||
| 5191 | 5198 | ||
| 5192 | DELTA ST MEDIA DRIVER | 5199 | DELTA ST MEDIA DRIVER |
| 5193 | M: Hugues Fruchet <hugues.fruchet@foss.st.com> | 5200 | M: Hugues Fruchet <hugues.fruchet@foss.st.com> |
| @@ -9397,6 +9404,11 @@ S: Maintained · 4203 unmodified lines | |||
| 9397 | F: arch/x86/include/asm/intel_scu_ipc.h | 9404 | F: arch/x86/include/asm/intel_scu_ipc.h |
| 9398 | F: drivers/platform/x86/intel_scu_* | 9405 | F: drivers/platform/x86/intel_scu_* |
| 9399 | 9406 | ||
| 9407 | INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER | ||
| 9408 | M: Daniel Scally <djrscally@gmail.com> | ||
| 9409 | S: Maintained | ||
| 9410 | F: drivers/platform/x86/intel/int3472/ | ||
| 9411 | |||
| 9400 | INTEL SPEED SELECT TECHNOLOGY | 9412 | INTEL SPEED SELECT TECHNOLOGY |
| 9401 | M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> | 9413 | M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> |
| 9402 | L: platform-driver-x86@vger.kernel.org | 9414 | L: platform-driver-x86@vger.kernel.org |
| @@ -9417,7 +9429,7 @@ F: include/linux/firmware/intel/stratix10-smc.h · 14 unmodified lines | |||
| 9417 | F: include/linux/firmware/intel/stratix10-svc-client.h | 9429 | F: include/linux/firmware/intel/stratix10-svc-client.h |
| 9418 | 9430 | ||
| 9419 | INTEL TELEMETRY DRIVER | 9431 | INTEL TELEMETRY DRIVER |
| 9420 | M: Rajneesh Bhardwaj <rajneesh.bhardwaj@linux.intel.com> | 9432 | M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com> |
| 9421 | M: "David E. Box" <david.e.box@linux.intel.com> | 9433 | M: "David E. Box" <david.e.box@linux.intel.com> |
| 9422 | L: platform-driver-x86@vger.kernel.org | 9434 | L: platform-driver-x86@vger.kernel.org |
| 9423 | S: Maintained | 9435 | S: Maintained |
| @@ -12209,7 +12221,7 @@ M: Maximilian Luz <luzmaximilian@gmail.com> · 2785 unmodified lines | |||
| 12209 | L: platform-driver-x86@vger.kernel.org | 12221 | L: platform-driver-x86@vger.kernel.org |
| 12210 | S: Maintained | 12222 | S: Maintained |
| 12211 | W: https://github.com/linux-surface/surface-aggregator-module | 12223 | W: https://github.com/linux-surface/surface-aggregator-module |
| 12212 | C: irc://chat.freenode.net/##linux-surface | 12224 | C: irc://irc.libera.chat/linux-surface |
| 12213 | F: Documentation/driver-api/surface_aggregator/ | 12225 | F: Documentation/driver-api/surface_aggregator/ |
| 12214 | F: drivers/platform/surface/aggregator/ | 12226 | F: drivers/platform/surface/aggregator/ |
| 12215 | F: drivers/platform/surface/surface_acpi_notify.c | 12227 | F: drivers/platform/surface/surface_acpi_notify.c |
| @@ -18215,6 +18227,13 @@ W: http://thinkwiki.org/wiki/Ibm-acpi · 5999 unmodified lines | |||
| 18215 | T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git | 18227 | T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git |
| 18216 | F: drivers/platform/x86/thinkpad_acpi.c | 18228 | F: drivers/platform/x86/thinkpad_acpi.c |
| 18217 | 18229 | ||
| 18230 | THINKPAD LMI DRIVER | ||
| 18231 | M: Mark Pearson <markpearson@lenovo.com> | ||
| 18232 | L: platform-driver-x86@vger.kernel.org | ||
| 18233 | S: Maintained | ||
| 18234 | F: Documentation/ABI/testing/sysfs-class-firmware-attributes | ||
| 18235 | F: drivers/platform/x86/think-lmi.? | ||
| 18236 | |||
| 18218 | THUNDERBOLT DMA TRAFFIC TEST DRIVER | 18237 | THUNDERBOLT DMA TRAFFIC TEST DRIVER |
| 18219 | M: Isaac Hazan <isaac.hazan@intel.com> | 18238 | M: Isaac Hazan <isaac.hazan@intel.com> |
| 18220 | L: linux-usb@vger.kernel.org | 18239 | L: linux-usb@vger.kernel.org |
| @@ -52,7 +52,7 @@ endif # PMIC_OPREGION · 51 unmodified lines | |||
| 52 | 52 | ||
| 53 | config TPS68470_PMIC_OPREGION | 53 | config TPS68470_PMIC_OPREGION |
| 54 | bool "ACPI operation region support for TPS68470 PMIC" | 54 | bool "ACPI operation region support for TPS68470 PMIC" |
| 55 | depends on MFD_TPS68470 | 55 | depends on INTEL_SKL_INT3472 |
| 56 | help | 56 | help |
| 57 | This config adds ACPI operation region support for TI TPS68470 PMIC. | 57 | This config adds ACPI operation region support for TI TPS68470 PMIC. |
| 58 | TPS68470 device is an advanced power management unit that powers | 58 | TPS68470 device is an advanced power management unit that powers |
| @@ -6,6 +6,7 @@ · 5 unmodified lines | |||
| 6 | // Chanwoo Choi <cw00.choi@samsung.com> | 6 | // Chanwoo Choi <cw00.choi@samsung.com> |
| 7 | // Krzysztof Kozlowski <krzk@kernel.org> | 7 | // Krzysztof Kozlowski <krzk@kernel.org> |
| 8 | 8 | ||
| 9 | #include <linux/devm-helpers.h> | ||
| 9 | #include <linux/kernel.h> | 10 | #include <linux/kernel.h> |
| 10 | #include <linux/module.h> | 11 | #include <linux/module.h> |
| 11 | #include <linux/i2c.h> | 12 | #include <linux/i2c.h> |
| @@ -673,7 +674,10 @@ static int max14577_muic_probe(struct platform_device *pdev) · 661 unmodified lines | |||
| 673 | platform_set_drvdata(pdev, info); | 674 | platform_set_drvdata(pdev, info); |
| 674 | mutex_init(&info->mutex); | 675 | mutex_init(&info->mutex); |
| 675 | 676 | ||
| 676 | INIT_WORK(&info->irq_work, max14577_muic_irq_work); | 677 | ret = devm_work_autocancel(&pdev->dev, &info->irq_work, |
| 678 | max14577_muic_irq_work); | ||
| 679 | if (ret) | ||
| 680 | return ret; | ||
| 677 | 681 | ||
| 678 | switch (max14577->dev_type) { | 682 | switch (max14577->dev_type) { |
| 679 | case MAXIM_DEVICE_TYPE_MAX77836: | 683 | case MAXIM_DEVICE_TYPE_MAX77836: |
| @@ -766,15 +770,6 @@ static int max14577_muic_probe(struct platform_device *pdev) · 86 unmodified lines | |||
| 766 | return ret; | 770 | return ret; |
| 767 | } | 771 | } |
| 768 | 772 | ||
| 769 | static int max14577_muic_remove(struct platform_device *pdev) | ||
| 770 | { | ||
| 771 | struct max14577_muic_info *info = platform_get_drvdata(pdev); | ||
| 772 | |||
| 773 | cancel_work_sync(&info->irq_work); | ||
| 774 | |||
| 775 | return 0; | ||
| 776 | } | ||
| 777 | |||
| 778 | static const struct platform_device_id max14577_muic_id[] = { | 773 | static const struct platform_device_id max14577_muic_id[] = { |
| 779 | { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, | 774 | { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, |
| 780 | { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, | 775 | { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, |
| @@ -797,7 +792,6 @@ static struct platform_driver max14577_muic_driver = { · 16 unmodified lines | |||
| 797 | .of_match_table = of_max14577_muic_dt_match, | 792 | .of_match_table = of_max14577_muic_dt_match, |
| 798 | }, | 793 | }, |
| 799 | .probe = max14577_muic_probe, | 794 | .probe = max14577_muic_probe, |
| 800 | .remove = max14577_muic_remove, | ||
| 801 | .id_table = max14577_muic_id, | 795 | .id_table = max14577_muic_id, |
| 802 | }; | 796 | }; |
| 803 | 797 | ||
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | // Copyright (C) 2012 Samsung Electrnoics | 5 | // Copyright (C) 2012 Samsung Electrnoics |
| 6 | // Chanwoo Choi <cw00.choi@samsung.com> | 6 | // Chanwoo Choi <cw00.choi@samsung.com> |
| 7 | 7 | ||
| 8 | #include <linux/devm-helpers.h> | ||
| 8 | #include <linux/kernel.h> | 9 | #include <linux/kernel.h> |
| 9 | #include <linux/module.h> | 10 | #include <linux/module.h> |
| 10 | #include <linux/i2c.h> | 11 | #include <linux/i2c.h> |
| @@ -1127,7 +1128,10 @@ static int max77693_muic_probe(struct platform_device *pdev) · 1116 unmodified lines | |||
| 1127 | platform_set_drvdata(pdev, info); | 1128 | platform_set_drvdata(pdev, info); |
| 1128 | mutex_init(&info->mutex); | 1129 | mutex_init(&info->mutex); |
| 1129 | 1130 | ||
| 1130 | INIT_WORK(&info->irq_work, max77693_muic_irq_work); | 1131 | ret = devm_work_autocancel(&pdev->dev, &info->irq_work, |
| 1132 | max77693_muic_irq_work); | ||
| 1133 | if (ret) | ||
| 1134 | return ret; | ||
| 1131 | 1135 | ||
| 1132 | /* Support irq domain for MAX77693 MUIC device */ | 1136 | /* Support irq domain for MAX77693 MUIC device */ |
| 1133 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { | 1137 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { |
| @@ -1254,22 +1258,11 @@ static int max77693_muic_probe(struct platform_device *pdev) · 120 unmodified lines | |||
| 1254 | return ret; | 1258 | return ret; |
| 1255 | } | 1259 | } |
| 1256 | 1260 | ||
| 1257 | static int max77693_muic_remove(struct platform_device *pdev) | ||
| 1258 | { | ||
| 1259 | struct max77693_muic_info *info = platform_get_drvdata(pdev); | ||
| 1260 | |||
| 1261 | cancel_work_sync(&info->irq_work); | ||
| 1262 | input_unregister_device(info->dock); | ||
| 1263 | |||
| 1264 | return 0; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | static struct platform_driver max77693_muic_driver = { | 1261 | static struct platform_driver max77693_muic_driver = { |
| 1268 | .driver = { | 1262 | .driver = { |
| 1269 | .name = DEV_NAME, | 1263 | .name = DEV_NAME, |
| 1270 | }, | 1264 | }, |
| 1271 | .probe = max77693_muic_probe, | 1265 | .probe = max77693_muic_probe, |
| 1272 | .remove = max77693_muic_remove, | ||
| 1273 | }; | 1266 | }; |
| 1274 | 1267 | ||
| 1275 | module_platform_driver(max77693_muic_driver); | 1268 | module_platform_driver(max77693_muic_driver); |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | // Copyright (C) 2012 Samsung Electronics | 5 | // Copyright (C) 2012 Samsung Electronics |
| 6 | // Donggeun Kim <dg77.kim@samsung.com> | 6 | // Donggeun Kim <dg77.kim@samsung.com> |
| 7 | 7 | ||
| 8 | #include <linux/devm-helpers.h> | ||
| 8 | #include <linux/kernel.h> | 9 | #include <linux/kernel.h> |
| 9 | #include <linux/module.h> | 10 | #include <linux/module.h> |
| 10 | #include <linux/i2c.h> | 11 | #include <linux/i2c.h> |
| @@ -650,27 +651,30 @@ static int max8997_muic_probe(struct platform_device *pdev) · 639 unmodified lines | |||
| 650 | mutex_init(&info->mutex); | 651 | mutex_init(&info->mutex); |
| 651 | 652 | ||
| 652 | INIT_WORK(&info->irq_work, max8997_muic_irq_work); | 653 | INIT_WORK(&info->irq_work, max8997_muic_irq_work); |
| 654 | ret = devm_work_autocancel(&pdev->dev, &info->irq_work, | ||
| 655 | max8997_muic_irq_work); | ||
| 656 | if (ret) | ||
| 657 | return ret; | ||
| 653 | 658 | ||
| 654 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { | 659 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { |
| 655 | struct max8997_muic_irq *muic_irq = &muic_irqs[i]; | 660 | struct max8997_muic_irq *muic_irq = &muic_irqs[i]; |
| 656 | unsigned int virq = 0; | 661 | unsigned int virq = 0; |
| 657 | 662 | ||
| 658 | virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq); | 663 | virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq); |
| 659 | if (!virq) { | 664 | if (!virq) |
| 660 | ret = -EINVAL; | 665 | return -EINVAL; |
| 661 | goto err_irq; | 666 | |
| 662 | } | ||
| 663 | muic_irq->virq = virq; | 667 | muic_irq->virq = virq; |
| 664 | 668 | ||
| 665 | ret = request_threaded_irq(virq, NULL, | 669 | ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, |
| 666 | max8997_muic_irq_handler, | 670 | max8997_muic_irq_handler, |
| 667 | IRQF_NO_SUSPEND, | 671 | IRQF_NO_SUSPEND, |
| 668 | muic_irq->name, info); | 672 | muic_irq->name, info); |
| 669 | if (ret) { | 673 | if (ret) { |
| 670 | dev_err(&pdev->dev, | 674 | dev_err(&pdev->dev, |
| 671 | "failed: irq request (IRQ: %d, error :%d)\n", | 675 | "failed: irq request (IRQ: %d, error :%d)\n", |
| 672 | muic_irq->irq, ret); | 676 | muic_irq->irq, ret); |
| 673 | goto err_irq; | 677 | return ret; |
| 674 | } | 678 | } |
| 675 | } | 679 | } |
| 676 | 680 | ||
| @@ -678,14 +682,13 @@ static int max8997_muic_probe(struct platform_device *pdev) · 1 unmodified line | |||
| 678 | info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); | 682 | info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); |
| 679 | if (IS_ERR(info->edev)) { | 683 | if (IS_ERR(info->edev)) { |
| 680 | dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); | 684 | dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); |
| 681 | ret = PTR_ERR(info->edev); | 685 | return PTR_ERR(info->edev); |
| 682 | goto err_irq; | ||
| 683 | } | 686 | } |
| 684 | 687 | ||
| 685 | ret = devm_extcon_dev_register(&pdev->dev, info->edev); | 688 | ret = devm_extcon_dev_register(&pdev->dev, info->edev); |
| 686 | if (ret) { | 689 | if (ret) { |
| 687 | dev_err(&pdev->dev, "failed to register extcon device\n"); | 690 | dev_err(&pdev->dev, "failed to register extcon device\n"); |
| 688 | goto err_irq; | 691 | return ret; |
| 689 | } | 692 | } |
| 690 | 693 | ||
| 691 | if (pdata && pdata->muic_pdata) { | 694 | if (pdata && pdata->muic_pdata) { |
| @@ -756,23 +759,6 @@ static int max8997_muic_probe(struct platform_device *pdev) · 64 unmodified lines | |||
| 756 | delay_jiffies); | 759 | delay_jiffies); |
| 757 | 760 | ||
| 758 | return 0; | 761 | return 0; |
| 759 | |||
| 760 | err_irq: | ||
| 761 | while (--i >= 0) | ||
| 762 | free_irq(muic_irqs[i].virq, info); | ||
| 763 | return ret; | ||
| 764 | } | ||
| 765 | |||
| 766 | static int max8997_muic_remove(struct platform_device *pdev) | ||
| 767 | { | ||
| 768 | struct max8997_muic_info *info = platform_get_drvdata(pdev); | ||
| 769 | int i; | ||
| 770 | |||
| 771 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) | ||
| 772 | free_irq(muic_irqs[i].virq, info); | ||
| 773 | cancel_work_sync(&info->irq_work); | ||
| 774 | |||
| 775 | return 0; | ||
| 776 | } | 762 | } |
| 777 | 763 | ||
| 778 | static struct platform_driver max8997_muic_driver = { | 764 | static struct platform_driver max8997_muic_driver = { |
| @@ -780,7 +766,6 @@ static struct platform_driver max8997_muic_driver = { · 1 unmodified line | |||
| 780 | .name = DEV_NAME, | 766 | .name = DEV_NAME, |
| 781 | }, | 767 | }, |
| 782 | .probe = max8997_muic_probe, | 768 | .probe = max8997_muic_probe, |
| 783 | .remove = max8997_muic_remove, | ||
| 784 | }; | 769 | }; |
| 785 | 770 | ||
| 786 | module_platform_driver(max8997_muic_driver); | 771 | module_platform_driver(max8997_muic_driver); |
| @@ -1367,7 +1367,7 @@ config GPIO_TPS65912 · 1366 unmodified lines | |||
| 1367 | 1367 | ||
| 1368 | config GPIO_TPS68470 | 1368 | config GPIO_TPS68470 |
| 1369 | bool "TPS68470 GPIO" | 1369 | bool "TPS68470 GPIO" |
| 1370 | depends on MFD_TPS68470 | 1370 | depends on INTEL_SKL_INT3472 |
| 1371 | help | 1371 | help |
| 1372 | Select this option to enable GPIO driver for the TPS68470 | 1372 | Select this option to enable GPIO driver for the TPS68470 |
| 1373 | chip family. | 1373 | chip family. |
| @@ -339,8 +339,6 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) · 338 unmodified lines | |||
| 339 | if (!cg) | 339 | if (!cg) |
| 340 | return -ENOMEM; | 340 | return -ENOMEM; |
| 341 | 341 | ||
| 342 | platform_set_drvdata(pdev, cg); | ||
| 343 | |||
| 344 | mutex_init(&cg->buslock); | 342 | mutex_init(&cg->buslock); |
| 345 | cg->chip.label = KBUILD_MODNAME; | 343 | cg->chip.label = KBUILD_MODNAME; |
| 346 | cg->chip.direction_input = crystalcove_gpio_dir_in; | 344 | cg->chip.direction_input = crystalcove_gpio_dir_in; |
| @@ -372,13 +370,7 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) · 25 unmodified lines | |||
| 372 | return retval; | 370 | return retval; |
| 373 | } | 371 | } |
| 374 | 372 | ||
| 375 | retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); | 373 | return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); |
| 376 | if (retval) { | ||
| 377 | dev_warn(&pdev->dev, "add gpio chip error: %d\n", retval); | ||
| 378 | return retval; | ||
| 379 | } | ||
| 380 | |||
| 381 | return 0; | ||
| 382 | } | 374 | } |
| 383 | 375 | ||
| 384 | static struct platform_driver crystalcove_gpio_driver = { | 376 | static struct platform_driver crystalcove_gpio_driver = { |
| @@ -99,19 +99,14 @@ struct wcove_gpio { · 98 unmodified lines | |||
| 99 | bool set_irq_mask; | 99 | bool set_irq_mask; |
| 100 | }; | 100 | }; |
| 101 | 101 | ||
| 102 | static inline int to_reg(int gpio, enum ctrl_register reg_type) | 102 | static inline int to_reg(int gpio, enum ctrl_register type) |
| 103 | { | 103 | { |
| 104 | unsigned int reg; | 104 | unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE; |
| 105 | 105 | ||
| 106 | if (gpio >= WCOVE_GPIO_NUM) | 106 | if (gpio >= WCOVE_GPIO_NUM) |
| 107 | return -EOPNOTSUPP; | 107 | return -EOPNOTSUPP; |
| 108 | 108 | ||
| 109 | if (reg_type == CTRL_IN) | 109 | return reg + gpio; |
| 110 | reg = GPIO_IN_CTRL_BASE + gpio; | ||
| 111 | else | ||
| 112 | reg = GPIO_OUT_CTRL_BASE + gpio; | ||
| 113 | |||
| 114 | return reg; | ||
| 115 | } | 110 | } |
| 116 | 111 | ||
| 117 | static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask) | 112 | 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) · 11 unmodified lines | |||
| 129 | return reg; | 124 | return reg; |
| 130 | } | 125 | } |
| 131 | 126 | ||
| 132 | static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio) | 127 | static void wcove_update_irq_mask(struct wcove_gpio *wg, irq_hw_number_t gpio) |
| 133 | { | 128 | { |
| 134 | unsigned int mask, reg = to_ireg(gpio, IRQ_MASK, &mask); | 129 | unsigned int mask, reg = to_ireg(gpio, IRQ_MASK, &mask); |
| 135 | 130 | ||
| @@ -139,13 +134,10 @@ static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio) · 3 unmodified lines | |||
| 139 | regmap_clear_bits(wg->regmap, reg, mask); | 134 | regmap_clear_bits(wg->regmap, reg, mask); |
| 140 | } | 135 | } |
| 141 | 136 | ||
| 142 | static void wcove_update_irq_ctrl(struct wcove_gpio *wg, int gpio) | 137 | static void wcove_update_irq_ctrl(struct wcove_gpio *wg, irq_hw_number_t gpio) |
| 143 | { | 138 | { |
| 144 | int reg = to_reg(gpio, CTRL_IN); | 139 | int reg = to_reg(gpio, CTRL_IN); |
| 145 | 140 | ||
| 146 | if (reg < 0) | ||
| 147 | return; | ||
| 148 | |||
| 149 | regmap_update_bits(wg->regmap, reg, CTLI_INTCNT_BE, wg->intcnt); | 141 | regmap_update_bits(wg->regmap, reg, CTLI_INTCNT_BE, wg->intcnt); |
| 150 | } | 142 | } |
| 151 | 143 | ||
| @@ -248,8 +240,9 @@ static int wcove_irq_type(struct irq_data *data, unsigned int type) · 96 unmodified lines | |||
| 248 | { | 240 | { |
| 249 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | 241 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 250 | struct wcove_gpio *wg = gpiochip_get_data(chip); | 242 | struct wcove_gpio *wg = gpiochip_get_data(chip); |
| 243 | irq_hw_number_t gpio = irqd_to_hwirq(data); | ||
| 251 | 244 | ||
| 252 | if (data->hwirq >= WCOVE_GPIO_NUM) | 245 | if (gpio >= WCOVE_GPIO_NUM) |
| 253 | return 0; | 246 | return 0; |
| 254 | 247 | ||
| 255 | switch (type) { | 248 | switch (type) { |
| @@ -286,7 +279,7 @@ static void wcove_bus_sync_unlock(struct irq_data *data) · 30 unmodified lines | |||
| 286 | { | 279 | { |
| 287 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | 280 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 288 | struct wcove_gpio *wg = gpiochip_get_data(chip); | 281 | struct wcove_gpio *wg = gpiochip_get_data(chip); |
| 289 | int gpio = data->hwirq; | 282 | irq_hw_number_t gpio = irqd_to_hwirq(data); |
| 290 | 283 | ||
| 291 | if (wg->update & UPDATE_IRQ_TYPE) | 284 | if (wg->update & UPDATE_IRQ_TYPE) |
| 292 | wcove_update_irq_ctrl(wg, gpio); | 285 | wcove_update_irq_ctrl(wg, gpio); |
| @@ -301,8 +294,9 @@ static void wcove_irq_unmask(struct irq_data *data) · 8 unmodified lines | |||
| 301 | { | 294 | { |
| 302 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | 295 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 303 | struct wcove_gpio *wg = gpiochip_get_data(chip); | 296 | struct wcove_gpio *wg = gpiochip_get_data(chip); |
| 297 | irq_hw_number_t gpio = irqd_to_hwirq(data); | ||
| 304 | 298 | ||
| 305 | if (data->hwirq >= WCOVE_GPIO_NUM) | 299 | if (gpio >= WCOVE_GPIO_NUM) |
| 306 | return; | 300 | return; |
| 307 | 301 | ||
| 308 | wg->set_irq_mask = false; | 302 | wg->set_irq_mask = false; |
| @@ -313,8 +307,9 @@ static void wcove_irq_mask(struct irq_data *data) · 4 unmodified lines | |||
| 313 | { | 307 | { |
| 314 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); | 308 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 315 | struct wcove_gpio *wg = gpiochip_get_data(chip); | 309 | struct wcove_gpio *wg = gpiochip_get_data(chip); |
| 310 | irq_hw_number_t gpio = irqd_to_hwirq(data); | ||
| 316 | 311 | ||
| 317 | if (data->hwirq >= WCOVE_GPIO_NUM) | 312 | if (gpio >= WCOVE_GPIO_NUM) |
| 318 | return; | 313 | return; |
| 319 | 314 | ||
| 320 | wg->set_irq_mask = true; | 315 | wg->set_irq_mask = true; |
| @@ -369,8 +364,7 @@ static irqreturn_t wcove_gpio_irq_handler(int irq, void *data) · 48 unmodified lines | |||
| 369 | return IRQ_HANDLED; | 364 | return IRQ_HANDLED; |
| 370 | } | 365 | } |
| 371 | 366 | ||
| 372 | static void wcove_gpio_dbg_show(struct seq_file *s, | 367 | static void wcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) |
| 373 | struct gpio_chip *chip) | ||
| 374 | { | 368 | { |
| 375 | unsigned int ctlo, ctli, irq_mask, irq_status; | 369 | unsigned int ctlo, ctli, irq_mask, irq_status; |
| 376 | struct wcove_gpio *wg = gpiochip_get_data(chip); | 370 | struct wcove_gpio *wg = gpiochip_get_data(chip); |
| @@ -379,10 +373,15 @@ static void wcove_gpio_dbg_show(struct seq_file *s, · 2 unmodified lines | |||
| 379 | for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) { | 373 | for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) { |
| 380 | ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo); | 374 | ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo); |
| 381 | ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_IN), &ctli); | 375 | ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_IN), &ctli); |
| 376 | if (ret) { | ||
| 377 | dev_err(wg->dev, "Failed to read registers: CTRL out/in\n"); | ||
| 378 | break; | ||
| 379 | } | ||
| 380 | |||
| 382 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask); | 381 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask); |
| 383 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status); | 382 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status); |
| 384 | if (ret) { | 383 | if (ret) { |
| 385 | pr_err("Failed to read registers: ctrl out/in or irq status/mask\n"); | 384 | dev_err(wg->dev, "Failed to read registers: IRQ status/mask\n"); |
| 386 | break; | 385 | break; |
| 387 | } | 386 | } |
| 388 | 387 | ||
| @@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) · 127 unmodified lines | |||
| 128 | return gpiochip_get_desc(chip, pin); | 128 | return gpiochip_get_desc(chip, pin); |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | /** | ||
| 132 | * acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and | ||
| 133 | * hold a refcount to the GPIO device. | ||
| 134 | * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") | ||
| 135 | * @pin: ACPI GPIO pin number (0-based, controller-relative) | ||
| 136 | * @label: Label to pass to gpiod_request() | ||
| 137 | * | ||
| 138 | * This function is a simple pass-through to acpi_get_gpiod(), except that | ||
| 139 | * as it is intended for use outside of the GPIO layer (in a similar fashion to | ||
| 140 | * gpiod_get_index() for example) it also holds a reference to the GPIO device. | ||
| 141 | */ | ||
| 142 | struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label) | ||
| 143 | { | ||
| 144 | struct gpio_desc *gpio; | ||
| 145 | int ret; | ||
| 146 | |||
| 147 | gpio = acpi_get_gpiod(path, pin); | ||
| 148 | if (IS_ERR(gpio)) | ||
| 149 | return gpio; | ||
| 150 | |||
| 151 | ret = gpiod_request(gpio, label); | ||
| 152 | if (ret) | ||
| 153 | return ERR_PTR(ret); | ||
| 154 | |||
| 155 | return gpio; | ||
| 156 | } | ||
| 157 | EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod); | ||
| 158 | |||
| 131 | static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) | 159 | static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) |
| 132 | { | 160 | { |
| 133 | struct acpi_gpio_event *event = data; | 161 | struct acpi_gpio_event *event = data; |
| @@ -168,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, · 34 unmodified lines | |||
| 168 | } | 196 | } |
| 169 | EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); | 197 | EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); |
| 170 | 198 | ||
| 199 | /** | ||
| 200 | * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO | ||
| 201 | * I/O resource or return False if not. | ||
| 202 | * @ares: Pointer to the ACPI resource to fetch | ||
| 203 | * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer | ||
| 204 | */ | ||
| 205 | bool acpi_gpio_get_io_resource(struct acpi_resource *ares, | ||
| 206 | struct acpi_resource_gpio **agpio) | ||
| 207 | { | ||
| 208 | struct acpi_resource_gpio *gpio; | ||
| 209 | |||
| 210 | if (ares->type != ACPI_RESOURCE_TYPE_GPIO) | ||
| 211 | return false; | ||
| 212 | |||
| 213 | gpio = &ares->data.gpio; | ||
| 214 | if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) | ||
| 215 | return false; | ||
| 216 | |||
| 217 | *agpio = gpio; | ||
| 218 | return true; | ||
| 219 | } | ||
| 220 | EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource); | ||
| 221 | |||
| 171 | static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, | 222 | static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, |
| 172 | struct acpi_gpio_event *event) | 223 | struct acpi_gpio_event *event) |
| 173 | { | 224 | { |
| @@ -178,51 +178,6 @@ static const unsigned long goodix_irq_flags[] = { · 177 unmodified lines | |||
| 178 | IRQ_TYPE_LEVEL_HIGH, | 178 | IRQ_TYPE_LEVEL_HIGH, |
| 179 | }; | 179 | }; |
| 180 | 180 | ||
| 181 | /* | ||
| 182 | * Those tablets have their coordinates origin at the bottom right | ||
| 183 | * of the tablet, as if rotated 180 degrees | ||
| 184 | */ | ||
| 185 | static const struct dmi_system_id rotated_screen[] = { | ||
| 186 | #if defined(CONFIG_DMI) && defined(CONFIG_X86) | ||
| 187 | { | ||
| 188 | .ident = "Teclast X89", | ||
| 189 | .matches = { | ||
| 190 | /* tPAD is too generic, also match on bios date */ | ||
| 191 | DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), | ||
| 192 | DMI_MATCH(DMI_BOARD_NAME, "tPAD"), | ||
| 193 | DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"), | ||
| 194 | }, | ||
| 195 | }, | ||
| 196 | { | ||
| 197 | .ident = "Teclast X98 Pro", | ||
| 198 | .matches = { | ||
| 199 | /* | ||
| 200 | * Only match BIOS date, because the manufacturers | ||
| 201 | * BIOS does not report the board name at all | ||
| 202 | * (sometimes)... | ||
| 203 | */ | ||
| 204 | DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), | ||
| 205 | DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"), | ||
| 206 | }, | ||
| 207 | }, | ||
| 208 | { | ||
| 209 | .ident = "WinBook TW100", | ||
| 210 | .matches = { | ||
| 211 | DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), | ||
| 212 | DMI_MATCH(DMI_PRODUCT_NAME, "TW100") | ||
| 213 | } | ||
| 214 | }, | ||
| 215 | { | ||
| 216 | .ident = "WinBook TW700", | ||
| 217 | .matches = { | ||
| 218 | DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), | ||
| 219 | DMI_MATCH(DMI_PRODUCT_NAME, "TW700") | ||
| 220 | }, | ||
| 221 | }, | ||
| 222 | #endif | ||
| 223 | {} | ||
| 224 | }; | ||
| 225 | |||
| 226 | static const struct dmi_system_id nine_bytes_report[] = { | 181 | static const struct dmi_system_id nine_bytes_report[] = { |
| 227 | #if defined(CONFIG_DMI) && defined(CONFIG_X86) | 182 | #if defined(CONFIG_DMI) && defined(CONFIG_X86) |
| 228 | { | 183 | { |
| @@ -1123,13 +1078,6 @@ static int goodix_configure_dev(struct goodix_ts_data *ts) · 894 unmodified lines | |||
| 1123 | ABS_MT_POSITION_Y, ts->prop.max_y); | 1078 | ABS_MT_POSITION_Y, ts->prop.max_y); |
| 1124 | } | 1079 | } |
| 1125 | 1080 | ||
| 1126 | if (dmi_check_system(rotated_screen)) { | ||
| 1127 | ts->prop.invert_x = true; | ||
| 1128 | ts->prop.invert_y = true; | ||
| 1129 | dev_dbg(&ts->client->dev, | ||
| 1130 | "Applying '180 degrees rotated screen' quirk\n"); | ||
| 1131 | } | ||
| 1132 | |||
| 1133 | if (dmi_check_system(nine_bytes_report)) { | 1081 | if (dmi_check_system(nine_bytes_report)) { |
| 1134 | ts->contact_size = 9; | 1082 | ts->contact_size = 9; |
| 1135 | 1083 | ||
| @@ -1499,24 +1499,6 @@ config MFD_TPS65217 · 1498 unmodified lines | |||
| 1499 | This driver can also be built as a module. If so, the module | 1499 | This driver can also be built as a module. If so, the module |
| 1500 | will be called tps65217. | 1500 | will be called tps65217. |
| 1501 | 1501 | ||
| 1502 | config MFD_TPS68470 | ||
| 1503 | bool "TI TPS68470 Power Management / LED chips" | ||
| 1504 | depends on ACPI && PCI && I2C=y | ||
| 1505 | depends on I2C_DESIGNWARE_PLATFORM=y | ||
| 1506 | select MFD_CORE | ||
| 1507 | select REGMAP_I2C | ||
| 1508 | help | ||
| 1509 | If you say yes here you get support for the TPS68470 series of | ||
| 1510 | Power Management / LED chips. | ||
| 1511 | |||
| 1512 | These include voltage regulators, LEDs and other features | ||
| 1513 | that are often used in portable devices. | ||
| 1514 | |||
| 1515 | This option is a bool as it provides an ACPI operation | ||
| 1516 | region, which must be available before any of the devices | ||
| 1517 | using this are probed. This option also configures the | ||
| 1518 | designware-i2c driver to be built-in, for the same reason. | ||
| 1519 | |||
| 1520 | config MFD_TI_LP873X | 1502 | config MFD_TI_LP873X |
| 1521 | tristate "TI LP873X Power Management IC" | 1503 | tristate "TI LP873X Power Management IC" |
| 1522 | depends on I2C | 1504 | depends on I2C |
| @@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o · 104 unmodified lines | |||
| 105 | obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o | 105 | obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o |
| 106 | obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o | 106 | obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o |
| 107 | obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o | 107 | obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o |
| 108 | obj-$(CONFIG_MFD_TPS68470) += tps68470.o | ||
| 109 | obj-$(CONFIG_MFD_TPS80031) += tps80031.o | 108 | obj-$(CONFIG_MFD_TPS80031) += tps80031.o |
| 110 | obj-$(CONFIG_MENELAUS) += menelaus.o | 109 | obj-$(CONFIG_MENELAUS) += menelaus.o |
| 111 | 110 | ||
| @@ -1,97 +0,0 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * TPS68470 chip Parent driver | ||
| 4 | * | ||
| 5 | * Copyright (C) 2017 Intel Corporation | ||
| 6 | * | ||
| 7 | * Authors: | ||
| 8 | * Rajmohan Mani <rajmohan.mani@intel.com> | ||
| 9 | * Tianshu Qiu <tian.shu.qiu@intel.com> | ||
| 10 | * Jian Xu Zheng <jian.xu.zheng@intel.com> | ||
| 11 | * Yuning Pu <yuning.pu@intel.com> | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/acpi.h> | ||
| 15 | #include <linux/delay.h> | ||
| 16 | #include <linux/i2c.h> | ||
| 17 | #include <linux/init.h> | ||
| 18 | #include <linux/mfd/core.h> | ||
| 19 | #include <linux/mfd/tps68470.h> | ||
| 20 | #include <linux/regmap.h> | ||
| 21 | |||
| 22 | static const struct mfd_cell tps68470s[] = { | ||
| 23 | { .name = "tps68470-gpio" }, | ||
| 24 | { .name = "tps68470_pmic_opregion" }, | ||
| 25 | }; | ||
| 26 | |||
| 27 | static const struct regmap_config tps68470_regmap_config = { | ||
| 28 | .reg_bits = 8, | ||
| 29 | .val_bits = 8, | ||
| 30 | .max_register = TPS68470_REG_MAX, | ||
| 31 | }; | ||
| 32 | |||
| 33 | static int tps68470_chip_init(struct device *dev, struct regmap *regmap) | ||
| 34 | { | ||
| 35 | unsigned int version; | ||
| 36 | int ret; | ||
| 37 | |||
| 38 | /* Force software reset */ | ||
| 39 | ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); | ||
| 40 | if (ret) | ||
| 41 | return ret; | ||
| 42 | |||
| 43 | ret = regmap_read(regmap, TPS68470_REG_REVID, &version); | ||
| 44 | if (ret) { | ||
| 45 | dev_err(dev, "Failed to read revision register: %d\n", ret); | ||
| 46 | return ret; | ||
| 47 | } | ||
| 48 | |||
| 49 | dev_info(dev, "TPS68470 REVID: 0x%x\n", version); | ||
| 50 | |||
| 51 | return 0; | ||
| 52 | } | ||
| 53 | |||
| 54 | static int tps68470_probe(struct i2c_client *client) | ||
| 55 | { | ||
| 56 | struct device *dev = &client->dev; | ||
| 57 | struct regmap *regmap; | ||
| 58 | int ret; | ||
| 59 | |||
| 60 | regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); | ||
| 61 | if (IS_ERR(regmap)) { | ||
| 62 | dev_err(dev, "devm_regmap_init_i2c Error %ld\n", | ||
| 63 | PTR_ERR(regmap)); | ||
| 64 | return PTR_ERR(regmap); | ||
| 65 | } | ||
| 66 | |||
| 67 | i2c_set_clientdata(client, regmap); | ||
| 68 | |||
| 69 | ret = tps68470_chip_init(dev, regmap); | ||
| 70 | if (ret < 0) { | ||
| 71 | dev_err(dev, "TPS68470 Init Error %d\n", ret); | ||
| 72 | return ret; | ||
| 73 | } | ||
| 74 | |||
| 75 | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, | ||
| 76 | ARRAY_SIZE(tps68470s), NULL, 0, NULL); | ||
| 77 | if (ret < 0) { | ||
| 78 | dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); | ||
| 79 | return ret; | ||
| 80 | } | ||
| 81 | |||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | static const struct acpi_device_id tps68470_acpi_ids[] = { | ||
| 86 | {"INT3472"}, | ||
| 87 | {}, | ||
| 88 | }; | ||
| 89 | |||
| 90 | static struct i2c_driver tps68470_driver = { | ||
| 91 | .driver = { | ||
| 92 | .name = "tps68470", | ||
| 93 | .acpi_match_table = tps68470_acpi_ids, | ||
| 94 | }, | ||
| 95 | .probe_new = tps68470_probe, | ||
| 96 | }; | ||
| 97 | builtin_i2c_driver(tps68470_driver); | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0+ | 1 | # SPDX-License-Identifier: GPL-2.0+ |
| 2 | # Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 2 | # Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 3 | 3 | ||
| 4 | menuconfig SURFACE_AGGREGATOR | 4 | menuconfig SURFACE_AGGREGATOR |
| 5 | tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" | 5 | tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" |
| @@ -1,5 +1,5 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0+ | 1 | # SPDX-License-Identifier: GPL-2.0+ |
| 2 | # Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 2 | # Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 3 | 3 | ||
| 4 | # For include/trace/define_trace.h to include trace.h | 4 | # For include/trace/define_trace.h to include trace.h |
| 5 | CFLAGS_core.o = -I$(src) | 5 | CFLAGS_core.o = -I$(src) |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * Surface System Aggregator Module bus and device integration. | 3 | * Surface System Aggregator Module bus and device integration. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include <linux/device.h> | 8 | #include <linux/device.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * Surface System Aggregator Module bus and device integration. | 3 | * Surface System Aggregator Module bus and device integration. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_BUS_H | 8 | #ifndef _SURFACE_AGGREGATOR_BUS_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * Main SSAM/SSH controller structure and functionality. | 3 | * Main SSAM/SSH controller structure and functionality. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include <linux/acpi.h> | 8 | #include <linux/acpi.h> |
| @@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, · 398 unmodified lines | |||
| 407 | return NULL; | 407 | return NULL; |
| 408 | } | 408 | } |
| 409 | 409 | ||
| 410 | /** | ||
| 411 | * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the | ||
| 412 | * given event and free its entry if the reference count reaches zero. | ||
| 413 | * @nf: The notifier system reference. | ||
| 414 | * @reg: The registry used to enable/disable the event. | ||
| 415 | * @id: The event ID. | ||
| 416 | * | ||
| 417 | * Decrements the reference-/activation-count of the specified event, freeing | ||
| 418 | * its entry if it reaches zero. | ||
| 419 | * | ||
| 420 | * Note: ``nf->lock`` must be held when calling this function. | ||
| 421 | */ | ||
| 422 | static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, | ||
| 423 | struct ssam_event_registry reg, | ||
| 424 | struct ssam_event_id id) | ||
| 425 | { | ||
| 426 | struct ssam_nf_refcount_entry *entry; | ||
| 427 | |||
| 428 | lockdep_assert_held(&nf->lock); | ||
| 429 | |||
| 430 | entry = ssam_nf_refcount_dec(nf, reg, id); | ||
| 431 | if (entry && entry->refcount == 0) | ||
| 432 | kfree(entry); | ||
| 433 | } | ||
| 434 | |||
| 410 | /** | 435 | /** |
| 411 | * ssam_nf_refcount_empty() - Test if the notification system has any | 436 | * ssam_nf_refcount_empty() - Test if the notification system has any |
| 412 | * enabled/active events. | 437 | * enabled/active events. |
| @@ -2122,14 +2147,123 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) · 1709 unmodified lines | |||
| 2122 | 2147 | ||
| 2123 | /* -- Top-level event registry interface. ----------------------------------- */ | 2148 | /* -- Top-level event registry interface. ----------------------------------- */ |
| 2124 | 2149 | ||
| 2150 | /** | ||
| 2151 | * ssam_nf_refcount_enable() - Enable event for reference count entry if it has | ||
| 2152 | * not already been enabled. | ||
| 2153 | * @ctrl: The controller to enable the event on. | ||
| 2154 | * @entry: The reference count entry for the event to be enabled. | ||
| 2155 | * @flags: The flags used for enabling the event on the EC. | ||
| 2156 | * | ||
| 2157 | * Enable the event associated with the given reference count entry if the | ||
| 2158 | * reference count equals one, i.e. the event has not previously been enabled. | ||
| 2159 | * If the event has already been enabled (i.e. reference count not equal to | ||
| 2160 | * one), check that the flags used for enabling match and warn about this if | ||
| 2161 | * they do not. | ||
| 2162 | * | ||
| 2163 | * This does not modify the reference count itself, which is done with | ||
| 2164 | * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). | ||
| 2165 | * | ||
| 2166 | * Note: ``nf->lock`` must be held when calling this function. | ||
| 2167 | * | ||
| 2168 | * Return: Returns zero on success. If the event is enabled by this call, | ||
| 2169 | * returns the status of the event-enable EC command. | ||
| 2170 | */ | ||
| 2171 | static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, | ||
| 2172 | struct ssam_nf_refcount_entry *entry, u8 flags) | ||
| 2173 | { | ||
| 2174 | const struct ssam_event_registry reg = entry->key.reg; | ||
| 2175 | const struct ssam_event_id id = entry->key.id; | ||
| 2176 | struct ssam_nf *nf = &ctrl->cplt.event.notif; | ||
| 2177 | int status; | ||
| 2178 | |||
| 2179 | lockdep_assert_held(&nf->lock); | ||
| 2180 | |||
| 2181 | ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", | ||
| 2182 | reg.target_category, id.target_category, id.instance, entry->refcount); | ||
| 2183 | |||
| 2184 | if (entry->refcount == 1) { | ||
| 2185 | status = ssam_ssh_event_enable(ctrl, reg, id, flags); | ||
| 2186 | if (status) | ||
| 2187 | return status; | ||
| 2188 | |||
| 2189 | entry->flags = flags; | ||
| 2190 | |||
| 2191 | } else if (entry->flags != flags) { | ||
| 2192 | ssam_warn(ctrl, | ||
| 2193 | "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", | ||
| 2194 | flags, entry->flags, reg.target_category, id.target_category, | ||
| 2195 | id.instance); | ||
| 2196 | } | ||
| 2197 | |||
| 2198 | return 0; | ||
| 2199 | } | ||
| 2200 | |||
| 2201 | /** | ||
| 2202 | * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is | ||
| 2203 | * no longer in use and free the corresponding entry. | ||
| 2204 | * @ctrl: The controller to disable the event on. | ||
| 2205 | * @entry: The reference count entry for the event to be disabled. | ||
| 2206 | * @flags: The flags used for enabling the event on the EC. | ||
| 2207 | * | ||
| 2208 | * If the reference count equals zero, i.e. the event is no longer requested by | ||
| 2209 | * any client, the event will be disabled and the corresponding reference count | ||
| 2210 | * entry freed. The reference count entry must not be used any more after a | ||
| 2211 | * call to this function. | ||
| 2212 | * | ||
| 2213 | * Also checks if the flags used for disabling the event match the flags used | ||
| 2214 | * for enabling the event and warns if they do not (regardless of reference | ||
| 2215 | * count). | ||
| 2216 | * | ||
| 2217 | * This does not modify the reference count itself, which is done with | ||
| 2218 | * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). | ||
| 2219 | * | ||
| 2220 | * Note: ``nf->lock`` must be held when calling this function. | ||
| 2221 | * | ||
| 2222 | * Return: Returns zero on success. If the event is disabled by this call, | ||
| 2223 | * returns the status of the event-enable EC command. | ||
| 2224 | */ | ||
| 2225 | static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, | ||
| 2226 | struct ssam_nf_refcount_entry *entry, u8 flags) | ||
| 2227 | { | ||
| 2228 | const struct ssam_event_registry reg = entry->key.reg; | ||
| 2229 | const struct ssam_event_id id = entry->key.id; | ||
| 2230 | struct ssam_nf *nf = &ctrl->cplt.event.notif; | ||
| 2231 | int status = 0; | ||
| 2232 | |||
| 2233 | lockdep_assert_held(&nf->lock); | ||
| 2234 | |||
| 2235 | ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", | ||
| 2236 | reg.target_category, id.target_category, id.instance, entry->refcount); | ||
| 2237 | |||
| 2238 | if (entry->flags != flags) { | ||
| 2239 | ssam_warn(ctrl, | ||
| 2240 | "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", | ||
| 2241 | flags, entry->flags, reg.target_category, id.target_category, | ||
| 2242 | id.instance); | ||
| 2243 | } | ||
| 2244 | |||
| 2245 | if (entry->refcount == 0) { | ||
| 2246 | status = ssam_ssh_event_disable(ctrl, reg, id, flags); | ||
| 2247 | kfree(entry); | ||
| 2248 | } | ||
| 2249 | |||
| 2250 | return status; | ||
| 2251 | } | ||
| 2252 | |||
| 2125 | /** | 2253 | /** |
| 2126 | * ssam_notifier_register() - Register an event notifier. | 2254 | * ssam_notifier_register() - Register an event notifier. |
| 2127 | * @ctrl: The controller to register the notifier on. | 2255 | * @ctrl: The controller to register the notifier on. |
| 2128 | * @n: The event notifier to register. | 2256 | * @n: The event notifier to register. |
| 2129 | * | 2257 | * |
| 2130 | * Register an event notifier and increment the usage counter of the | 2258 | * Register an event notifier. Increment the usage counter of the associated |
| 2131 | * associated SAM event. If the event was previously not enabled, it will be | 2259 | * SAM event if the notifier is not marked as an observer. If the event is not |
| 2132 | * enabled during this call. | 2260 | * marked as an observer and is currently not enabled, it will be enabled |
| 2261 | * during this call. If the notifier is marked as an observer, no attempt will | ||
| 2262 | * be made at enabling any event and no reference count will be modified. | ||
| 2263 | * | ||
| 2264 | * Notifiers marked as observers do not need to be associated with one specific | ||
| 2265 | * event, i.e. as long as no event matching is performed, only the event target | ||
| 2266 | * category needs to be set. | ||
| 2133 | * | 2267 | * |
| 2134 | * Return: Returns zero on success, %-ENOSPC if there have already been | 2268 | * Return: Returns zero on success, %-ENOSPC if there have already been |
| 2135 | * %INT_MAX notifiers for the event ID/type associated with the notifier block | 2269 | * %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) · 2 unmodified lines | |||
| 2138 | * for the specific associated event, returns the status of the event-enable | 2272 | * for the specific associated event, returns the status of the event-enable |
| 2139 | * EC-command. | 2273 | * EC-command. |
| 2140 | */ | 2274 | */ |
| 2141 | int ssam_notifier_register(struct ssam_controller *ctrl, | 2275 | int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) |
| 2142 | struct ssam_event_notifier *n) | ||
| 2143 | { | 2276 | { |
| 2144 | u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); | 2277 | u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); |
| 2145 | struct ssam_nf_refcount_entry *entry; | 2278 | struct ssam_nf_refcount_entry *entry = NULL; |
| 2146 | struct ssam_nf_head *nf_head; | 2279 | struct ssam_nf_head *nf_head; |
| 2147 | struct ssam_nf *nf; | 2280 | struct ssam_nf *nf; |
| 2148 | int status; | 2281 | int status; |
| @@ -2155,44 +2288,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, · 6 unmodified lines | |||
| 2155 | 2288 | ||
| 2156 | mutex_lock(&nf->lock); | 2289 | mutex_lock(&nf->lock); |
| 2157 | 2290 | ||
| 2158 | entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); | 2291 | if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { |
| 2159 | if (IS_ERR(entry)) { | 2292 | entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); |
| 2160 | mutex_unlock(&nf->lock); | 2293 | if (IS_ERR(entry)) { |
| 2161 | return PTR_ERR(entry); | 2294 | mutex_unlock(&nf->lock); |
| 2295 | return PTR_ERR(entry); | ||
| 2296 | } | ||
| 2162 | } | 2297 | } |
| 2163 | 2298 | ||
| 2164 | ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", | ||
| 2165 | n->event.reg.target_category, n->event.id.target_category, | ||
| 2166 | n->event.id.instance, entry->refcount); | ||
| 2167 | |||
| 2168 | status = ssam_nfblk_insert(nf_head, &n->base); | 2299 | status = ssam_nfblk_insert(nf_head, &n->base); |
| 2169 | if (status) { | 2300 | if (status) { |
| 2170 | entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); | 2301 | if (entry) |
| 2171 | if (entry->refcount == 0) | 2302 | ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); |
| 2172 | kfree(entry); | ||
| 2173 | 2303 | ||
| 2174 | mutex_unlock(&nf->lock); | 2304 | mutex_unlock(&nf->lock); |
| 2175 | return status; | 2305 | return status; |
| 2176 | } | 2306 | } |
| 2177 | 2307 | ||
| 2178 | if (entry->refcount == 1) { | 2308 | if (entry) { |
| 2179 | status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, | 2309 | status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); |
| 2180 | n->event.flags); | ||
| 2181 | if (status) { | 2310 | if (status) { |
| 2182 | ssam_nfblk_remove(&n->base); | 2311 | ssam_nfblk_remove(&n->base); |
| 2183 | kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); | 2312 | ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); |
| 2184 | mutex_unlock(&nf->lock); | 2313 | mutex_unlock(&nf->lock); |
| 2185 | synchronize_srcu(&nf_head->srcu); | 2314 | synchronize_srcu(&nf_head->srcu); |
| 2186 | return status; | 2315 | return status; |
| 2187 | } | 2316 | } |
| 2188 | |||
| 2189 | entry->flags = n->event.flags; | ||
| 2190 | |||
| 2191 | } else if (entry->flags != n->event.flags) { | ||
| 2192 | ssam_warn(ctrl, | ||
| 2193 | "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", | ||
| 2194 | n->event.flags, entry->flags, n->event.reg.target_category, | ||
| 2195 | n->event.id.target_category, n->event.id.instance); | ||
| 2196 | } | 2317 | } |
| 2197 | 2318 | ||
| 2198 | mutex_unlock(&nf->lock); | 2319 | mutex_unlock(&nf->lock); |
| @@ -2205,17 +2326,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); · 6 unmodified lines | |||
| 2205 | * @ctrl: The controller the notifier has been registered on. | 2326 | * @ctrl: The controller the notifier has been registered on. |
| 2206 | * @n: The event notifier to unregister. | 2327 | * @n: The event notifier to unregister. |
| 2207 | * | 2328 | * |
| 2208 | * Unregister an event notifier and decrement the usage counter of the | 2329 | * Unregister an event notifier. Decrement the usage counter of the associated |
| 2209 | * associated SAM event. If the usage counter reaches zero, the event will be | 2330 | * SAM event if the notifier is not marked as an observer. If the usage counter |
| 2210 | * disabled. | 2331 | * reaches zero, the event will be disabled. |
| 2211 | * | 2332 | * |
| 2212 | * Return: Returns zero on success, %-ENOENT if the given notifier block has | 2333 | * Return: Returns zero on success, %-ENOENT if the given notifier block has |
| 2213 | * not been registered on the controller. If the given notifier block was the | 2334 | * not been registered on the controller. If the given notifier block was the |
| 2214 | * last one associated with its specific event, returns the status of the | 2335 | * last one associated with its specific event, returns the status of the |
| 2215 | * event-disable EC-command. | 2336 | * event-disable EC-command. |
| 2216 | */ | 2337 | */ |
| 2217 | int ssam_notifier_unregister(struct ssam_controller *ctrl, | 2338 | int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) |
| 2218 | struct ssam_event_notifier *n) | ||
| 2219 | { | 2339 | { |
| 2220 | u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); | 2340 | u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); |
| 2221 | struct ssam_nf_refcount_entry *entry; | 2341 | struct ssam_nf_refcount_entry *entry; |
| @@ -2236,33 +2356,24 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, · 14 unmodified lines | |||
| 2236 | return -ENOENT; | 2356 | return -ENOENT; |
| 2237 | } | 2357 | } |
| 2238 | 2358 | ||
| 2239 | entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); | 2359 | /* |
| 2240 | if (WARN_ON(!entry)) { | 2360 | * If this is an observer notifier, do not attempt to disable the |
| 2241 | /* | 2361 | * event, just remove it. |
| 2242 | * If this does not return an entry, there's a logic error | 2362 | */ |
| 2243 | * somewhere: The notifier block is registered, but the event | 2363 | if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { |
| 2244 | * refcount entry is not there. Remove the notifier block | 2364 | entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); |
| 2245 | * anyways. | 2365 | if (WARN_ON(!entry)) { |
| 2246 | */ | 2366 | /* |
| 2247 | status = -ENOENT; | 2367 | * If this does not return an entry, there's a logic |
| 2248 | goto remove; | 2368 | * error somewhere: The notifier block is registered, |
| 2249 | } | 2369 | * but the event refcount entry is not there. Remove |
| 2250 | 2370 | * the notifier block anyways. | |
| 2251 | ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", | 2371 | */ |
| 2252 | n->event.reg.target_category, n->event.id.target_category, | 2372 | status = -ENOENT; |
| 2253 | n->event.id.instance, entry->refcount); | 2373 | goto remove; |
| 2254 | 2374 | } | |
| 2255 | if (entry->flags != n->event.flags) { | ||
| 2256 | ssam_warn(ctrl, | ||
| 2257 | "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", | ||
| 2258 | n->event.flags, entry->flags, n->event.reg.target_category, | ||
| 2259 | n->event.id.target_category, n->event.id.instance); | ||
| 2260 | } | ||
| 2261 | 2375 | ||
| 2262 | if (entry->refcount == 0) { | 2376 | status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); |
| 2263 | status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, | ||
| 2264 | n->event.flags); | ||
| 2265 | kfree(entry); | ||
| 2266 | } | 2377 | } |
| 2267 | 2378 | ||
| 2268 | remove: | 2379 | remove: |
| @@ -2274,6 +2385,105 @@ remove: · 5 unmodified lines | |||
| 2274 | } | 2385 | } |
| 2275 | EXPORT_SYMBOL_GPL(ssam_notifier_unregister); | 2386 | EXPORT_SYMBOL_GPL(ssam_notifier_unregister); |
| 2276 | 2387 | ||
| 2388 | /** | ||
| 2389 | * ssam_controller_event_enable() - Enable the specified event. | ||
| 2390 | * @ctrl: The controller to enable the event for. | ||
| 2391 | * @reg: The event registry to use for enabling the event. | ||
| 2392 | * @id: The event ID specifying the event to be enabled. | ||
| 2393 | * @flags: The SAM event flags used for enabling the event. | ||
| 2394 | * | ||
| 2395 | * Increment the event reference count of the specified event. If the event has | ||
| 2396 | * not been enabled previously, it will be enabled by this call. | ||
| 2397 | * | ||
| 2398 | * Note: In general, ssam_notifier_register() with a non-observer notifier | ||
| 2399 | * should be preferred for enabling/disabling events, as this will guarantee | ||
| 2400 | * proper ordering and event forwarding in case of errors during event | ||
| 2401 | * enabling/disabling. | ||
| 2402 | * | ||
| 2403 | * Return: Returns zero on success, %-ENOSPC if the reference count for the | ||
| 2404 | * specified event has reached its maximum, %-ENOMEM if the corresponding event | ||
| 2405 | * entry could not be allocated. If this is the first time that this event has | ||
| 2406 | * been enabled (i.e. the reference count was incremented from zero to one by | ||
| 2407 | * this call), returns the status of the event-enable EC-command. | ||
| 2408 | */ | ||
| 2409 | int ssam_controller_event_enable(struct ssam_controller *ctrl, | ||
| 2410 | struct ssam_event_registry reg, | ||
| 2411 | struct ssam_event_id id, u8 flags) | ||
| 2412 | { | ||
| 2413 | u16 rqid = ssh_tc_to_rqid(id.target_category); | ||
| 2414 | struct ssam_nf *nf = &ctrl->cplt.event.notif; | ||
| 2415 | struct ssam_nf_refcount_entry *entry; | ||
| 2416 | int status; | ||
| 2417 | |||
| 2418 | if (!ssh_rqid_is_event(rqid)) | ||
| 2419 | return -EINVAL; | ||
| 2420 | |||
| 2421 | mutex_lock(&nf->lock); | ||
| 2422 | |||
| 2423 | entry = ssam_nf_refcount_inc(nf, reg, id); | ||
| 2424 | if (IS_ERR(entry)) { | ||
| 2425 | mutex_unlock(&nf->lock); | ||
| 2426 | return PTR_ERR(entry); | ||
| 2427 | } | ||
| 2428 | |||
| 2429 | status = ssam_nf_refcount_enable(ctrl, entry, flags); | ||
| 2430 | if (status) { | ||
| 2431 | ssam_nf_refcount_dec_free(nf, reg, id); | ||
| 2432 | mutex_unlock(&nf->lock); | ||
| 2433 | return status; | ||
| 2434 | } | ||
| 2435 | |||
| 2436 | mutex_unlock(&nf->lock); | ||
| 2437 | return 0; | ||
| 2438 | } | ||
| 2439 | EXPORT_SYMBOL_GPL(ssam_controller_event_enable); | ||
| 2440 | |||
| 2441 | /** | ||
| 2442 | * ssam_controller_event_disable() - Disable the specified event. | ||
| 2443 | * @ctrl: The controller to disable the event for. | ||
| 2444 | * @reg: The event registry to use for disabling the event. | ||
| 2445 | * @id: The event ID specifying the event to be disabled. | ||
| 2446 | * @flags: The flags used when enabling the event. | ||
| 2447 | * | ||
| 2448 | * Decrement the reference count of the specified event. If the reference count | ||
| 2449 | * reaches zero, the event will be disabled. | ||
| 2450 | * | ||
| 2451 | * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a | ||
| 2452 | * non-observer notifier should be preferred for enabling/disabling events, as | ||
| 2453 | * this will guarantee proper ordering and event forwarding in case of errors | ||
| 2454 | * during event enabling/disabling. | ||
| 2455 | * | ||
| 2456 | * Return: Returns zero on success, %-ENOENT if the given event has not been | ||
| 2457 | * enabled on the controller. If the reference count of the event reaches zero | ||
| 2458 | * during this call, returns the status of the event-disable EC-command. | ||
| 2459 | */ | ||
| 2460 | int ssam_controller_event_disable(struct ssam_controller *ctrl, | ||
| 2461 | struct ssam_event_registry reg, | ||
| 2462 | struct ssam_event_id id, u8 flags) | ||
| 2463 | { | ||
| 2464 | u16 rqid = ssh_tc_to_rqid(id.target_category); | ||
| 2465 | struct ssam_nf *nf = &ctrl->cplt.event.notif; | ||
| 2466 | struct ssam_nf_refcount_entry *entry; | ||
| 2467 | int status; | ||
| 2468 | |||
| 2469 | if (!ssh_rqid_is_event(rqid)) | ||
| 2470 | return -EINVAL; | ||
| 2471 | |||
| 2472 | mutex_lock(&nf->lock); | ||
| 2473 | |||
| 2474 | entry = ssam_nf_refcount_dec(nf, reg, id); | ||
| 2475 | if (!entry) { | ||
| 2476 | mutex_unlock(&nf->lock); | ||
| 2477 | return -ENOENT; | ||
| 2478 | } | ||
| 2479 | |||
| 2480 | status = ssam_nf_refcount_disable_free(ctrl, entry, flags); | ||
| 2481 | |||
| 2482 | mutex_unlock(&nf->lock); | ||
| 2483 | return status; | ||
| 2484 | } | ||
| 2485 | EXPORT_SYMBOL_GPL(ssam_controller_event_disable); | ||
| 2486 | |||
| 2277 | /** | 2487 | /** |
| 2278 | * ssam_notifier_disable_registered() - Disable events for all registered | 2488 | * ssam_notifier_disable_registered() - Disable events for all registered |
| 2279 | * notifiers. | 2489 | * notifiers. |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * Main SSAM/SSH controller structure and functionality. | 3 | * Main SSAM/SSH controller structure and functionality. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H | 8 | #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H |
| @@ -7,7 +7,7 @@ · 6 unmodified lines | |||
| 7 | * Handles communication via requests as well as enabling, disabling, and | 7 | * Handles communication via requests as well as enabling, disabling, and |
| 8 | * relaying of events. | 8 | * relaying of events. |
| 9 | * | 9 | * |
| 10 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 10 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 11 | */ | 11 | */ |
| 12 | 12 | ||
| 13 | #include <linux/acpi.h> | 13 | #include <linux/acpi.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH message builder functions. | 3 | * SSH message builder functions. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH packet transport layer. | 3 | * SSH packet transport layer. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include <asm/unaligned.h> | 8 | #include <asm/unaligned.h> |
| @@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) · 1558 unmodified lines | |||
| 1567 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); | 1567 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); |
| 1568 | 1568 | ||
| 1569 | atomic_dec(&ptl->pending.count); | 1569 | atomic_dec(&ptl->pending.count); |
| 1570 | list_del(&p->pending_node); | 1570 | list_move_tail(&p->pending_node, &claimed); |
| 1571 | |||
| 1572 | list_add_tail(&p->pending_node, &claimed); | ||
| 1573 | } | 1571 | } |
| 1574 | 1572 | ||
| 1575 | spin_unlock(&ptl->pending.lock); | 1573 | spin_unlock(&ptl->pending.lock); |
| @@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) · 381 unmodified lines | |||
| 1957 | smp_mb__before_atomic(); | 1955 | smp_mb__before_atomic(); |
| 1958 | clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); | 1956 | clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); |
| 1959 | 1957 | ||
| 1960 | list_del(&p->queue_node); | 1958 | list_move_tail(&p->queue_node, &complete_q); |
| 1961 | list_add_tail(&p->queue_node, &complete_q); | ||
| 1962 | } | 1959 | } |
| 1963 | spin_unlock(&ptl->queue.lock); | 1960 | spin_unlock(&ptl->queue.lock); |
| 1964 | 1961 | ||
| @@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) · 5 unmodified lines | |||
| 1970 | smp_mb__before_atomic(); | 1967 | smp_mb__before_atomic(); |
| 1971 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); | 1968 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); |
| 1972 | 1969 | ||
| 1973 | list_del(&p->pending_node); | 1970 | list_move_tail(&p->pending_node, &complete_q); |
| 1974 | list_add_tail(&p->pending_node, &complete_q); | ||
| 1975 | } | 1971 | } |
| 1976 | atomic_set(&ptl->pending.count, 0); | 1972 | atomic_set(&ptl->pending.count, 0); |
| 1977 | spin_unlock(&ptl->pending.lock); | 1973 | spin_unlock(&ptl->pending.lock); |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH packet transport layer. | 3 | * SSH packet transport layer. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH message parser. | 3 | * SSH message parser. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include <asm/unaligned.h> | 8 | #include <asm/unaligned.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH message parser. | 3 | * SSH message parser. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH request transport layer. | 3 | * SSH request transport layer. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #include <asm/unaligned.h> | 8 | #include <asm/unaligned.h> |
| @@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) · 854 unmodified lines | |||
| 863 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); | 863 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); |
| 864 | 864 | ||
| 865 | atomic_dec(&rtl->pending.count); | 865 | atomic_dec(&rtl->pending.count); |
| 866 | list_del(&r->node); | 866 | list_move_tail(&r->node, &claimed); |
| 867 | |||
| 868 | list_add_tail(&r->node, &claimed); | ||
| 869 | } | 867 | } |
| 870 | spin_unlock(&rtl->pending.lock); | 868 | spin_unlock(&rtl->pending.lock); |
| 871 | 869 | ||
| @@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) · 332 unmodified lines | |||
| 1204 | smp_mb__before_atomic(); | 1202 | smp_mb__before_atomic(); |
| 1205 | clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); | 1203 | clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); |
| 1206 | 1204 | ||
| 1207 | list_del(&r->node); | 1205 | list_move_tail(&r->node, &claimed); |
| 1208 | list_add_tail(&r->node, &claimed); | ||
| 1209 | } | 1206 | } |
| 1210 | spin_unlock(&rtl->queue.lock); | 1207 | spin_unlock(&rtl->queue.lock); |
| 1211 | 1208 | ||
| @@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) · 26 unmodified lines | |||
| 1238 | smp_mb__before_atomic(); | 1235 | smp_mb__before_atomic(); |
| 1239 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); | 1236 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); |
| 1240 | 1237 | ||
| 1241 | list_del(&r->node); | 1238 | list_move_tail(&r->node, &claimed); |
| 1242 | list_add_tail(&r->node, &claimed); | ||
| 1243 | } | 1239 | } |
| 1244 | spin_unlock(&rtl->pending.lock); | 1240 | spin_unlock(&rtl->pending.lock); |
| 1245 | } | 1241 | } |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * SSH request transport layer. | 3 | * SSH request transport layer. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | |||
| 2 | /* | 2 | /* |
| 3 | * Trace points for SSAM/SSH. | 3 | * Trace points for SSAM/SSH. |
| 4 | * | 4 | * |
| 5 | * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com> | 5 | * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 6 | */ | 6 | */ |
| 7 | 7 | ||
| 8 | #undef TRACE_SYSTEM | 8 | #undef TRACE_SYSTEM |
| @@ -3,29 +3,69 @@ · 2 unmodified lines | |||
| 3 | * Provides user-space access to the SSAM EC via the /dev/surface/aggregator | 3 | * Provides user-space access to the SSAM EC via the /dev/surface/aggregator |
| 4 | * misc device. Intended for debugging and development. | 4 | * misc device. Intended for debugging and development. |
| 5 | * | 5 | * |
| 6 | * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com> | 6 | * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 7 | */ | 7 | */ |
| 8 | 8 | ||
| 9 | #include <linux/fs.h> | 9 | #include <linux/fs.h> |
| 10 | #include <linux/ioctl.h> | ||
| 10 | #include <linux/kernel.h> | 11 | #include <linux/kernel.h> |
| 12 | #include <linux/kfifo.h> | ||
| 11 | #include <linux/kref.h> | 13 | #include <linux/kref.h> |
| 12 | #include <linux/miscdevice.h> | 14 | #include <linux/miscdevice.h> |
| 13 | #include <linux/module.h> | 15 | #include <linux/module.h> |
| 14 | #include <linux/platform_device.h> | 16 | #include <linux/platform_device.h> |
| 17 | #include <linux/poll.h> | ||
| 15 | #include <linux/rwsem.h> | 18 | #include <linux/rwsem.h> |
| 16 | #include <linux/slab.h> | 19 | #include <linux/slab.h> |
| 17 | #include <linux/uaccess.h> | 20 | #include <linux/uaccess.h> |
| 21 | #include <linux/vmalloc.h> | ||
| 18 | 22 | ||
| 19 | #include <linux/surface_aggregator/cdev.h> | 23 | #include <linux/surface_aggregator/cdev.h> |
| 20 | #include <linux/surface_aggregator/controller.h> | 24 | #include <linux/surface_aggregator/controller.h> |
| 25 | #include <linux/surface_aggregator/serial_hub.h> | ||
| 21 | 26 | ||
| 22 | #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" | 27 | #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" |
| 23 | 28 | ||
| 29 | |||
| 30 | /* -- Main structures. ------------------------------------------------------ */ | ||
| 31 | |||
| 32 | enum ssam_cdev_device_state { | ||
| 33 | SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), | ||
| 34 | }; | ||
| 35 | |||
| 24 | struct ssam_cdev { | 36 | struct ssam_cdev { |
| 25 | struct kref kref; | 37 | struct kref kref; |
| 26 | struct rw_semaphore lock; | 38 | struct rw_semaphore lock; |
| 39 | |||
| 40 | struct device *dev; | ||
| 27 | struct ssam_controller *ctrl; | 41 | struct ssam_controller *ctrl; |
| 28 | struct miscdevice mdev; | 42 | struct miscdevice mdev; |
| 43 | unsigned long flags; | ||
| 44 | |||
| 45 | struct rw_semaphore client_lock; /* Guards client list. */ | ||
| 46 | struct list_head client_list; | ||
| 47 | }; | ||
| 48 | |||
| 49 | struct ssam_cdev_client; | ||
| 50 | |||
| 51 | struct ssam_cdev_notifier { | ||
| 52 | struct ssam_cdev_client *client; | ||
| 53 | struct ssam_event_notifier nf; | ||
| 54 | }; | ||
| 55 | |||
| 56 | struct ssam_cdev_client { | ||
| 57 | struct ssam_cdev *cdev; | ||
| 58 | struct list_head node; | ||
| 59 | |||
| 60 | struct mutex notifier_lock; /* Guards notifier access for registration */ | ||
| 61 | struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; | ||
| 62 | |||
| 63 | struct mutex read_lock; /* Guards FIFO buffer read access */ | ||
| 64 | struct mutex write_lock; /* Guards FIFO buffer write access */ | ||
| 65 | DECLARE_KFIFO(buffer, u8, 4096); | ||
| 66 | |||
| 67 | wait_queue_head_t waitq; | ||
| 68 | struct fasync_struct *fasync; | ||
| 29 | }; | 69 | }; |
| 30 | 70 | ||
| 31 | static void __ssam_cdev_release(struct kref *kref) | 71 | static void __ssam_cdev_release(struct kref *kref) |
| @@ -47,24 +87,173 @@ static void ssam_cdev_put(struct ssam_cdev *cdev) · 15 unmodified lines | |||
| 47 | kref_put(&cdev->kref, __ssam_cdev_release); | 87 | kref_put(&cdev->kref, __ssam_cdev_release); |
| 48 | } | 88 | } |
| 49 | 89 | ||
| 50 | static int ssam_cdev_device_open(struct inode *inode, struct file *filp) | 90 | |
| 91 | /* -- Notifier handling. ---------------------------------------------------- */ | ||
| 92 | |||
| 93 | static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) | ||
| 51 | { | 94 | { |
| 52 | struct miscdevice *mdev = filp->private_data; | 95 | struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); |
| 53 | struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); | 96 | struct ssam_cdev_client *client = cdev_nf->client; |
| 97 | struct ssam_cdev_event event; | ||
| 98 | size_t n = struct_size(&event, data, in->length); | ||
| 99 | |||
| 100 | /* Translate event. */ | ||
| 101 | event.target_category = in->target_category; | ||
| 102 | event.target_id = in->target_id; | ||
| 103 | event.command_id = in->command_id; | ||
| 104 | event.instance_id = in->instance_id; | ||
| 105 | event.length = in->length; | ||
| 106 | |||
| 107 | mutex_lock(&client->write_lock); | ||
| 108 | |||
| 109 | /* Make sure we have enough space. */ | ||
| 110 | if (kfifo_avail(&client->buffer) < n) { | ||
| 111 | dev_warn(client->cdev->dev, | ||
| 112 | "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", | ||
| 113 | in->target_category, in->target_id, in->command_id, in->instance_id); | ||
| 114 | mutex_unlock(&client->write_lock); | ||
| 115 | return 0; | ||
| 116 | } | ||
| 117 | |||
| 118 | /* Copy event header and payload. */ | ||
| 119 | kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); | ||
| 120 | kfifo_in(&client->buffer, &in->data[0], in->length); | ||
| 121 | |||
| 122 | mutex_unlock(&client->write_lock); | ||
| 123 | |||
| 124 | /* Notify waiting readers. */ | ||
| 125 | kill_fasync(&client->fasync, SIGIO, POLL_IN); | ||
| 126 | wake_up_interruptible(&client->waitq); | ||
| 54 | 127 | ||
| 55 | filp->private_data = ssam_cdev_get(cdev); | 128 | /* |
| 56 | return stream_open(inode, filp); | 129 | * Don't mark events as handled, this is the job of a proper driver and |
| 130 | * not the debugging interface. | ||
| 131 | */ | ||
| 132 | return 0; | ||
| 57 | } | 133 | } |
| 58 | 134 | ||
| 59 | static int ssam_cdev_device_release(struct inode *inode, struct file *filp) | 135 | static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) |
| 60 | { | 136 | { |
| 61 | ssam_cdev_put(filp->private_data); | 137 | const u16 rqid = ssh_tc_to_rqid(tc); |
| 62 | return 0; | 138 | const u16 event = ssh_rqid_to_event(rqid); |
| 139 | struct ssam_cdev_notifier *nf; | ||
| 140 | int status; | ||
| 141 | |||
| 142 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 143 | |||
| 144 | /* Validate notifier target category. */ | ||
| 145 | if (!ssh_rqid_is_event(rqid)) | ||
| 146 | return -EINVAL; | ||
| 147 | |||
| 148 | mutex_lock(&client->notifier_lock); | ||
| 149 | |||
| 150 | /* Check if the notifier has already been registered. */ | ||
| 151 | if (client->notifier[event]) { | ||
| 152 | mutex_unlock(&client->notifier_lock); | ||
| 153 | return -EEXIST; | ||
| 154 | } | ||
| 155 | |||
| 156 | /* Allocate new notifier. */ | ||
| 157 | nf = kzalloc(sizeof(*nf), GFP_KERNEL); | ||
| 158 | if (!nf) { | ||
| 159 | mutex_unlock(&client->notifier_lock); | ||
| 160 | return -ENOMEM; | ||
| 161 | } | ||
| 162 | |||
| 163 | /* | ||
| 164 | * Create a dummy notifier with the minimal required fields for | ||
| 165 | * observer registration. Note that we can skip fully specifying event | ||
| 166 | * and registry here as we do not need any matching and use silent | ||
| 167 | * registration, which does not enable the corresponding event. | ||
| 168 | */ | ||
| 169 | nf->client = client; | ||
| 170 | nf->nf.base.fn = ssam_cdev_notifier; | ||
| 171 | nf->nf.base.priority = priority; | ||
| 172 | nf->nf.event.id.target_category = tc; | ||
| 173 | nf->nf.event.mask = 0; /* Do not do any matching. */ | ||
| 174 | nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; | ||
| 175 | |||
| 176 | /* Register notifier. */ | ||
| 177 | status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); | ||
| 178 | if (status) | ||
| 179 | kfree(nf); | ||
| 180 | else | ||
| 181 | client->notifier[event] = nf; | ||
| 182 | |||
| 183 | mutex_unlock(&client->notifier_lock); | ||
| 184 | return status; | ||
| 185 | } | ||
| 186 | |||
| 187 | static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) | ||
| 188 | { | ||
| 189 | const u16 rqid = ssh_tc_to_rqid(tc); | ||
| 190 | const u16 event = ssh_rqid_to_event(rqid); | ||
| 191 | int status; | ||
| 192 | |||
| 193 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 194 | |||
| 195 | /* Validate notifier target category. */ | ||
| 196 | if (!ssh_rqid_is_event(rqid)) | ||
| 197 | return -EINVAL; | ||
| 198 | |||
| 199 | mutex_lock(&client->notifier_lock); | ||
| 200 | |||
| 201 | /* Check if the notifier is currently registered. */ | ||
| 202 | if (!client->notifier[event]) { | ||
| 203 | mutex_unlock(&client->notifier_lock); | ||
| 204 | return -ENOENT; | ||
| 205 | } | ||
| 206 | |||
| 207 | /* Unregister and free notifier. */ | ||
| 208 | status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); | ||
| 209 | kfree(client->notifier[event]); | ||
| 210 | client->notifier[event] = NULL; | ||
| 211 | |||
| 212 | mutex_unlock(&client->notifier_lock); | ||
| 213 | return status; | ||
| 214 | } | ||
| 215 | |||
| 216 | static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) | ||
| 217 | { | ||
| 218 | int i; | ||
| 219 | |||
| 220 | down_read(&client->cdev->lock); | ||
| 221 | |||
| 222 | /* | ||
| 223 | * This function may be used during shutdown, thus we need to test for | ||
| 224 | * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. | ||
| 225 | */ | ||
| 226 | if (client->cdev->ctrl) { | ||
| 227 | for (i = 0; i < SSH_NUM_EVENTS; i++) | ||
| 228 | ssam_cdev_notifier_unregister(client, i + 1); | ||
| 229 | |||
| 230 | } else { | ||
| 231 | int count = 0; | ||
| 232 | |||
| 233 | /* | ||
| 234 | * Device has been shut down. Any notifier remaining is a bug, | ||
| 235 | * so warn about that as this would otherwise hardly be | ||
| 236 | * noticeable. Nevertheless, free them as well. | ||
| 237 | */ | ||
| 238 | mutex_lock(&client->notifier_lock); | ||
| 239 | for (i = 0; i < SSH_NUM_EVENTS; i++) { | ||
| 240 | count += !!(client->notifier[i]); | ||
| 241 | kfree(client->notifier[i]); | ||
| 242 | client->notifier[i] = NULL; | ||
| 243 | } | ||
| 244 | mutex_unlock(&client->notifier_lock); | ||
| 245 | |||
| 246 | WARN_ON(count > 0); | ||
| 247 | } | ||
| 248 | |||
| 249 | up_read(&client->cdev->lock); | ||
| 63 | } | 250 | } |
| 64 | 251 | ||
| 65 | static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) | 252 | |
| 253 | /* -- IOCTL functions. ------------------------------------------------------ */ | ||
| 254 | |||
| 255 | static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) | ||
| 66 | { | 256 | { |
| 67 | struct ssam_cdev_request __user *r; | ||
| 68 | struct ssam_cdev_request rqst; | 257 | struct ssam_cdev_request rqst; |
| 69 | struct ssam_request spec = {}; | 258 | struct ssam_request spec = {}; |
| 70 | struct ssam_response rsp = {}; | 259 | struct ssam_response rsp = {}; |
| @@ -72,7 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) · 1 unmodified line | |||
| 72 | void __user *rspdata; | 261 | void __user *rspdata; |
| 73 | int status = 0, ret = 0, tmp; | 262 | int status = 0, ret = 0, tmp; |
| 74 | 263 | ||
| 75 | r = (struct ssam_cdev_request __user *)arg; | 264 | lockdep_assert_held_read(&client->cdev->lock); |
| 265 | |||
| 76 | ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); | 266 | ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); |
| 77 | if (ret) | 267 | if (ret) |
| 78 | goto out; | 268 | goto out; |
| @@ -152,7 +342,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) · 73 unmodified lines | |||
| 152 | } | 342 | } |
| 153 | 343 | ||
| 154 | /* Perform request. */ | 344 | /* Perform request. */ |
| 155 | status = ssam_request_sync(cdev->ctrl, &spec, &rsp); | 345 | status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); |
| 156 | if (status) | 346 | if (status) |
| 157 | goto out; | 347 | goto out; |
| 158 | 348 | ||
| @@ -177,48 +367,315 @@ out: · 18 unmodified lines | |||
| 177 | return ret; | 367 | return ret; |
| 178 | } | 368 | } |
| 179 | 369 | ||
| 180 | static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, | 370 | static long ssam_cdev_notif_register(struct ssam_cdev_client *client, |
| 371 | const struct ssam_cdev_notifier_desc __user *d) | ||
| 372 | { | ||
| 373 | struct ssam_cdev_notifier_desc desc; | ||
| 374 | long ret; | ||
| 375 | |||
| 376 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 377 | |||
| 378 | ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); | ||
| 379 | if (ret) | ||
| 380 | return ret; | ||
| 381 | |||
| 382 | return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); | ||
| 383 | } | ||
| 384 | |||
| 385 | static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, | ||
| 386 | const struct ssam_cdev_notifier_desc __user *d) | ||
| 387 | { | ||
| 388 | struct ssam_cdev_notifier_desc desc; | ||
| 389 | long ret; | ||
| 390 | |||
| 391 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 392 | |||
| 393 | ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); | ||
| 394 | if (ret) | ||
| 395 | return ret; | ||
| 396 | |||
| 397 | return ssam_cdev_notifier_unregister(client, desc.target_category); | ||
| 398 | } | ||
| 399 | |||
| 400 | static long ssam_cdev_event_enable(struct ssam_cdev_client *client, | ||
| 401 | const struct ssam_cdev_event_desc __user *d) | ||
| 402 | { | ||
| 403 | struct ssam_cdev_event_desc desc; | ||
| 404 | struct ssam_event_registry reg; | ||
| 405 | struct ssam_event_id id; | ||
| 406 | long ret; | ||
| 407 | |||
| 408 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 409 | |||
| 410 | /* Read descriptor from user-space. */ | ||
| 411 | ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); | ||
| 412 | if (ret) | ||
| 413 | return ret; | ||
| 414 | |||
| 415 | /* Translate descriptor. */ | ||
| 416 | reg.target_category = desc.reg.target_category; | ||
| 417 | reg.target_id = desc.reg.target_id; | ||
| 418 | reg.cid_enable = desc.reg.cid_enable; | ||
| 419 | reg.cid_disable = desc.reg.cid_disable; | ||
| 420 | |||
| 421 | id.target_category = desc.id.target_category; | ||
| 422 | id.instance = desc.id.instance; | ||
| 423 | |||
| 424 | /* Disable event. */ | ||
| 425 | return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); | ||
| 426 | } | ||
| 427 | |||
| 428 | static long ssam_cdev_event_disable(struct ssam_cdev_client *client, | ||
| 429 | const struct ssam_cdev_event_desc __user *d) | ||
| 430 | { | ||
| 431 | struct ssam_cdev_event_desc desc; | ||
| 432 | struct ssam_event_registry reg; | ||
| 433 | struct ssam_event_id id; | ||
| 434 | long ret; | ||
| 435 | |||
| 436 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 437 | |||
| 438 | /* Read descriptor from user-space. */ | ||
| 439 | ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); | ||
| 440 | if (ret) | ||
| 441 | return ret; | ||
| 442 | |||
| 443 | /* Translate descriptor. */ | ||
| 444 | reg.target_category = desc.reg.target_category; | ||
| 445 | reg.target_id = desc.reg.target_id; | ||
| 446 | reg.cid_enable = desc.reg.cid_enable; | ||
| 447 | reg.cid_disable = desc.reg.cid_disable; | ||
| 448 | |||
| 449 | id.target_category = desc.id.target_category; | ||
| 450 | id.instance = desc.id.instance; | ||
| 451 | |||
| 452 | /* Disable event. */ | ||
| 453 | return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); | ||
| 454 | } | ||
| 455 | |||
| 456 | |||
| 457 | /* -- File operations. ------------------------------------------------------ */ | ||
| 458 | |||
| 459 | static int ssam_cdev_device_open(struct inode *inode, struct file *filp) | ||
| 460 | { | ||
| 461 | struct miscdevice *mdev = filp->private_data; | ||
| 462 | struct ssam_cdev_client *client; | ||
| 463 | struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); | ||
| 464 | |||
| 465 | /* Initialize client */ | ||
| 466 | client = vzalloc(sizeof(*client)); | ||
| 467 | if (!client) | ||
| 468 | return -ENOMEM; | ||
| 469 | |||
| 470 | client->cdev = ssam_cdev_get(cdev); | ||
| 471 | |||
| 472 | INIT_LIST_HEAD(&client->node); | ||
| 473 | |||
| 474 | mutex_init(&client->notifier_lock); | ||
| 475 | |||
| 476 | mutex_init(&client->read_lock); | ||
| 477 | mutex_init(&client->write_lock); | ||
| 478 | INIT_KFIFO(client->buffer); | ||
| 479 | init_waitqueue_head(&client->waitq); | ||
| 480 | |||
| 481 | filp->private_data = client; | ||
| 482 | |||
| 483 | /* Attach client. */ | ||
| 484 | down_write(&cdev->client_lock); | ||
| 485 | |||
| 486 | if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { | ||
| 487 | up_write(&cdev->client_lock); | ||
| 488 | mutex_destroy(&client->write_lock); | ||
| 489 | mutex_destroy(&client->read_lock); | ||
| 490 | mutex_destroy(&client->notifier_lock); | ||
| 491 | ssam_cdev_put(client->cdev); | ||
| 492 | vfree(client); | ||
| 493 | return -ENODEV; | ||
| 494 | } | ||
| 495 | list_add_tail(&client->node, &cdev->client_list); | ||
| 496 | |||
| 497 | up_write(&cdev->client_lock); | ||
| 498 | |||
| 499 | stream_open(inode, filp); | ||
| 500 | return 0; | ||
| 501 | } | ||
| 502 | |||
| 503 | static int ssam_cdev_device_release(struct inode *inode, struct file *filp) | ||
| 504 | { | ||
| 505 | struct ssam_cdev_client *client = filp->private_data; | ||
| 506 | |||
| 507 | /* Force-unregister all remaining notifiers of this client. */ | ||
| 508 | ssam_cdev_notifier_unregister_all(client); | ||
| 509 | |||
| 510 | /* Detach client. */ | ||
| 511 | down_write(&client->cdev->client_lock); | ||
| 512 | list_del(&client->node); | ||
| 513 | up_write(&client->cdev->client_lock); | ||
| 514 | |||
| 515 | /* Free client. */ | ||
| 516 | mutex_destroy(&client->write_lock); | ||
| 517 | mutex_destroy(&client->read_lock); | ||
| 518 | |||
| 519 | mutex_destroy(&client->notifier_lock); | ||
| 520 | |||
| 521 | ssam_cdev_put(client->cdev); | ||
| 522 | vfree(client); | ||
| 523 | |||
| 524 | return 0; | ||
| 525 | } | ||
| 526 | |||
| 527 | static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, | ||
| 181 | unsigned long arg) | 528 | unsigned long arg) |
| 182 | { | 529 | { |
| 530 | lockdep_assert_held_read(&client->cdev->lock); | ||
| 531 | |||
| 183 | switch (cmd) { | 532 | switch (cmd) { |
| 184 | case SSAM_CDEV_REQUEST: | 533 | case SSAM_CDEV_REQUEST: |
| 185 | return ssam_cdev_request(cdev, arg); | 534 | return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); |
| 535 | |||
| 536 | case SSAM_CDEV_NOTIF_REGISTER: | ||
| 537 | return ssam_cdev_notif_register(client, | ||
| 538 | (struct ssam_cdev_notifier_desc __user *)arg); | ||
| 539 | |||
| 540 | case SSAM_CDEV_NOTIF_UNREGISTER: | ||
| 541 | return ssam_cdev_notif_unregister(client, | ||
| 542 | (struct ssam_cdev_notifier_desc __user *)arg); | ||
| 543 | |||
| 544 | case SSAM_CDEV_EVENT_ENABLE: | ||
| 545 | return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); | ||
| 546 | |||
| 547 | case SSAM_CDEV_EVENT_DISABLE: | ||
| 548 | return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); | ||
| 186 | 549 | ||
| 187 | default: | 550 | default: |
| 188 | return -ENOTTY; | 551 | return -ENOTTY; |
| 189 | } | 552 | } |
| 190 | } | 553 | } |
| 191 | 554 | ||
| 192 | static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, | 555 | static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| 193 | unsigned long arg) | ||
| 194 | { | 556 | { |
| 195 | struct ssam_cdev *cdev = file->private_data; | 557 | struct ssam_cdev_client *client = file->private_data; |
| 196 | long status; | 558 | long status; |
| 197 | 559 | ||
| 198 | /* Ensure that controller is valid for as long as we need it. */ | 560 | /* Ensure that controller is valid for as long as we need it. */ |
| 561 | if (down_read_killable(&client->cdev->lock)) | ||
| 562 | return -ERESTARTSYS; | ||
| 563 | |||
| 564 | if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { | ||
| 565 | up_read(&client->cdev->lock); | ||
| 566 | return -ENODEV; | ||
| 567 | } | ||
| 568 | |||
| 569 | status = __ssam_cdev_device_ioctl(client, cmd, arg); | ||
| 570 | |||
| 571 | up_read(&client->cdev->lock); | ||
| 572 | return status; | ||
| 573 | } | ||
| 574 | |||
| 575 | static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) | ||
| 576 | { | ||
| 577 | struct ssam_cdev_client *client = file->private_data; | ||
| 578 | struct ssam_cdev *cdev = client->cdev; | ||
| 579 | unsigned int copied; | ||
| 580 | int status = 0; | ||
| 581 | |||
| 199 | if (down_read_killable(&cdev->lock)) | 582 | if (down_read_killable(&cdev->lock)) |
| 200 | return -ERESTARTSYS; | 583 | return -ERESTARTSYS; |
| 201 | 584 | ||
| 202 | if (!cdev->ctrl) { | 585 | /* Make sure we're not shut down. */ |
| 586 | if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { | ||
| 203 | up_read(&cdev->lock); | 587 | up_read(&cdev->lock); |
| 204 | return -ENODEV; | 588 | return -ENODEV; |
| 205 | } | 589 | } |
| 206 | 590 | ||
| 207 | status = __ssam_cdev_device_ioctl(cdev, cmd, arg); | 591 | do { |
| 592 | /* Check availability, wait if necessary. */ | ||
| 593 | if (kfifo_is_empty(&client->buffer)) { | ||
| 594 | up_read(&cdev->lock); | ||
| 595 | |||
| 596 | if (file->f_flags & O_NONBLOCK) | ||
| 597 | return -EAGAIN; | ||
| 598 | |||
| 599 | status = wait_event_interruptible(client->waitq, | ||
| 600 | !kfifo_is_empty(&client->buffer) || | ||
| 601 | test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, | ||
| 602 | &cdev->flags)); | ||
| 603 | if (status < 0) | ||
| 604 | return status; | ||
| 605 | |||
| 606 | if (down_read_killable(&cdev->lock)) | ||
| 607 | return -ERESTARTSYS; | ||
| 608 | |||
| 609 | /* Need to check that we're not shut down again. */ | ||
| 610 | if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { | ||
| 611 | up_read(&cdev->lock); | ||
| 612 | return -ENODEV; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | /* Try to read from FIFO. */ | ||
| 617 | if (mutex_lock_interruptible(&client->read_lock)) { | ||
| 618 | up_read(&cdev->lock); | ||
| 619 | return -ERESTARTSYS; | ||
| 620 | } | ||
| 621 | |||
| 622 | status = kfifo_to_user(&client->buffer, buf, count, &copied); | ||
| 623 | mutex_unlock(&client->read_lock); | ||
| 624 | |||
| 625 | if (status < 0) { | ||
| 626 | up_read(&cdev->lock); | ||
| 627 | return status; | ||
| 628 | } | ||
| 629 | |||
| 630 | /* We might not have gotten anything, check this here. */ | ||
| 631 | if (copied == 0 && (file->f_flags & O_NONBLOCK)) { | ||
| 632 | up_read(&cdev->lock); | ||
| 633 | return -EAGAIN; | ||
| 634 | } | ||
| 635 | } while (copied == 0); | ||
| 208 | 636 | ||
| 209 | up_read(&cdev->lock); | 637 | up_read(&cdev->lock); |
| 210 | return status; | 638 | return copied; |
| 639 | } | ||
| 640 | |||
| 641 | static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) | ||
| 642 | { | ||
| 643 | struct ssam_cdev_client *client = file->private_data; | ||
| 644 | __poll_t events = 0; | ||
| 645 | |||
| 646 | if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) | ||
| 647 | return EPOLLHUP | EPOLLERR; | ||
| 648 | |||
| 649 | poll_wait(file, &client->waitq, pt); | ||
| 650 | |||
| 651 | if (!kfifo_is_empty(&client->buffer)) | ||
| 652 | events |= EPOLLIN | EPOLLRDNORM; | ||
| 653 | |||
| 654 | return events; | ||
| 655 | } | ||
| 656 | |||
| 657 | static int ssam_cdev_fasync(int fd, struct file *file, int on) | ||
| 658 | { | ||
| 659 | struct ssam_cdev_client *client = file->private_data; | ||
| 660 | |||
| 661 | return fasync_helper(fd, file, on, &client->fasync); | ||
| 211 | } | 662 | } |
| 212 | 663 | ||
| 213 | static const struct file_operations ssam_controller_fops = { | 664 | static const struct file_operations ssam_controller_fops = { |
| 214 | .owner = THIS_MODULE, | 665 | .owner = THIS_MODULE, |
| 215 | .open = ssam_cdev_device_open, | 666 | .open = ssam_cdev_device_open, |
| 216 | .release = ssam_cdev_device_release, | 667 | .release = ssam_cdev_device_release, |
| 668 | .read = ssam_cdev_read, | ||
| 669 | .poll = ssam_cdev_poll, | ||
| 670 | .fasync = ssam_cdev_fasync, | ||
| 217 | .unlocked_ioctl = ssam_cdev_device_ioctl, | 671 | .unlocked_ioctl = ssam_cdev_device_ioctl, |
| 218 | .compat_ioctl = ssam_cdev_device_ioctl, | 672 | .compat_ioctl = ssam_cdev_device_ioctl, |
| 219 | .llseek = noop_llseek, | 673 | .llseek = no_llseek, |
| 220 | }; | 674 | }; |
| 221 | 675 | ||
| 676 | |||
| 677 | /* -- Device and driver setup ----------------------------------------------- */ | ||
| 678 | |||
| 222 | static int ssam_dbg_device_probe(struct platform_device *pdev) | 679 | static int ssam_dbg_device_probe(struct platform_device *pdev) |
| 223 | { | 680 | { |
| 224 | struct ssam_controller *ctrl; | 681 | struct ssam_controller *ctrl; |
| @@ -236,6 +693,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) · 11 unmodified lines | |||
| 236 | kref_init(&cdev->kref); | 693 | kref_init(&cdev->kref); |
| 237 | init_rwsem(&cdev->lock); | 694 | init_rwsem(&cdev->lock); |
| 238 | cdev->ctrl = ctrl; | 695 | cdev->ctrl = ctrl; |
| 696 | cdev->dev = &pdev->dev; | ||
| 239 | 697 | ||
| 240 | cdev->mdev.parent = &pdev->dev; | 698 | cdev->mdev.parent = &pdev->dev; |
| 241 | cdev->mdev.minor = MISC_DYNAMIC_MINOR; | 699 | cdev->mdev.minor = MISC_DYNAMIC_MINOR; |
| @@ -243,6 +701,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) · 1 unmodified line | |||
| 243 | cdev->mdev.nodename = "surface/aggregator"; | 701 | cdev->mdev.nodename = "surface/aggregator"; |
| 244 | cdev->mdev.fops = &ssam_controller_fops; | 702 | cdev->mdev.fops = &ssam_controller_fops; |
| 245 | 703 | ||
| 704 | init_rwsem(&cdev->client_lock); | ||
| 705 | INIT_LIST_HEAD(&cdev->client_list); | ||
| 706 | |||
| 246 | status = misc_register(&cdev->mdev); | 707 | status = misc_register(&cdev->mdev); |
| 247 | if (status) { | 708 | if (status) { |
| 248 | kfree(cdev); | 709 | kfree(cdev); |
| @@ -256,8 +717,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) · 7 unmodified lines | |||
| 256 | static int ssam_dbg_device_remove(struct platform_device *pdev) | 717 | static int ssam_dbg_device_remove(struct platform_device *pdev) |
| 257 | { | 718 | { |
| 258 | struct ssam_cdev *cdev = platform_get_drvdata(pdev); | 719 | struct ssam_cdev *cdev = platform_get_drvdata(pdev); |
| 720 | struct ssam_cdev_client *client; | ||
| 259 | 721 | ||
| 260 | misc_deregister(&cdev->mdev); | 722 | /* |
| 723 | * Mark device as shut-down. Prevent new clients from being added and | ||
| 724 | * new operations from being executed. | ||
| 725 | */ | ||
| 726 | set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); | ||
| 727 | |||
| 728 | down_write(&cdev->client_lock); | ||
| 729 | |||
| 730 | /* Remove all notifiers registered by us. */ | ||
| 731 | list_for_each_entry(client, &cdev->client_list, node) { | ||
| 732 | ssam_cdev_notifier_unregister_all(client); | ||
| 733 | } | ||
| 734 | |||
| 735 | /* Wake up async clients. */ | ||
| 736 | list_for_each_entry(client, &cdev->client_list, node) { | ||
| 737 | kill_fasync(&client->fasync, SIGIO, POLL_HUP); | ||
| 738 | } | ||
| 739 | |||
| 740 | /* Wake up blocking clients. */ | ||
| 741 | list_for_each_entry(client, &cdev->client_list, node) { | ||
| 742 | wake_up_interruptible(&client->waitq); | ||
| 743 | } | ||
| 744 | |||
| 745 | up_write(&cdev->client_lock); | ||
| 261 | 746 | ||
| 262 | /* | 747 | /* |
| 263 | * The controller is only guaranteed to be valid for as long as the | 748 | * 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) · 2 unmodified lines | |||
| 266 | */ | 751 | */ |
| 267 | down_write(&cdev->lock); | 752 | down_write(&cdev->lock); |
| 268 | cdev->ctrl = NULL; | 753 | cdev->ctrl = NULL; |
| 754 | cdev->dev = NULL; | ||
| 269 | up_write(&cdev->lock); | 755 | up_write(&cdev->lock); |
| 270 | 756 | ||
| 757 | misc_deregister(&cdev->mdev); | ||
| 758 | |||
| 271 | ssam_cdev_put(cdev); | 759 | ssam_cdev_put(cdev); |
| 272 | return 0; | 760 | return 0; |
| 273 | } | 761 | } |
| @@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { · 118 unmodified lines | |||
| 119 | .parent = &ssam_node_hub_base, | 119 | .parent = &ssam_node_hub_base, |
| 120 | }; | 120 | }; |
| 121 | 121 | ||
| 122 | /* Devices for Surface Book 2. */ | 122 | /* |
| 123 | static const struct software_node *ssam_node_group_sb2[] = { | 123 | * Devices for 5th- and 6th-generations models: |
| 124 | * - Surface Book 2, | ||
| 125 | * - Surface Laptop 1 and 2, | ||
| 126 | * - Surface Pro 5 and 6. | ||
| 127 | */ | ||
| 128 | static const struct software_node *ssam_node_group_gen5[] = { | ||
| 124 | &ssam_node_root, | 129 | &ssam_node_root, |
| 125 | &ssam_node_tmp_pprof, | 130 | &ssam_node_tmp_pprof, |
| 126 | NULL, | 131 | NULL, |
| @@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { · 15 unmodified lines | |||
| 142 | NULL, | 147 | NULL, |
| 143 | }; | 148 | }; |
| 144 | 149 | ||
| 145 | /* Devices for Surface Laptop 1. */ | ||
| 146 | static const struct software_node *ssam_node_group_sl1[] = { | ||
| 147 | &ssam_node_root, | ||
| 148 | &ssam_node_tmp_pprof, | ||
| 149 | NULL, | ||
| 150 | }; | ||
| 151 | |||
| 152 | /* Devices for Surface Laptop 2. */ | ||
| 153 | static const struct software_node *ssam_node_group_sl2[] = { | ||
| 154 | &ssam_node_root, | ||
| 155 | &ssam_node_tmp_pprof, | ||
| 156 | NULL, | ||
| 157 | }; | ||
| 158 | |||
| 159 | /* Devices for Surface Laptop 3 and 4. */ | 150 | /* Devices for Surface Laptop 3 and 4. */ |
| 160 | static const struct software_node *ssam_node_group_sl3[] = { | 151 | static const struct software_node *ssam_node_group_sl3[] = { |
| 161 | &ssam_node_root, | 152 | &ssam_node_root, |
| @@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { · 15 unmodified lines | |||
| 177 | NULL, | 168 | NULL, |
| 178 | }; | 169 | }; |
| 179 | 170 | ||
| 180 | /* Devices for Surface Pro 5. */ | ||
| 181 | static const struct software_node *ssam_node_group_sp5[] = { | ||
| 182 | &ssam_node_root, | ||
| 183 | &ssam_node_tmp_pprof, | ||
| 184 | NULL, | ||
| 185 | }; | ||
| 186 | |||
| 187 | /* Devices for Surface Pro 6. */ | ||
| 188 | static const struct software_node *ssam_node_group_sp6[] = { | ||
| 189 | &ssam_node_root, | ||
| 190 | &ssam_node_tmp_pprof, | ||
| 191 | NULL, | ||
| 192 | }; | ||
| 193 | |||
| 194 | /* Devices for Surface Pro 7 and Surface Pro 7+. */ | 171 | /* Devices for Surface Pro 7 and Surface Pro 7+. */ |
| 195 | static const struct software_node *ssam_node_group_sp7[] = { | 172 | static const struct software_node *ssam_node_group_sp7[] = { |
| 196 | &ssam_node_root, | 173 | &ssam_node_root, |
| @@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { · 298 unmodified lines | |||
| 495 | 472 | ||
| 496 | static const struct acpi_device_id ssam_platform_hub_match[] = { | 473 | static const struct acpi_device_id ssam_platform_hub_match[] = { |
| 497 | /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ | 474 | /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ |
| 498 | { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, | 475 | { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, |
| 499 | 476 | ||
| 500 | /* Surface Pro 6 (OMBR >= 0x10) */ | 477 | /* Surface Pro 6 (OMBR >= 0x10) */ |
| 501 | { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, | 478 | { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, |
| 502 | 479 | ||
| 503 | /* Surface Pro 7 */ | 480 | /* Surface Pro 7 */ |
| 504 | { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, | 481 | { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, |
| @@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { · 2 unmodified lines | |||
| 507 | { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, | 484 | { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, |
| 508 | 485 | ||
| 509 | /* Surface Book 2 */ | 486 | /* Surface Book 2 */ |
| 510 | { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, | 487 | { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, |
| 511 | 488 | ||
| 512 | /* Surface Book 3 */ | 489 | /* Surface Book 3 */ |
| 513 | { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, | 490 | { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, |
| 514 | 491 | ||
| 515 | /* Surface Laptop 1 */ | 492 | /* Surface Laptop 1 */ |
| 516 | { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, | 493 | { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, |
| 517 | 494 | ||
| 518 | /* Surface Laptop 2 */ | 495 | /* Surface Laptop 2 */ |
| 519 | { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, | 496 | { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, |
| 520 | 497 | ||
| 521 | /* Surface Laptop 3 (13", Intel) */ | 498 | /* Surface Laptop 3 (13", Intel) */ |
| 522 | { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, | 499 | { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, |
| @@ -415,16 +415,17 @@ config HP_ACCEL · 414 unmodified lines | |||
| 415 | To compile this driver as a module, choose M here: the module will | 415 | To compile this driver as a module, choose M here: the module will |
| 416 | be called hp_accel. | 416 | be called hp_accel. |
| 417 | 417 | ||
| 418 | config HP_WIRELESS | 418 | config WIRELESS_HOTKEY |
| 419 | tristate "HP wireless button" | 419 | tristate "Wireless hotkey button" |
| 420 | depends on ACPI | 420 | depends on ACPI |
| 421 | depends on INPUT | 421 | depends on INPUT |
| 422 | help | 422 | help |
| 423 | This driver provides supports for new HP wireless button for Windows 8. | 423 | This driver provides supports for the wireless buttons found on some AMD, |
| 424 | HP, & Xioami laptops. | ||
| 424 | On such systems the driver should load automatically (via ACPI alias). | 425 | On such systems the driver should load automatically (via ACPI alias). |
| 425 | 426 | ||
| 426 | To compile this driver as a module, choose M here: the module will | 427 | To compile this driver as a module, choose M here: the module will |
| 427 | be called hp-wireless. | 428 | be called wireless-hotkey. |
| 428 | 429 | ||
| 429 | config HP_WMI | 430 | config HP_WMI |
| 430 | tristate "HP WMI extras" | 431 | tristate "HP WMI extras" |
| @@ -639,6 +640,19 @@ config THINKPAD_ACPI_HOTKEY_POLL · 208 unmodified lines | |||
| 639 | If you are not sure, say Y here. The driver enables polling only if | 640 | If you are not sure, say Y here. The driver enables polling only if |
| 640 | it is strictly necessary to do so. | 641 | it is strictly necessary to do so. |
| 641 | 642 | ||
| 643 | config THINKPAD_LMI | ||
| 644 | tristate "Lenovo WMI-based systems management driver" | ||
| 645 | depends on ACPI_WMI | ||
| 646 | select FW_ATTR_CLASS | ||
| 647 | help | ||
| 648 | This driver allows changing BIOS settings on Lenovo machines whose | ||
| 649 | BIOS support the WMI interface. | ||
| 650 | |||
| 651 | To compile this driver as a module, choose M here: the module will | ||
| 652 | be called think-lmi. | ||
| 653 | |||
| 654 | source "drivers/platform/x86/intel/Kconfig" | ||
| 655 | |||
| 642 | config INTEL_ATOMISP2_LED | 656 | config INTEL_ATOMISP2_LED |
| 643 | tristate "Intel AtomISP2 camera LED driver" | 657 | tristate "Intel AtomISP2 camera LED driver" |
| 644 | depends on GPIOLIB && LEDS_GPIO | 658 | depends on GPIOLIB && LEDS_GPIO |
| @@ -673,30 +687,6 @@ config INTEL_ATOMISP2_PM · 28 unmodified lines | |||
| 673 | To compile this driver as a module, choose M here: the module | 687 | To compile this driver as a module, choose M here: the module |
| 674 | will be called intel_atomisp2_pm. | 688 | will be called intel_atomisp2_pm. |
| 675 | 689 | ||
| 676 | config INTEL_CHT_INT33FE | ||
| 677 | tristate "Intel Cherry Trail ACPI INT33FE Driver" | ||
| 678 | depends on X86 && ACPI && I2C && REGULATOR | ||
| 679 | depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) | ||
| 680 | depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) | ||
| 681 | depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) | ||
| 682 | help | ||
| 683 | This driver add support for the INT33FE ACPI device found on | ||
| 684 | some Intel Cherry Trail devices. | ||
| 685 | |||
| 686 | There are two kinds of INT33FE ACPI device possible: for hardware | ||
| 687 | with USB Type-C and Micro-B connectors. This driver supports both. | ||
| 688 | |||
| 689 | The INT33FE ACPI device has a CRS table with I2cSerialBusV2 | ||
| 690 | resources for Fuel Gauge Controller and (in the Type-C variant) | ||
| 691 | FUSB302 USB Type-C Controller and PI3USB30532 USB switch. | ||
| 692 | This driver instantiates i2c-clients for these, so that standard | ||
| 693 | i2c drivers for these chips can bind to the them. | ||
| 694 | |||
| 695 | If you enable this driver it is advised to also select | ||
| 696 | CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B | ||
| 697 | device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m | ||
| 698 | for Type-C device. | ||
| 699 | |||
| 700 | config INTEL_HID_EVENT | 690 | config INTEL_HID_EVENT |
| 701 | tristate "INTEL HID Event" | 691 | tristate "INTEL HID Event" |
| 702 | depends on ACPI | 692 | depends on ACPI |
| @@ -1076,6 +1066,9 @@ config TOUCHSCREEN_DMI · 373 unmodified lines | |||
| 1076 | the OS-image for the device. This option supplies the missing info. | 1066 | the OS-image for the device. This option supplies the missing info. |
| 1077 | Enable this for x86 tablets with Silead or Chipone touchscreens. | 1067 | Enable this for x86 tablets with Silead or Chipone touchscreens. |
| 1078 | 1068 | ||
| 1069 | config FW_ATTR_CLASS | ||
| 1070 | tristate | ||
| 1071 | |||
| 1079 | config INTEL_IMR | 1072 | config INTEL_IMR |
| 1080 | bool "Intel Isolated Memory Region support" | 1073 | bool "Intel Isolated Memory Region support" |
| 1081 | depends on X86_INTEL_QUARK && IOSF_MBI | 1074 | depends on X86_INTEL_QUARK && IOSF_MBI |
| @@ -52,7 +52,6 @@ obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o · 51 unmodified lines | |||
| 52 | 52 | ||
| 53 | # Hewlett Packard | 53 | # Hewlett Packard |
| 54 | obj-$(CONFIG_HP_ACCEL) += hp_accel.o | 54 | obj-$(CONFIG_HP_ACCEL) += hp_accel.o |
| 55 | obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o | ||
| 56 | obj-$(CONFIG_HP_WMI) += hp-wmi.o | 55 | obj-$(CONFIG_HP_WMI) += hp-wmi.o |
| 57 | obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o | 56 | obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o |
| 58 | 57 | ||
| @@ -64,14 +63,13 @@ obj-$(CONFIG_IBM_RTL) += ibm_rtl.o · 5 unmodified lines | |||
| 64 | obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o | 63 | obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o |
| 65 | obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o | 64 | obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o |
| 66 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o | 65 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o |
| 66 | obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o | ||
| 67 | 67 | ||
| 68 | # Intel | 68 | # Intel |
| 69 | obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ | ||
| 70 | |||
| 69 | obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o | 71 | obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o |
| 70 | obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o | 72 | obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o |
| 71 | obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o | ||
| 72 | intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ | ||
| 73 | intel_cht_int33fe_typec.o \ | ||
| 74 | intel_cht_int33fe_microb.o | ||
| 75 | obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o | 73 | obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o |
| 76 | obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o | 74 | obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o |
| 77 | obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o | 75 | obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o |
| @@ -112,9 +110,11 @@ obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o · 34 unmodified lines | |||
| 112 | obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o | 110 | obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o |
| 113 | 111 | ||
| 114 | # Platform drivers | 112 | # Platform drivers |
| 113 | obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o | ||
| 115 | obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o | 114 | obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o |
| 116 | obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o | 115 | obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o |
| 117 | obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o | 116 | obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o |
| 117 | obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o | ||
| 118 | 118 | ||
| 119 | # Intel uncore drivers | 119 | # Intel uncore drivers |
| 120 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o | 120 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o |
| @@ -110,11 +110,6 @@ static struct quirk_entry quirk_asus_forceals = { · 109 unmodified lines | |||
| 110 | .wmi_force_als_set = true, | 110 | .wmi_force_als_set = true, |
| 111 | }; | 111 | }; |
| 112 | 112 | ||
| 113 | static struct quirk_entry quirk_asus_vendor_backlight = { | ||
| 114 | .wmi_backlight_power = true, | ||
| 115 | .wmi_backlight_set_devstate = true, | ||
| 116 | }; | ||
| 117 | |||
| 118 | static struct quirk_entry quirk_asus_use_kbd_dock_devid = { | 113 | static struct quirk_entry quirk_asus_use_kbd_dock_devid = { |
| 119 | .use_kbd_dock_devid = true, | 114 | .use_kbd_dock_devid = true, |
| 120 | }; | 115 | }; |
| @@ -425,78 +420,6 @@ static const struct dmi_system_id asus_quirks[] = { · 304 unmodified lines | |||
| 425 | }, | 420 | }, |
| 426 | .driver_data = &quirk_asus_forceals, | 421 | .driver_data = &quirk_asus_forceals, |
| 427 | }, | 422 | }, |
| 428 | { | ||
| 429 | .callback = dmi_matched, | ||
| 430 | .ident = "ASUSTeK COMPUTER INC. GA401IH", | ||
| 431 | .matches = { | ||
| 432 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 433 | DMI_MATCH(DMI_PRODUCT_NAME, "GA401IH"), | ||
| 434 | }, | ||
| 435 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 436 | }, | ||
| 437 | { | ||
| 438 | .callback = dmi_matched, | ||
| 439 | .ident = "ASUSTeK COMPUTER INC. GA401II", | ||
| 440 | .matches = { | ||
| 441 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 442 | DMI_MATCH(DMI_PRODUCT_NAME, "GA401II"), | ||
| 443 | }, | ||
| 444 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 445 | }, | ||
| 446 | { | ||
| 447 | .callback = dmi_matched, | ||
| 448 | .ident = "ASUSTeK COMPUTER INC. GA401IU", | ||
| 449 | .matches = { | ||
| 450 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 451 | DMI_MATCH(DMI_PRODUCT_NAME, "GA401IU"), | ||
| 452 | }, | ||
| 453 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 454 | }, | ||
| 455 | { | ||
| 456 | .callback = dmi_matched, | ||
| 457 | .ident = "ASUSTeK COMPUTER INC. GA401IV", | ||
| 458 | .matches = { | ||
| 459 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 460 | DMI_MATCH(DMI_PRODUCT_NAME, "GA401IV"), | ||
| 461 | }, | ||
| 462 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 463 | }, | ||
| 464 | { | ||
| 465 | .callback = dmi_matched, | ||
| 466 | .ident = "ASUSTeK COMPUTER INC. GA401IVC", | ||
| 467 | .matches = { | ||
| 468 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 469 | DMI_MATCH(DMI_PRODUCT_NAME, "GA401IVC"), | ||
| 470 | }, | ||
| 471 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 472 | }, | ||
| 473 | { | ||
| 474 | .callback = dmi_matched, | ||
| 475 | .ident = "ASUSTeK COMPUTER INC. GA502II", | ||
| 476 | .matches = { | ||
| 477 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 478 | DMI_MATCH(DMI_PRODUCT_NAME, "GA502II"), | ||
| 479 | }, | ||
| 480 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 481 | }, | ||
| 482 | { | ||
| 483 | .callback = dmi_matched, | ||
| 484 | .ident = "ASUSTeK COMPUTER INC. GA502IU", | ||
| 485 | .matches = { | ||
| 486 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 487 | DMI_MATCH(DMI_PRODUCT_NAME, "GA502IU"), | ||
| 488 | }, | ||
| 489 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 490 | }, | ||
| 491 | { | ||
| 492 | .callback = dmi_matched, | ||
| 493 | .ident = "ASUSTeK COMPUTER INC. GA502IV", | ||
| 494 | .matches = { | ||
| 495 | DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), | ||
| 496 | DMI_MATCH(DMI_PRODUCT_NAME, "GA502IV"), | ||
| 497 | }, | ||
| 498 | .driver_data = &quirk_asus_vendor_backlight, | ||
| 499 | }, | ||
| 500 | { | 423 | { |
| 501 | .callback = dmi_matched, | 424 | .callback = dmi_matched, |
| 502 | .ident = "Asus Transformer T100TA / T100HA / T100CHI", | 425 | .ident = "Asus Transformer T100TA / T100HA / T100CHI", |
| @@ -5,7 +5,6 @@ · 4 unmodified lines | |||
| 5 | 5 | ||
| 6 | menuconfig X86_PLATFORM_DRIVERS_DELL | 6 | menuconfig X86_PLATFORM_DRIVERS_DELL |
| 7 | bool "Dell X86 Platform Specific Device Drivers" | 7 | bool "Dell X86 Platform Specific Device Drivers" |
| 8 | default n | ||
| 9 | depends on X86_PLATFORM_DEVICES | 8 | depends on X86_PLATFORM_DEVICES |
| 10 | help | 9 | help |
| 11 | Say Y here to get to see options for device drivers for various | 10 | Say Y here to get to see options for device drivers for various |
| @@ -53,6 +52,7 @@ config DELL_LAPTOP · 41 unmodified lines | |||
| 53 | depends on BACKLIGHT_CLASS_DEVICE | 52 | depends on BACKLIGHT_CLASS_DEVICE |
| 54 | depends on ACPI_VIDEO || ACPI_VIDEO = n | 53 | depends on ACPI_VIDEO || ACPI_VIDEO = n |
| 55 | depends on RFKILL || RFKILL = n | 54 | depends on RFKILL || RFKILL = n |
| 55 | depends on DELL_WMI || DELL_WMI = n | ||
| 56 | depends on SERIO_I8042 | 56 | depends on SERIO_I8042 |
| 57 | depends on DELL_SMBIOS | 57 | depends on DELL_SMBIOS |
| 58 | select POWER_SUPPLY | 58 | select POWER_SUPPLY |
| @@ -164,6 +164,14 @@ config DELL_WMI · 105 unmodified lines | |||
| 164 | To compile this driver as a module, choose M here: the module will | 164 | To compile this driver as a module, choose M here: the module will |
| 165 | be called dell-wmi. | 165 | be called dell-wmi. |
| 166 | 166 | ||
| 167 | config DELL_WMI_PRIVACY | ||
| 168 | bool "Dell WMI Hardware Privacy Support" | ||
| 169 | depends on DELL_WMI | ||
| 170 | depends on LEDS_TRIGGER_AUDIO | ||
| 171 | help | ||
| 172 | This option adds integration with the "Dell Hardware Privacy" | ||
| 173 | feature of Dell laptops to the dell-wmi driver. | ||
| 174 | |||
| 167 | config DELL_WMI_AIO | 175 | config DELL_WMI_AIO |
| 168 | tristate "WMI Hotkeys for Dell All-In-One series" | 176 | tristate "WMI Hotkeys for Dell All-In-One series" |
| 169 | default m | 177 | default m |
| @@ -197,6 +205,7 @@ config DELL_WMI_SYSMAN · 27 unmodified lines | |||
| 197 | depends on ACPI_WMI | 205 | depends on ACPI_WMI |
| 198 | depends on DMI | 206 | depends on DMI |
| 199 | select NLS | 207 | select NLS |
| 208 | select FW_ATTR_CLASS | ||
| 200 | help | 209 | help |
| 201 | This driver allows changing BIOS settings on many Dell machines from | 210 | This driver allows changing BIOS settings on many Dell machines from |
| 202 | 2018 and newer without the use of any additional software. | 211 | 2018 and newer without the use of any additional software. |
| @@ -15,6 +15,8 @@ dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o · 14 unmodified lines | |||
| 15 | dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o | 15 | dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o |
| 16 | obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o | 16 | obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o |
| 17 | obj-$(CONFIG_DELL_WMI) += dell-wmi.o | 17 | obj-$(CONFIG_DELL_WMI) += dell-wmi.o |
| 18 | dell-wmi-objs := dell-wmi-base.o | ||
| 19 | dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o | ||
| 18 | obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o | 20 | obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o |
| 19 | obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o | 21 | obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o |
| 20 | obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o | 22 | obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o |
| @@ -394,8 +394,7 @@ static int host_control_smi(void) · 393 unmodified lines | |||
| 394 | 394 | ||
| 395 | /* wait a few to see if it executed */ | 395 | /* wait a few to see if it executed */ |
| 396 | num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; | 396 | num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; |
| 397 | while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) | 397 | while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) { |
| 398 | == ESM_STATUS_CMD_UNSUCCESSFUL) { | ||
| 399 | num_ticks--; | 398 | num_ticks--; |
| 400 | if (num_ticks == EXPIRED_TIMER) | 399 | if (num_ticks == EXPIRED_TIMER) |
| 401 | return -ETIME; | 400 | return -ETIME; |
| @@ -31,6 +31,8 @@ · 30 unmodified lines | |||
| 31 | #include "dell-rbtn.h" | 31 | #include "dell-rbtn.h" |
| 32 | #include "dell-smbios.h" | 32 | #include "dell-smbios.h" |
| 33 | 33 | ||
| 34 | #include "dell-wmi-privacy.h" | ||
| 35 | |||
| 34 | struct quirk_entry { | 36 | struct quirk_entry { |
| 35 | bool touchpad_led; | 37 | bool touchpad_led; |
| 36 | bool kbd_led_not_present; | 38 | bool kbd_led_not_present; |
| @@ -90,6 +92,7 @@ static struct rfkill *wifi_rfkill; · 53 unmodified lines | |||
| 90 | static struct rfkill *bluetooth_rfkill; | 92 | static struct rfkill *bluetooth_rfkill; |
| 91 | static struct rfkill *wwan_rfkill; | 93 | static struct rfkill *wwan_rfkill; |
| 92 | static bool force_rfkill; | 94 | static bool force_rfkill; |
| 95 | static bool micmute_led_registered; | ||
| 93 | 96 | ||
| 94 | module_param(force_rfkill, bool, 0444); | 97 | module_param(force_rfkill, bool, 0444); |
| 95 | MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); | 98 | MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); |
| @@ -2205,11 +2208,13 @@ static int __init dell_init(void) · 2109 unmodified lines | |||
| 2205 | dell_laptop_register_notifier(&dell_laptop_notifier); | 2208 | dell_laptop_register_notifier(&dell_laptop_notifier); |
| 2206 | 2209 | ||
| 2207 | if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && | 2210 | if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && |
| 2208 | dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { | 2211 | dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE) && |
| 2212 | !dell_privacy_has_mic_mute()) { | ||
| 2209 | micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); | 2213 | micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); |
| 2210 | ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); | 2214 | ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); |
| 2211 | if (ret < 0) | 2215 | if (ret < 0) |
| 2212 | goto fail_led; | 2216 | goto fail_led; |
| 2217 | micmute_led_registered = true; | ||
| 2213 | } | 2218 | } |
| 2214 | 2219 | ||
| 2215 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) | 2220 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) |
| @@ -2257,7 +2262,8 @@ static int __init dell_init(void) · 41 unmodified lines | |||
| 2257 | fail_get_brightness: | 2262 | fail_get_brightness: |
| 2258 | backlight_device_unregister(dell_backlight_device); | 2263 | backlight_device_unregister(dell_backlight_device); |
| 2259 | fail_backlight: | 2264 | fail_backlight: |
| 2260 | led_classdev_unregister(&micmute_led_cdev); | 2265 | if (micmute_led_registered) |
| 2266 | led_classdev_unregister(&micmute_led_cdev); | ||
| 2261 | fail_led: | 2267 | fail_led: |
| 2262 | dell_cleanup_rfkill(); | 2268 | dell_cleanup_rfkill(); |
| 2263 | fail_rfkill: | 2269 | fail_rfkill: |
| @@ -2278,7 +2284,8 @@ static void __exit dell_exit(void) · 14 unmodified lines | |||
| 2278 | touchpad_led_exit(); | 2284 | touchpad_led_exit(); |
| 2279 | kbd_led_exit(); | 2285 | kbd_led_exit(); |
| 2280 | backlight_device_unregister(dell_backlight_device); | 2286 | backlight_device_unregister(dell_backlight_device); |
| 2281 | led_classdev_unregister(&micmute_led_cdev); | 2287 | if (micmute_led_registered) |
| 2288 | led_classdev_unregister(&micmute_led_cdev); | ||
| 2282 | dell_cleanup_rfkill(); | 2289 | dell_cleanup_rfkill(); |
| 2283 | if (platform_device) { | 2290 | if (platform_device) { |
| 2284 | platform_device_unregister(platform_device); | 2291 | platform_device_unregister(platform_device); |
| @@ -27,6 +27,7 @@ · 26 unmodified lines | |||
| 27 | #include <acpi/video.h> | 27 | #include <acpi/video.h> |
| 28 | #include "dell-smbios.h" | 28 | #include "dell-smbios.h" |
| 29 | #include "dell-wmi-descriptor.h" | 29 | #include "dell-wmi-descriptor.h" |
| 30 | #include "dell-wmi-privacy.h" | ||
| 30 | 31 | ||
| 31 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); | 32 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |
| 32 | MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); | 33 | MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); |
| @@ -427,7 +428,6 @@ static void dell_wmi_notify(struct wmi_device *wdev, · 394 unmodified lines | |||
| 427 | 428 | ||
| 428 | switch (buffer_entry[1]) { | 429 | switch (buffer_entry[1]) { |
| 429 | case 0x0000: /* One key pressed or event occurred */ | 430 | case 0x0000: /* One key pressed or event occurred */ |
| 430 | case 0x0012: /* Event with extended data occurred */ | ||
| 431 | if (len > 2) | 431 | if (len > 2) |
| 432 | dell_wmi_process_key(wdev, buffer_entry[1], | 432 | dell_wmi_process_key(wdev, buffer_entry[1], |
| 433 | buffer_entry[2]); | 433 | buffer_entry[2]); |
| @@ -439,6 +439,13 @@ static void dell_wmi_notify(struct wmi_device *wdev, · 5 unmodified lines | |||
| 439 | dell_wmi_process_key(wdev, buffer_entry[1], | 439 | dell_wmi_process_key(wdev, buffer_entry[1], |
| 440 | buffer_entry[i]); | 440 | buffer_entry[i]); |
| 441 | break; | 441 | break; |
| 442 | case 0x0012: | ||
| 443 | if ((len > 4) && dell_privacy_process_event(buffer_entry[1], buffer_entry[3], | ||
| 444 | buffer_entry[4])) | ||
| 445 | /* dell_privacy_process_event has handled the event */; | ||
| 446 | else if (len > 2) | ||
| 447 | dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2]); | ||
| 448 | break; | ||
| 442 | default: /* Unknown event */ | 449 | default: /* Unknown event */ |
| 443 | pr_info("Unknown WMI event type 0x%x\n", | 450 | pr_info("Unknown WMI event type 0x%x\n", |
| 444 | (int)buffer_entry[1]); | 451 | (int)buffer_entry[1]); |
| @@ -747,6 +754,10 @@ static int __init dell_wmi_init(void) · 302 unmodified lines | |||
| 747 | } | 754 | } |
| 748 | } | 755 | } |
| 749 | 756 | ||
| 757 | err = dell_privacy_register_driver(); | ||
| 758 | if (err) | ||
| 759 | return err; | ||
| 760 | |||
| 750 | return wmi_driver_register(&dell_wmi_driver); | 761 | return wmi_driver_register(&dell_wmi_driver); |
| 751 | } | 762 | } |
| 752 | late_initcall(dell_wmi_init); | 763 | late_initcall(dell_wmi_init); |
| @@ -757,6 +768,7 @@ static void __exit dell_wmi_exit(void) · 4 unmodified lines | |||
| 757 | dell_wmi_events_set_enabled(false); | 768 | dell_wmi_events_set_enabled(false); |
| 758 | 769 | ||
| 759 | wmi_driver_unregister(&dell_wmi_driver); | 770 | wmi_driver_unregister(&dell_wmi_driver); |
| 771 | dell_privacy_unregister_driver(); | ||
| 760 | } | 772 | } |
| 761 | module_exit(dell_wmi_exit); | 773 | module_exit(dell_wmi_exit); |
| 762 | 774 | ||
| @@ -0,0 +1,391 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | /* | ||
| 3 | * Dell privacy notification driver | ||
| 4 | * | ||
| 5 | * Copyright (C) 2021 Dell Inc. All Rights Reserved. | ||
| 6 | */ | ||
| 7 | |||
| 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
| 9 | |||
| 10 | #include <linux/acpi.h> | ||
| 11 | #include <linux/bitops.h> | ||
| 12 | #include <linux/input.h> | ||
| 13 | #include <linux/input/sparse-keymap.h> | ||
| 14 | #include <linux/list.h> | ||
| 15 | #include <linux/leds.h> | ||
| 16 | #include <linux/module.h> | ||
| 17 | #include <linux/wmi.h> | ||
| 18 | |||
| 19 | #include "dell-wmi-privacy.h" | ||
| 20 | |||
| 21 | #define DELL_PRIVACY_GUID "6932965F-1671-4CEB-B988-D3AB0A901919" | ||
| 22 | #define MICROPHONE_STATUS BIT(0) | ||
| 23 | #define CAMERA_STATUS BIT(1) | ||
| 24 | #define DELL_PRIVACY_AUDIO_EVENT 0x1 | ||
| 25 | #define DELL_PRIVACY_CAMERA_EVENT 0x2 | ||
| 26 | #define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev) | ||
| 27 | |||
| 28 | /* | ||
| 29 | * The wmi_list is used to store the privacy_priv struct with mutex protecting | ||
| 30 | */ | ||
| 31 | static LIST_HEAD(wmi_list); | ||
| 32 | static DEFINE_MUTEX(list_mutex); | ||
| 33 | |||
| 34 | struct privacy_wmi_data { | ||
| 35 | struct input_dev *input_dev; | ||
| 36 | struct wmi_device *wdev; | ||
| 37 | struct list_head list; | ||
| 38 | struct led_classdev cdev; | ||
| 39 | u32 features_present; | ||
| 40 | u32 last_status; | ||
| 41 | }; | ||
| 42 | |||
| 43 | /* DELL Privacy Type */ | ||
| 44 | enum dell_hardware_privacy_type { | ||
| 45 | DELL_PRIVACY_TYPE_AUDIO = 0, | ||
| 46 | DELL_PRIVACY_TYPE_CAMERA, | ||
| 47 | DELL_PRIVACY_TYPE_SCREEN, | ||
| 48 | DELL_PRIVACY_TYPE_MAX, | ||
| 49 | }; | ||
| 50 | |||
| 51 | static const char * const privacy_types[DELL_PRIVACY_TYPE_MAX] = { | ||
| 52 | [DELL_PRIVACY_TYPE_AUDIO] = "Microphone", | ||
| 53 | [DELL_PRIVACY_TYPE_CAMERA] = "Camera Shutter", | ||
| 54 | [DELL_PRIVACY_TYPE_SCREEN] = "ePrivacy Screen", | ||
| 55 | }; | ||
| 56 | |||
| 57 | /* | ||
| 58 | * Keymap for WMI privacy events of type 0x0012 | ||
| 59 | */ | ||
| 60 | static const struct key_entry dell_wmi_keymap_type_0012[] = { | ||
| 61 | /* privacy mic mute */ | ||
| 62 | { KE_KEY, 0x0001, { KEY_MICMUTE } }, | ||
| 63 | /* privacy camera mute */ | ||
| 64 | { KE_SW, 0x0002, { SW_CAMERA_LENS_COVER } }, | ||
| 65 | { KE_END, 0}, | ||
| 66 | }; | ||
| 67 | |||
| 68 | bool dell_privacy_has_mic_mute(void) | ||
| 69 | { | ||
| 70 | struct privacy_wmi_data *priv; | ||
| 71 | |||
| 72 | mutex_lock(&list_mutex); | ||
| 73 | priv = list_first_entry_or_null(&wmi_list, | ||
| 74 | struct privacy_wmi_data, | ||
| 75 | list); | ||
| 76 | mutex_unlock(&list_mutex); | ||
| 77 | |||
| 78 | return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); | ||
| 79 | } | ||
| 80 | EXPORT_SYMBOL_GPL(dell_privacy_has_mic_mute); | ||
| 81 | |||
| 82 | /* | ||
| 83 | * The flow of privacy event: | ||
| 84 | * 1) User presses key. HW does stuff with this key (timeout is started) | ||
| 85 | * 2) WMI event is emitted from BIOS | ||
| 86 | * 3) WMI event is received by dell-privacy | ||
| 87 | * 4) KEY_MICMUTE emitted from dell-privacy | ||
| 88 | * 5) Userland picks up key and modifies kcontrol for SW mute | ||
| 89 | * 6) Codec kernel driver catches and calls ledtrig_audio_set which will call | ||
| 90 | * led_set_brightness() on the LED registered by dell_privacy_leds_setup() | ||
| 91 | * 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute activates. | ||
| 92 | * If the EC is not notified then the HW mic mute will activate when the timeout | ||
| 93 | * triggers, just a bit later than with the active ack. | ||
| 94 | */ | ||
| 95 | bool dell_privacy_process_event(int type, int code, int status) | ||
| 96 | { | ||
| 97 | struct privacy_wmi_data *priv; | ||
| 98 | const struct key_entry *key; | ||
| 99 | bool ret = false; | ||
| 100 | |||
| 101 | mutex_lock(&list_mutex); | ||
| 102 | priv = list_first_entry_or_null(&wmi_list, | ||
| 103 | struct privacy_wmi_data, | ||
| 104 | list); | ||
| 105 | if (!priv) | ||
| 106 | goto error; | ||
| 107 | |||
| 108 | key = sparse_keymap_entry_from_scancode(priv->input_dev, (type << 16) | code); | ||
| 109 | if (!key) { | ||
| 110 | dev_warn(&priv->wdev->dev, "Unknown key with type 0x%04x and code 0x%04x pressed\n", | ||
| 111 | type, code); | ||
| 112 | goto error; | ||
| 113 | } | ||
| 114 | dev_dbg(&priv->wdev->dev, "Key with type 0x%04x and code 0x%04x pressed\n", type, code); | ||
| 115 | |||
| 116 | switch (code) { | ||
| 117 | case DELL_PRIVACY_AUDIO_EVENT: /* Mic mute */ | ||
| 118 | case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */ | ||
| 119 | priv->last_status = status; | ||
| 120 | sparse_keymap_report_entry(priv->input_dev, key, 1, true); | ||
| 121 | ret = true; | ||
| 122 | break; | ||
| 123 | default: | ||
| 124 | dev_dbg(&priv->wdev->dev, "unknown event type 0x%04x 0x%04x\n", type, code); | ||
| 125 | } | ||
| 126 | |||
| 127 | error: | ||
| 128 | mutex_unlock(&list_mutex); | ||
| 129 | return ret; | ||
| 130 | } | ||
| 131 | |||
| 132 | static ssize_t dell_privacy_supported_type_show(struct device *dev, | ||
| 133 | struct device_attribute *attr, | ||
| 134 | char *buf) | ||
| 135 | { | ||
| 136 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | ||
| 137 | enum dell_hardware_privacy_type type; | ||
| 138 | u32 privacy_list; | ||
| 139 | int len = 0; | ||
| 140 | |||
| 141 | privacy_list = priv->features_present; | ||
| 142 | for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { | ||
| 143 | if (privacy_list & BIT(type)) | ||
| 144 | len += sysfs_emit_at(buf, len, "[%s] [supported]\n", privacy_types[type]); | ||
| 145 | else | ||
| 146 | len += sysfs_emit_at(buf, len, "[%s] [unsupported]\n", privacy_types[type]); | ||
| 147 | } | ||
| 148 | |||
| 149 | return len; | ||
| 150 | } | ||
| 151 | |||
| 152 | static ssize_t dell_privacy_current_state_show(struct device *dev, | ||
| 153 | struct device_attribute *attr, | ||
| 154 | char *buf) | ||
| 155 | { | ||
| 156 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | ||
| 157 | u32 privacy_supported = priv->features_present; | ||
| 158 | enum dell_hardware_privacy_type type; | ||
| 159 | u32 privacy_state = priv->last_status; | ||
| 160 | int len = 0; | ||
| 161 | |||
| 162 | for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { | ||
| 163 | if (privacy_supported & BIT(type)) { | ||
| 164 | if (privacy_state & BIT(type)) | ||
| 165 | len += sysfs_emit_at(buf, len, "[%s] [unmuted]\n", privacy_types[type]); | ||
| 166 | else | ||
| 167 | len += sysfs_emit_at(buf, len, "[%s] [muted]\n", privacy_types[type]); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | return len; | ||
| 172 | } | ||
| 173 | |||
| 174 | static DEVICE_ATTR_RO(dell_privacy_supported_type); | ||
| 175 | static DEVICE_ATTR_RO(dell_privacy_current_state); | ||
| 176 | |||
| 177 | static struct attribute *privacy_attributes[] = { | ||
| 178 | &dev_attr_dell_privacy_supported_type.attr, | ||
| 179 | &dev_attr_dell_privacy_current_state.attr, | ||
| 180 | NULL, | ||
| 181 | }; | ||
| 182 | |||
| 183 | static const struct attribute_group privacy_attribute_group = { | ||
| 184 | .attrs = privacy_attributes | ||
| 185 | }; | ||
| 186 | |||
| 187 | /* | ||
| 188 | * Describes the Device State class exposed by BIOS which can be consumed by | ||
| 189 | * various applications interested in knowing the Privacy feature capabilities. | ||
| 190 | * class DeviceState | ||
| 191 | * { | ||
| 192 | * [key, read] string InstanceName; | ||
| 193 | * [read] boolean ReadOnly; | ||
| 194 | * | ||
| 195 | * [WmiDataId(1), read] uint32 DevicesSupported; | ||
| 196 | * 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen | ||
| 197 | * | ||
| 198 | * [WmiDataId(2), read] uint32 CurrentState; | ||
| 199 | * 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen | ||
| 200 | * }; | ||
| 201 | */ | ||
| 202 | static int get_current_status(struct wmi_device *wdev) | ||
| 203 | { | ||
| 204 | struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); | ||
| 205 | union acpi_object *obj_present; | ||
| 206 | u32 *buffer; | ||
| 207 | int ret = 0; | ||
| 208 | |||
| 209 | if (!priv) { | ||
| 210 | dev_err(&wdev->dev, "dell privacy priv is NULL\n"); | ||
| 211 | return -EINVAL; | ||
| 212 | } | ||
| 213 | /* check privacy support features and device states */ | ||
| 214 | obj_present = wmidev_block_query(wdev, 0); | ||
| 215 | if (!obj_present) { | ||
| 216 | dev_err(&wdev->dev, "failed to read Binary MOF\n"); | ||
| 217 | return -EIO; | ||
| 218 | } | ||
| 219 | |||
| 220 | if (obj_present->type != ACPI_TYPE_BUFFER) { | ||
| 221 | dev_err(&wdev->dev, "Binary MOF is not a buffer!\n"); | ||
| 222 | ret = -EIO; | ||
| 223 | goto obj_free; | ||
| 224 | } | ||
| 225 | /* Although it's not technically a failure, this would lead to | ||
| 226 | * unexpected behavior | ||
| 227 | */ | ||
| 228 | if (obj_present->buffer.length != 8) { | ||
| 229 | dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n", | ||
| 230 | obj_present->buffer.length); | ||
| 231 | ret = -EINVAL; | ||
| 232 | goto obj_free; | ||
| 233 | } | ||
| 234 | buffer = (u32 *)obj_present->buffer.pointer; | ||
| 235 | priv->features_present = buffer[0]; | ||
| 236 | priv->last_status = buffer[1]; | ||
| 237 | |||
| 238 | obj_free: | ||
| 239 | kfree(obj_present); | ||
| 240 | return ret; | ||
| 241 | } | ||
| 242 | |||
| 243 | static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev, | ||
| 244 | enum led_brightness brightness) | ||
| 245 | { | ||
| 246 | struct privacy_wmi_data *priv = led_to_priv(led_cdev); | ||
| 247 | static char *acpi_method = (char *)"ECAK"; | ||
| 248 | acpi_status status; | ||
| 249 | acpi_handle handle; | ||
| 250 | |||
| 251 | handle = ec_get_handle(); | ||
| 252 | if (!handle) | ||
| 253 | return -EIO; | ||
| 254 | |||
| 255 | if (!acpi_has_method(handle, acpi_method)) | ||
| 256 | return -EIO; | ||
| 257 | |||
| 258 | status = acpi_evaluate_object(handle, acpi_method, NULL, NULL); | ||
| 259 | if (ACPI_FAILURE(status)) { | ||
| 260 | dev_err(&priv->wdev->dev, "Error setting privacy EC ack value: %s\n", | ||
| 261 | acpi_format_exception(status)); | ||
| 262 | return -EIO; | ||
| 263 | } | ||
| 264 | |||
| 265 | return 0; | ||
| 266 | } | ||
| 267 | |||
| 268 | /* | ||
| 269 | * Pressing the mute key activates a time delayed circuit to physically cut | ||
| 270 | * off the mute. The LED is in the same circuit, so it reflects the true | ||
| 271 | * state of the HW mute. The reason for the EC "ack" is so that software | ||
| 272 | * can first invoke a SW mute before the HW circuit is cut off. Without SW | ||
| 273 | * cutting this off first does not affect the time delayed muting or status | ||
| 274 | * of the LED but there is a possibility of a "popping" noise. | ||
| 275 | * | ||
| 276 | * If the EC receives the SW ack, the circuit will be activated before the | ||
| 277 | * delay completed. | ||
| 278 | * | ||
| 279 | * Exposing as an LED device allows the codec drivers notification path to | ||
| 280 | * EC ACK to work | ||
| 281 | */ | ||
| 282 | static int dell_privacy_leds_setup(struct device *dev) | ||
| 283 | { | ||
| 284 | struct privacy_wmi_data *priv = dev_get_drvdata(dev); | ||
| 285 | |||
| 286 | priv->cdev.name = "dell-privacy::micmute"; | ||
| 287 | priv->cdev.max_brightness = 1; | ||
| 288 | priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set; | ||
| 289 | priv->cdev.default_trigger = "audio-micmute"; | ||
| 290 | priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); | ||
| 291 | return devm_led_classdev_register(dev, &priv->cdev); | ||
| 292 | } | ||
| 293 | |||
| 294 | static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context) | ||
| 295 | { | ||
| 296 | struct privacy_wmi_data *priv; | ||
| 297 | struct key_entry *keymap; | ||
| 298 | int ret, i; | ||
| 299 | |||
| 300 | ret = wmi_has_guid(DELL_PRIVACY_GUID); | ||
| 301 | if (!ret) | ||
| 302 | pr_debug("Unable to detect available Dell privacy devices!\n"); | ||
| 303 | |||
| 304 | priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); | ||
| 305 | if (!priv) | ||
| 306 | return -ENOMEM; | ||
| 307 | |||
| 308 | dev_set_drvdata(&wdev->dev, priv); | ||
| 309 | priv->wdev = wdev; | ||
| 310 | /* create evdev passing interface */ | ||
| 311 | priv->input_dev = devm_input_allocate_device(&wdev->dev); | ||
| 312 | if (!priv->input_dev) | ||
| 313 | return -ENOMEM; | ||
| 314 | |||
| 315 | /* remap the wmi keymap event to new keymap */ | ||
| 316 | keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012), | ||
| 317 | sizeof(struct key_entry), GFP_KERNEL); | ||
| 318 | if (!keymap) | ||
| 319 | return -ENOMEM; | ||
| 320 | |||
| 321 | /* remap the keymap code with Dell privacy key type 0x12 as prefix | ||
| 322 | * KEY_MICMUTE scancode will be reported as 0x120001 | ||
| 323 | */ | ||
| 324 | for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { | ||
| 325 | keymap[i] = dell_wmi_keymap_type_0012[i]; | ||
| 326 | keymap[i].code |= (0x0012 << 16); | ||
| 327 | } | ||
| 328 | ret = sparse_keymap_setup(priv->input_dev, keymap, NULL); | ||
| 329 | kfree(keymap); | ||
| 330 | if (ret) | ||
| 331 | return ret; | ||
| 332 | |||
| 333 | priv->input_dev->dev.parent = &wdev->dev; | ||
| 334 | priv->input_dev->name = "Dell Privacy Driver"; | ||
| 335 | priv->input_dev->id.bustype = BUS_HOST; | ||
| 336 | |||
| 337 | ret = input_register_device(priv->input_dev); | ||
| 338 | if (ret) | ||
| 339 | return ret; | ||
| 340 | |||
| 341 | ret = get_current_status(priv->wdev); | ||
| 342 | if (ret) | ||
| 343 | return ret; | ||
| 344 | |||
| 345 | ret = devm_device_add_group(&wdev->dev, &privacy_attribute_group); | ||
| 346 | if (ret) | ||
| 347 | return ret; | ||
| 348 | |||
| 349 | if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) { | ||
| 350 | ret = dell_privacy_leds_setup(&priv->wdev->dev); | ||
| 351 | if (ret) | ||
| 352 | return ret; | ||
| 353 | } | ||
| 354 | mutex_lock(&list_mutex); | ||
| 355 | list_add_tail(&priv->list, &wmi_list); | ||
| 356 | mutex_unlock(&list_mutex); | ||
| 357 | return 0; | ||
| 358 | } | ||
| 359 | |||
| 360 | static void dell_privacy_wmi_remove(struct wmi_device *wdev) | ||
| 361 | { | ||
| 362 | struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); | ||
| 363 | |||
| 364 | mutex_lock(&list_mutex); | ||
| 365 | list_del(&priv->list); | ||
| 366 | mutex_unlock(&list_mutex); | ||
| 367 | } | ||
| 368 | |||
| 369 | static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = { | ||
| 370 | { .guid_string = DELL_PRIVACY_GUID }, | ||
| 371 | { }, | ||
| 372 | }; | ||
| 373 | |||
| 374 | static struct wmi_driver dell_privacy_wmi_driver = { | ||
| 375 | .driver = { | ||
| 376 | .name = "dell-privacy", | ||
| 377 | }, | ||
| 378 | .probe = dell_privacy_wmi_probe, | ||
| 379 | .remove = dell_privacy_wmi_remove, | ||
| 380 | .id_table = dell_wmi_privacy_wmi_id_table, | ||
| 381 | }; | ||
| 382 | |||
| 383 | int dell_privacy_register_driver(void) | ||
| 384 | { | ||
| 385 | return wmi_driver_register(&dell_privacy_wmi_driver); | ||
| 386 | } | ||
| 387 | |||
| 388 | void dell_privacy_unregister_driver(void) | ||
| 389 | { | ||
| 390 | wmi_driver_unregister(&dell_privacy_wmi_driver); | ||
| 391 | } | ||
| @@ -0,0 +1,36 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-only */ | ||
| 2 | /* | ||
| 3 | * Dell privacy notification driver | ||
| 4 | * | ||
| 5 | * Copyright (C) 2021 Dell Inc. All Rights Reserved. | ||
| 6 | */ | ||
| 7 | |||
| 8 | #ifndef _DELL_PRIVACY_WMI_H_ | ||
| 9 | #define _DELL_PRIVACY_WMI_H_ | ||
| 10 | |||
| 11 | #if IS_ENABLED(CONFIG_DELL_WMI_PRIVACY) | ||
| 12 | bool dell_privacy_has_mic_mute(void); | ||
| 13 | bool dell_privacy_process_event(int type, int code, int status); | ||
| 14 | int dell_privacy_register_driver(void); | ||
| 15 | void dell_privacy_unregister_driver(void); | ||
| 16 | #else /* CONFIG_DELL_PRIVACY */ | ||
| 17 | static inline bool dell_privacy_has_mic_mute(void) | ||
| 18 | { | ||
| 19 | return false; | ||
| 20 | } | ||
| 21 | |||
| 22 | static inline bool dell_privacy_process_event(int type, int code, int status) | ||
| 23 | { | ||
| 24 | return false; | ||
| 25 | } | ||
| 26 | |||
| 27 | static inline int dell_privacy_register_driver(void) | ||
| 28 | { | ||
| 29 | return 0; | ||
| 30 | } | ||
| 31 | |||
| 32 | static inline void dell_privacy_unregister_driver(void) | ||
| 33 | { | ||
| 34 | } | ||
| 35 | #endif /* CONFIG_DELL_PRIVACY */ | ||
| 36 | #endif | ||
| @@ -152,12 +152,15 @@ static ssize_t curr_val##_store(struct kobject *kobj, \ · 151 unmodified lines | |||
| 152 | return ret ? ret : count; \ | 152 | return ret ? ret : count; \ |
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | #define check_property_type(attr, prop, valuetype) \ | ||
| 156 | (attr##_obj[prop].type != valuetype) | ||
| 157 | |||
| 155 | union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); | 158 | union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); |
| 156 | int get_instance_count(const char *guid_string); | 159 | int get_instance_count(const char *guid_string); |
| 157 | void strlcpy_attr(char *dest, char *src); | 160 | void strlcpy_attr(char *dest, char *src); |
| 158 | 161 | ||
| 159 | int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, | 162 | int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, |
| 160 | struct kobject *attr_name_kobj); | 163 | struct kobject *attr_name_kobj, u32 enum_property_count); |
| 161 | int alloc_enum_data(void); | 164 | int alloc_enum_data(void); |
| 162 | void exit_enum_attributes(void); | 165 | void exit_enum_attributes(void); |
| 163 | 166 | ||
| @@ -132,39 +132,68 @@ int alloc_enum_data(void) · 131 unmodified lines | |||
| 132 | * @enumeration_obj: ACPI object with enumeration data | 132 | * @enumeration_obj: ACPI object with enumeration data |
| 133 | * @instance_id: The instance to enumerate | 133 | * @instance_id: The instance to enumerate |
| 134 | * @attr_name_kobj: The parent kernel object | 134 | * @attr_name_kobj: The parent kernel object |
| 135 | * @enum_property_count: Total properties count under enumeration type | ||
| 135 | */ | 136 | */ |
| 136 | int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, | 137 | int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, |
| 137 | struct kobject *attr_name_kobj) | 138 | struct kobject *attr_name_kobj, u32 enum_property_count) |
| 138 | { | 139 | { |
| 139 | int i, next_obj, value_modifier_count, possible_values_count; | 140 | int i, next_obj, value_modifier_count, possible_values_count; |
| 140 | 141 | ||
| 141 | wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; | 142 | wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; |
| 143 | if (check_property_type(enumeration, ATTR_NAME, ACPI_TYPE_STRING)) | ||
| 144 | return -EINVAL; | ||
| 142 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, | 145 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, |
| 143 | enumeration_obj[ATTR_NAME].string.pointer); | 146 | enumeration_obj[ATTR_NAME].string.pointer); |
| 147 | if (check_property_type(enumeration, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | ||
| 148 | return -EINVAL; | ||
| 144 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, | 149 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, |
| 145 | enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); | 150 | enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 151 | if (check_property_type(enumeration, DISPLAY_NAME, ACPI_TYPE_STRING)) | ||
| 152 | return -EINVAL; | ||
| 146 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, | 153 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, |
| 147 | enumeration_obj[DISPLAY_NAME].string.pointer); | 154 | enumeration_obj[DISPLAY_NAME].string.pointer); |
| 155 | if (check_property_type(enumeration, DEFAULT_VAL, ACPI_TYPE_STRING)) | ||
| 156 | return -EINVAL; | ||
| 148 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, | 157 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, |
| 149 | enumeration_obj[DEFAULT_VAL].string.pointer); | 158 | enumeration_obj[DEFAULT_VAL].string.pointer); |
| 159 | if (check_property_type(enumeration, MODIFIER, ACPI_TYPE_STRING)) | ||
| 160 | return -EINVAL; | ||
| 150 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, | 161 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, |
| 151 | enumeration_obj[MODIFIER].string.pointer); | 162 | enumeration_obj[MODIFIER].string.pointer); |
| 152 | 163 | ||
| 153 | next_obj = MODIFIER + 1; | 164 | next_obj = MODIFIER + 1; |
| 154 | 165 | ||
| 155 | value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; | 166 | if (next_obj >= enum_property_count) |
| 167 | return -EINVAL; | ||
| 168 | |||
| 169 | if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER)) | ||
| 170 | return -EINVAL; | ||
| 171 | value_modifier_count = (uintptr_t)enumeration_obj[next_obj++].string.pointer; | ||
| 156 | 172 | ||
| 157 | for (i = 0; i < value_modifier_count; i++) { | 173 | for (i = 0; i < value_modifier_count; i++) { |
| 174 | if (next_obj >= enum_property_count) | ||
| 175 | return -EINVAL; | ||
| 176 | if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) | ||
| 177 | return -EINVAL; | ||
| 158 | strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, | 178 | strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, |
| 159 | enumeration_obj[++next_obj].string.pointer); | 179 | enumeration_obj[next_obj++].string.pointer); |
| 160 | strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); | 180 | strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); |
| 161 | } | 181 | } |
| 162 | 182 | ||
| 163 | possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; | 183 | if (next_obj >= enum_property_count) |
| 184 | return -EINVAL; | ||
| 185 | |||
| 186 | if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER)) | ||
| 187 | return -EINVAL; | ||
| 188 | possible_values_count = (uintptr_t) enumeration_obj[next_obj++].string.pointer; | ||
| 164 | 189 | ||
| 165 | for (i = 0; i < possible_values_count; i++) { | 190 | for (i = 0; i < possible_values_count; i++) { |
| 191 | if (next_obj >= enum_property_count) | ||
| 192 | return -EINVAL; | ||
| 193 | if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) | ||
| 194 | return -EINVAL; | ||
| 166 | strcat(wmi_priv.enumeration_data[instance_id].possible_values, | 195 | strcat(wmi_priv.enumeration_data[instance_id].possible_values, |
| 167 | enumeration_obj[++next_obj].string.pointer); | 196 | enumeration_obj[next_obj++].string.pointer); |
| 168 | strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); | 197 | strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); |
| 169 | } | 198 | } |
| 170 | 199 | ||
| @@ -141,20 +141,36 @@ int populate_int_data(union acpi_object *integer_obj, int instance_id, · 140 unmodified lines | |||
| 141 | struct kobject *attr_name_kobj) | 141 | struct kobject *attr_name_kobj) |
| 142 | { | 142 | { |
| 143 | wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; | 143 | wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; |
| 144 | if (check_property_type(integer, ATTR_NAME, ACPI_TYPE_STRING)) | ||
| 145 | return -EINVAL; | ||
| 144 | strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, | 146 | strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, |
| 145 | integer_obj[ATTR_NAME].string.pointer); | 147 | integer_obj[ATTR_NAME].string.pointer); |
| 148 | if (check_property_type(integer, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | ||
| 149 | return -EINVAL; | ||
| 146 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, | 150 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, |
| 147 | integer_obj[DISPL_NAME_LANG_CODE].string.pointer); | 151 | integer_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 152 | if (check_property_type(integer, DISPLAY_NAME, ACPI_TYPE_STRING)) | ||
| 153 | return -EINVAL; | ||
| 148 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, | 154 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, |
| 149 | integer_obj[DISPLAY_NAME].string.pointer); | 155 | integer_obj[DISPLAY_NAME].string.pointer); |
| 156 | if (check_property_type(integer, DEFAULT_VAL, ACPI_TYPE_INTEGER)) | ||
| 157 | return -EINVAL; | ||
| 150 | wmi_priv.integer_data[instance_id].default_value = | 158 | wmi_priv.integer_data[instance_id].default_value = |
| 151 | (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; | 159 | (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; |
| 160 | if (check_property_type(integer, MODIFIER, ACPI_TYPE_STRING)) | ||
| 161 | return -EINVAL; | ||
| 152 | strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, | 162 | strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, |
| 153 | integer_obj[MODIFIER].string.pointer); | 163 | integer_obj[MODIFIER].string.pointer); |
| 164 | if (check_property_type(integer, MIN_VALUE, ACPI_TYPE_INTEGER)) | ||
| 165 | return -EINVAL; | ||
| 154 | wmi_priv.integer_data[instance_id].min_value = | 166 | wmi_priv.integer_data[instance_id].min_value = |
| 155 | (uintptr_t)integer_obj[MIN_VALUE].string.pointer; | 167 | (uintptr_t)integer_obj[MIN_VALUE].string.pointer; |
| 168 | if (check_property_type(integer, MAX_VALUE, ACPI_TYPE_INTEGER)) | ||
| 169 | return -EINVAL; | ||
| 156 | wmi_priv.integer_data[instance_id].max_value = | 170 | wmi_priv.integer_data[instance_id].max_value = |
| 157 | (uintptr_t)integer_obj[MAX_VALUE].string.pointer; | 171 | (uintptr_t)integer_obj[MAX_VALUE].string.pointer; |
| 172 | if (check_property_type(integer, SCALAR_INCR, ACPI_TYPE_INTEGER)) | ||
| 173 | return -EINVAL; | ||
| 158 | wmi_priv.integer_data[instance_id].scalar_increment = | 174 | wmi_priv.integer_data[instance_id].scalar_increment = |
| 159 | (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; | 175 | (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; |
| 160 | 176 | ||
| @@ -159,10 +159,16 @@ int alloc_po_data(void) · 158 unmodified lines | |||
| 159 | int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) | 159 | int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) |
| 160 | { | 160 | { |
| 161 | wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; | 161 | wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; |
| 162 | if (check_property_type(po, ATTR_NAME, ACPI_TYPE_STRING)) | ||
| 163 | return -EINVAL; | ||
| 162 | strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, | 164 | strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, |
| 163 | po_obj[ATTR_NAME].string.pointer); | 165 | po_obj[ATTR_NAME].string.pointer); |
| 166 | if (check_property_type(po, MIN_PASS_LEN, ACPI_TYPE_INTEGER)) | ||
| 167 | return -EINVAL; | ||
| 164 | wmi_priv.po_data[instance_id].min_password_length = | 168 | wmi_priv.po_data[instance_id].min_password_length = |
| 165 | (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; | 169 | (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; |
| 170 | if (check_property_type(po, MAX_PASS_LEN, ACPI_TYPE_INTEGER)) | ||
| 171 | return -EINVAL; | ||
| 166 | wmi_priv.po_data[instance_id].max_password_length = | 172 | wmi_priv.po_data[instance_id].max_password_length = |
| 167 | (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; | 173 | (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; |
| 168 | 174 | ||
| @@ -95,9 +95,9 @@ int set_new_password(const char *password_type, const char *new) · 94 unmodified lines | |||
| 95 | 95 | ||
| 96 | print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); | 96 | print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); |
| 97 | ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); | 97 | ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); |
| 98 | /* clear current_password here and use user input from wmi_priv.current_password */ | 98 | /* on success copy the new password to current password */ |
| 99 | if (!ret) | 99 | if (!ret) |
| 100 | memset(current_password, 0, MAX_BUFF); | 100 | strscpy(current_password, new, MAX_BUFF); |
| 101 | /* explain to user the detailed failure reason */ | 101 | /* explain to user the detailed failure reason */ |
| 102 | else if (ret == -EOPNOTSUPP) | 102 | else if (ret == -EOPNOTSUPP) |
| 103 | dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); | 103 | dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); |
| @@ -118,24 +118,38 @@ int alloc_str_data(void) · 117 unmodified lines | |||
| 118 | 118 | ||
| 119 | /** | 119 | /** |
| 120 | * populate_str_data() - Populate all properties of an instance under string attribute | 120 | * populate_str_data() - Populate all properties of an instance under string attribute |
| 121 | * @str_obj: ACPI object with integer data | 121 | * @str_obj: ACPI object with string data |
| 122 | * @instance_id: The instance to enumerate | 122 | * @instance_id: The instance to enumerate |
| 123 | * @attr_name_kobj: The parent kernel object | 123 | * @attr_name_kobj: The parent kernel object |
| 124 | */ | 124 | */ |
| 125 | int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) | 125 | int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) |
| 126 | { | 126 | { |
| 127 | wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; | 127 | wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; |
| 128 | if (check_property_type(str, ATTR_NAME, ACPI_TYPE_STRING)) | ||
| 129 | return -EINVAL; | ||
| 128 | strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, | 130 | strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, |
| 129 | str_obj[ATTR_NAME].string.pointer); | 131 | str_obj[ATTR_NAME].string.pointer); |
| 132 | if (check_property_type(str, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | ||
| 133 | return -EINVAL; | ||
| 130 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, | 134 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, |
| 131 | str_obj[DISPL_NAME_LANG_CODE].string.pointer); | 135 | str_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 136 | if (check_property_type(str, DISPLAY_NAME, ACPI_TYPE_STRING)) | ||
| 137 | return -EINVAL; | ||
| 132 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name, | 138 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name, |
| 133 | str_obj[DISPLAY_NAME].string.pointer); | 139 | str_obj[DISPLAY_NAME].string.pointer); |
| 140 | if (check_property_type(str, DEFAULT_VAL, ACPI_TYPE_STRING)) | ||
| 141 | return -EINVAL; | ||
| 134 | strlcpy_attr(wmi_priv.str_data[instance_id].default_value, | 142 | strlcpy_attr(wmi_priv.str_data[instance_id].default_value, |
| 135 | str_obj[DEFAULT_VAL].string.pointer); | 143 | str_obj[DEFAULT_VAL].string.pointer); |
| 144 | if (check_property_type(str, MODIFIER, ACPI_TYPE_STRING)) | ||
| 145 | return -EINVAL; | ||
| 136 | strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, | 146 | strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, |
| 137 | str_obj[MODIFIER].string.pointer); | 147 | str_obj[MODIFIER].string.pointer); |
| 148 | if (check_property_type(str, MIN_LEN, ACPI_TYPE_INTEGER)) | ||
| 149 | return -EINVAL; | ||
| 138 | wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; | 150 | wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; |
| 151 | if (check_property_type(str, MAX_LEN, ACPI_TYPE_INTEGER)) | ||
| 152 | return -EINVAL; | ||
| 139 | wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; | 153 | wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; |
| 140 | 154 | ||
| 141 | return sysfs_create_group(attr_name_kobj, &str_attr_group); | 155 | return sysfs_create_group(attr_name_kobj, &str_attr_group); |
| @@ -13,14 +13,11 @@ · 12 unmodified lines | |||
| 13 | #include <linux/kernel.h> | 13 | #include <linux/kernel.h> |
| 14 | #include <linux/wmi.h> | 14 | #include <linux/wmi.h> |
| 15 | #include "dell-wmi-sysman.h" | 15 | #include "dell-wmi-sysman.h" |
| 16 | #include "../../firmware_attributes_class.h" | ||
| 16 | 17 | ||
| 17 | #define MAX_TYPES 4 | 18 | #define MAX_TYPES 4 |
| 18 | #include <linux/nls.h> | 19 | #include <linux/nls.h> |
| 19 | 20 | ||
| 20 | static struct class firmware_attributes_class = { | ||
| 21 | .name = "firmware-attributes", | ||
| 22 | }; | ||
| 23 | |||
| 24 | struct wmi_sysman_priv wmi_priv = { | 21 | struct wmi_sysman_priv wmi_priv = { |
| 25 | .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), | 22 | .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), |
| 26 | }; | 23 | }; |
| @@ -28,6 +25,7 @@ struct wmi_sysman_priv wmi_priv = { · 1 unmodified line | |||
| 28 | /* reset bios to defaults */ | 25 | /* reset bios to defaults */ |
| 29 | static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; | 26 | static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; |
| 30 | static int reset_option = -1; | 27 | static int reset_option = -1; |
| 28 | static struct class *fw_attr_class; | ||
| 31 | 29 | ||
| 32 | 30 | ||
| 33 | /** | 31 | /** |
| @@ -481,7 +479,8 @@ static int init_bios_attributes(int attr_type, const char *guid) · 447 unmodified lines | |||
| 481 | /* enumerate all of this attribute */ | 479 | /* enumerate all of this attribute */ |
| 482 | switch (attr_type) { | 480 | switch (attr_type) { |
| 483 | case ENUM: | 481 | case ENUM: |
| 484 | retval = populate_enum_data(elements, instance_id, attr_name_kobj); | 482 | retval = populate_enum_data(elements, instance_id, attr_name_kobj, |
| 483 | obj->package.count); | ||
| 485 | break; | 484 | break; |
| 486 | case INT: | 485 | case INT: |
| 487 | retval = populate_int_data(elements, instance_id, attr_name_kobj); | 486 | retval = populate_int_data(elements, instance_id, attr_name_kobj); |
| @@ -541,11 +540,11 @@ static int __init sysman_init(void) · 53 unmodified lines | |||
| 541 | goto err_exit_bios_attr_pass_interface; | 540 | goto err_exit_bios_attr_pass_interface; |
| 542 | } | 541 | } |
| 543 | 542 | ||
| 544 | ret = class_register(&firmware_attributes_class); | 543 | ret = fw_attributes_class_get(&fw_attr_class); |
| 545 | if (ret) | 544 | if (ret) |
| 546 | goto err_exit_bios_attr_pass_interface; | 545 | goto err_exit_bios_attr_pass_interface; |
| 547 | 546 | ||
| 548 | wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), | 547 | wmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), |
| 549 | NULL, "%s", DRIVER_NAME); | 548 | NULL, "%s", DRIVER_NAME); |
| 550 | if (IS_ERR(wmi_priv.class_dev)) { | 549 | if (IS_ERR(wmi_priv.class_dev)) { |
| 551 | ret = PTR_ERR(wmi_priv.class_dev); | 550 | ret = PTR_ERR(wmi_priv.class_dev); |
| @@ -602,10 +601,10 @@ err_release_attributes_data: · 50 unmodified lines | |||
| 602 | release_attributes_data(); | 601 | release_attributes_data(); |
| 603 | 602 | ||
| 604 | err_destroy_classdev: | 603 | err_destroy_classdev: |
| 605 | device_destroy(&firmware_attributes_class, MKDEV(0, 0)); | 604 | device_destroy(fw_attr_class, MKDEV(0, 0)); |
| 606 | 605 | ||
| 607 | err_unregister_class: | 606 | err_unregister_class: |
| 608 | class_unregister(&firmware_attributes_class); | 607 | fw_attributes_class_put(); |
| 609 | 608 | ||
| 610 | err_exit_bios_attr_pass_interface: | 609 | err_exit_bios_attr_pass_interface: |
| 611 | exit_bios_attr_pass_interface(); | 610 | exit_bios_attr_pass_interface(); |
| @@ -619,8 +618,8 @@ err_exit_bios_attr_set_interface: · 7 unmodified lines | |||
| 619 | static void __exit sysman_exit(void) | 618 | static void __exit sysman_exit(void) |
| 620 | { | 619 | { |
| 621 | release_attributes_data(); | 620 | release_attributes_data(); |
| 622 | device_destroy(&firmware_attributes_class, MKDEV(0, 0)); | 621 | device_destroy(fw_attr_class, MKDEV(0, 0)); |
| 623 | class_unregister(&firmware_attributes_class); | 622 | fw_attributes_class_put(); |
| 624 | exit_bios_attr_set_interface(); | 623 | exit_bios_attr_set_interface(); |
| 625 | exit_bios_attr_pass_interface(); | 624 | exit_bios_attr_pass_interface(); |
| 626 | } | 625 | } |
| @@ -0,0 +1,52 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | |||
| 3 | /* Firmware attributes class helper module */ | ||
| 4 | |||
| 5 | #include <linux/mutex.h> | ||
| 6 | #include <linux/device/class.h> | ||
| 7 | #include <linux/module.h> | ||
| 8 | #include "firmware_attributes_class.h" | ||
| 9 | |||
| 10 | static DEFINE_MUTEX(fw_attr_lock); | ||
| 11 | static int fw_attr_inuse; | ||
| 12 | |||
| 13 | static struct class firmware_attributes_class = { | ||
| 14 | .name = "firmware-attributes", | ||
| 15 | }; | ||
| 16 | |||
| 17 | int fw_attributes_class_get(struct class **fw_attr_class) | ||
| 18 | { | ||
| 19 | int err; | ||
| 20 | |||
| 21 | mutex_lock(&fw_attr_lock); | ||
| 22 | if (!fw_attr_inuse) { /*first time class is being used*/ | ||
| 23 | err = class_register(&firmware_attributes_class); | ||
| 24 | if (err) { | ||
| 25 | mutex_unlock(&fw_attr_lock); | ||
| 26 | return err; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | fw_attr_inuse++; | ||
| 30 | *fw_attr_class = &firmware_attributes_class; | ||
| 31 | mutex_unlock(&fw_attr_lock); | ||
| 32 | return 0; | ||
| 33 | } | ||
| 34 | EXPORT_SYMBOL_GPL(fw_attributes_class_get); | ||
| 35 | |||
| 36 | int fw_attributes_class_put(void) | ||
| 37 | { | ||
| 38 | mutex_lock(&fw_attr_lock); | ||
| 39 | if (!fw_attr_inuse) { | ||
| 40 | mutex_unlock(&fw_attr_lock); | ||
| 41 | return -EINVAL; | ||
| 42 | } | ||
| 43 | fw_attr_inuse--; | ||
| 44 | if (!fw_attr_inuse) /* No more consumers */ | ||
| 45 | class_unregister(&firmware_attributes_class); | ||
| 46 | mutex_unlock(&fw_attr_lock); | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | EXPORT_SYMBOL_GPL(fw_attributes_class_put); | ||
| 50 | |||
| 51 | MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); | ||
| 52 | MODULE_LICENSE("GPL"); | ||
| @@ -0,0 +1,11 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
| 2 | |||
| 3 | /* Firmware attributes class helper module */ | ||
| 4 | |||
| 5 | #ifndef FW_ATTR_CLASS_H | ||
| 6 | #define FW_ATTR_CLASS_H | ||
| 7 | |||
| 8 | int fw_attributes_class_get(struct class **fw_attr_class); | ||
| 9 | int fw_attributes_class_put(void); | ||
| 10 | |||
| 11 | #endif /* FW_ATTR_CLASS_H */ | ||
| @@ -462,7 +462,7 @@ static struct attribute *hdaps_attributes[] = { · 461 unmodified lines | |||
| 462 | NULL, | 462 | NULL, |
| 463 | }; | 463 | }; |
| 464 | 464 | ||
| 465 | static struct attribute_group hdaps_attribute_group = { | 465 | static const struct attribute_group hdaps_attribute_group = { |
| 466 | .attrs = hdaps_attributes, | 466 | .attrs = hdaps_attributes, |
| 467 | }; | 467 | }; |
| 468 | 468 | ||
| @@ -1,102 +0,0 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | /* | ||
| 3 | * Airplane mode button for HP & Xiaomi laptops | ||
| 4 | * | ||
| 5 | * Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com> | ||
| 6 | */ | ||
| 7 | |||
| 8 | #include <linux/kernel.h> | ||
| 9 | #include <linux/module.h> | ||
| 10 | #include <linux/init.h> | ||
| 11 | #include <linux/input.h> | ||
| 12 | #include <linux/platform_device.h> | ||
| 13 | #include <linux/acpi.h> | ||
| 14 | #include <acpi/acpi_bus.h> | ||
| 15 | |||
| 16 | MODULE_LICENSE("GPL"); | ||
| 17 | MODULE_AUTHOR("Alex Hung"); | ||
| 18 | MODULE_ALIAS("acpi*:HPQ6001:*"); | ||
| 19 | MODULE_ALIAS("acpi*:WSTADEF:*"); | ||
| 20 | MODULE_ALIAS("acpi*:AMDI0051:*"); | ||
| 21 | |||
| 22 | static struct input_dev *hpwl_input_dev; | ||
| 23 | |||
| 24 | static const struct acpi_device_id hpwl_ids[] = { | ||
| 25 | {"HPQ6001", 0}, | ||
| 26 | {"WSTADEF", 0}, | ||
| 27 | {"AMDI0051", 0}, | ||
| 28 | {"", 0}, | ||
| 29 | }; | ||
| 30 | |||
| 31 | static int hp_wireless_input_setup(void) | ||
| 32 | { | ||
| 33 | int err; | ||
| 34 | |||
| 35 | hpwl_input_dev = input_allocate_device(); | ||
| 36 | if (!hpwl_input_dev) | ||
| 37 | return -ENOMEM; | ||
| 38 | |||
| 39 | hpwl_input_dev->name = "HP Wireless hotkeys"; | ||
| 40 | hpwl_input_dev->phys = "hpq6001/input0"; | ||
| 41 | hpwl_input_dev->id.bustype = BUS_HOST; | ||
| 42 | hpwl_input_dev->evbit[0] = BIT(EV_KEY); | ||
| 43 | set_bit(KEY_RFKILL, hpwl_input_dev->keybit); | ||
| 44 | |||
| 45 | err = input_register_device(hpwl_input_dev); | ||
| 46 | if (err) | ||
| 47 | goto err_free_dev; | ||
| 48 | |||
| 49 | return 0; | ||
| 50 | |||
| 51 | err_free_dev: | ||
| 52 | input_free_device(hpwl_input_dev); | ||
| 53 | return err; | ||
| 54 | } | ||
| 55 | |||
| 56 | static void hp_wireless_input_destroy(void) | ||
| 57 | { | ||
| 58 | input_unregister_device(hpwl_input_dev); | ||
| 59 | } | ||
| 60 | |||
| 61 | static void hpwl_notify(struct acpi_device *acpi_dev, u32 event) | ||
| 62 | { | ||
| 63 | if (event != 0x80) { | ||
| 64 | pr_info("Received unknown event (0x%x)\n", event); | ||
| 65 | return; | ||
| 66 | } | ||
| 67 | |||
| 68 | input_report_key(hpwl_input_dev, KEY_RFKILL, 1); | ||
| 69 | input_sync(hpwl_input_dev); | ||
| 70 | input_report_key(hpwl_input_dev, KEY_RFKILL, 0); | ||
| 71 | input_sync(hpwl_input_dev); | ||
| 72 | } | ||
| 73 | |||
| 74 | static int hpwl_add(struct acpi_device *device) | ||
| 75 | { | ||
| 76 | int err; | ||
| 77 | |||
| 78 | err = hp_wireless_input_setup(); | ||
| 79 | if (err) | ||
| 80 | pr_err("Failed to setup hp wireless hotkeys\n"); | ||
| 81 | |||
| 82 | return err; | ||
| 83 | } | ||
| 84 | |||
| 85 | static int hpwl_remove(struct acpi_device *device) | ||
| 86 | { | ||
| 87 | hp_wireless_input_destroy(); | ||
| 88 | return 0; | ||
| 89 | } | ||
| 90 | |||
| 91 | static struct acpi_driver hpwl_driver = { | ||
| 92 | .name = "hp-wireless", | ||
| 93 | .owner = THIS_MODULE, | ||
| 94 | .ids = hpwl_ids, | ||
| 95 | .ops = { | ||
| 96 | .add = hpwl_add, | ||
| 97 | .remove = hpwl_remove, | ||
| 98 | .notify = hpwl_notify, | ||
| 99 | }, | ||
| 100 | }; | ||
| 101 | |||
| 102 | module_acpi_driver(hpwl_driver); | ||
| @@ -1408,6 +1408,18 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) · 1407 unmodified lines | |||
| 1408 | case 6: | 1408 | case 6: |
| 1409 | ideapad_input_report(priv, bit); | 1409 | ideapad_input_report(priv, bit); |
| 1410 | break; | 1410 | break; |
| 1411 | case 10: | ||
| 1412 | /* | ||
| 1413 | * This event gets send on a Yoga 300-11IBR when the EC | ||
| 1414 | * believes that the device has changed between laptop/ | ||
| 1415 | * tent/stand/tablet mode. The EC relies on getting | ||
| 1416 | * angle info from 2 accelerometers through a special | ||
| 1417 | * windows service calling a DSM on the DUAL250E ACPI- | ||
| 1418 | * device. Linux does not do this, making the laptop/ | ||
| 1419 | * tent/stand/tablet mode info unreliable, so we simply | ||
| 1420 | * ignore these events. | ||
| 1421 | */ | ||
| 1422 | break; | ||
| 1411 | case 9: | 1423 | case 9: |
| 1412 | ideapad_sync_rfk_state(priv); | 1424 | ideapad_sync_rfk_state(priv); |
| 1413 | break; | 1425 | break; |
| @@ -0,0 +1,22 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | # | ||
| 3 | # Intel x86 Platform Specific Drivers | ||
| 4 | # | ||
| 5 | |||
| 6 | menuconfig X86_PLATFORM_DRIVERS_INTEL | ||
| 7 | bool "Intel x86 Platform Specific Device Drivers" | ||
| 8 | default y | ||
| 9 | help | ||
| 10 | Say Y here to get to see options for device drivers for | ||
| 11 | various Intel x86 platforms, including vendor-specific | ||
| 12 | drivers. This option alone does not add any kernel code. | ||
| 13 | |||
| 14 | If you say N, all options in this submenu will be skipped | ||
| 15 | and disabled. | ||
| 16 | |||
| 17 | if X86_PLATFORM_DRIVERS_INTEL | ||
| 18 | |||
| 19 | source "drivers/platform/x86/intel/int33fe/Kconfig" | ||
| 20 | source "drivers/platform/x86/intel/int3472/Kconfig" | ||
| 21 | |||
| 22 | endif # X86_PLATFORM_DRIVERS_INTEL | ||
| @@ -0,0 +1,8 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0 | ||
| 2 | # | ||
| 3 | # Makefile for drivers/platform/x86/intel | ||
| 4 | # Intel x86 Platform-Specific Drivers | ||
| 5 | # | ||
| 6 | |||
| 7 | obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ | ||
| 8 | obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | config INTEL_CHT_INT33FE | ||
| 3 | tristate "Intel Cherry Trail ACPI INT33FE Driver" | ||
| 4 | depends on X86 && ACPI && I2C && REGULATOR | ||
| 5 | depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) | ||
| 6 | depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) | ||
| 7 | depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) | ||
| 8 | help | ||
| 9 | This driver add support for the INT33FE ACPI device found on | ||
| 10 | some Intel Cherry Trail devices. | ||
| 11 | |||
| 12 | There are two kinds of INT33FE ACPI device possible: for hardware | ||
| 13 | with USB Type-C and Micro-B connectors. This driver supports both. | ||
| 14 | |||
| 15 | The INT33FE ACPI device has a CRS table with I2cSerialBusV2 | ||
| 16 | resources for Fuel Gauge Controller and (in the Type-C variant) | ||
| 17 | FUSB302 USB Type-C Controller and PI3USB30532 USB switch. | ||
| 18 | This driver instantiates i2c-clients for these, so that standard | ||
| 19 | i2c drivers for these chips can bind to the them. | ||
| 20 | |||
| 21 | If you enable this driver it is advised to also select | ||
| 22 | CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B | ||
| 23 | device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m | ||
| 24 | for Type-C device. | ||
| @@ -0,0 +1,5 @@ | |||
| 1 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 2 | obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o | ||
| 3 | intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ | ||
| 4 | intel_cht_int33fe_typec.o \ | ||
| 5 | intel_cht_int33fe_microb.o | ||
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -168,8 +168,8 @@ static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) · 167 unmodified lines | |||
| 168 | return -ENODEV; | 168 | return -ENODEV; |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | /* Then the DP child device node */ | 171 | /* Then the DP-2 child device node */ |
| 172 | data->dp = device_get_named_child_node(&pdev->dev, "DD02"); | 172 | data->dp = device_get_named_child_node(&pdev->dev, "DD04"); |
| 173 | pci_dev_put(pdev); | 173 | pci_dev_put(pdev); |
| 174 | if (!data->dp) | 174 | if (!data->dp) |
| 175 | return -ENODEV; | 175 | return -ENODEV; |
| @@ -0,0 +1,30 @@ | |||
| 1 | config INTEL_SKL_INT3472 | ||
| 2 | tristate "Intel SkyLake ACPI INT3472 Driver" | ||
| 3 | depends on ACPI | ||
| 4 | depends on COMMON_CLK | ||
| 5 | depends on I2C | ||
| 6 | depends on GPIOLIB | ||
| 7 | depends on REGULATOR | ||
| 8 | select MFD_CORE | ||
| 9 | select REGMAP_I2C | ||
| 10 | help | ||
| 11 | This driver adds power controller support for the Intel SkyCam | ||
| 12 | devices found on the Intel SkyLake platforms. | ||
| 13 | |||
| 14 | The INT3472 is a camera power controller, a logical device found on | ||
| 15 | Intel Skylake-based systems that can map to different hardware | ||
| 16 | devices depending on the platform. On machines designed for Chrome OS | ||
| 17 | it maps to a TPS68470 camera PMIC. On machines designed for Windows, | ||
| 18 | it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a | ||
| 19 | set of discrete GPIOs and power gates. | ||
| 20 | |||
| 21 | If your device was designed for Chrome OS, this driver will provide | ||
| 22 | an ACPI OpRegion, which must be available before any of the devices | ||
| 23 | using it are probed. For this reason, you should select Y if your | ||
| 24 | device was designed for ChromeOS. For the same reason the | ||
| 25 | I2C_DESIGNWARE_PLATFORM option must be set to Y too. | ||
| 26 | |||
| 27 | Say Y or M here if you have a SkyLake device designed for use | ||
| 28 | with Windows or ChromeOS. Say N here if you are not sure. | ||
| 29 | |||
| 30 | The module will be named "intel-skl-int3472". | ||
| @@ -0,0 +1,5 @@ | |||
| 1 | obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o | ||
| 2 | intel_skl_int3472-objs := intel_skl_int3472_common.o \ | ||
| 3 | intel_skl_int3472_discrete.o \ | ||
| 4 | intel_skl_int3472_tps68470.o \ | ||
| 5 | intel_skl_int3472_clk_and_regulator.o | ||
| @@ -0,0 +1,207 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* Author: Dan Scally <djrscally@gmail.com> */ | ||
| 3 | |||
| 4 | #include <linux/acpi.h> | ||
| 5 | #include <linux/clkdev.h> | ||
| 6 | #include <linux/clk-provider.h> | ||
| 7 | #include <linux/device.h> | ||
| 8 | #include <linux/gpio/consumer.h> | ||
| 9 | #include <linux/regulator/driver.h> | ||
| 10 | #include <linux/slab.h> | ||
| 11 | |||
| 12 | #include "intel_skl_int3472_common.h" | ||
| 13 | |||
| 14 | /* | ||
| 15 | * The regulators have to have .ops to be valid, but the only ops we actually | ||
| 16 | * support are .enable and .disable which are handled via .ena_gpiod. Pass an | ||
| 17 | * empty struct to clear the check without lying about capabilities. | ||
| 18 | */ | ||
| 19 | static const struct regulator_ops int3472_gpio_regulator_ops; | ||
| 20 | |||
| 21 | static int skl_int3472_clk_prepare(struct clk_hw *hw) | ||
| 22 | { | ||
| 23 | struct int3472_gpio_clock *clk = to_int3472_clk(hw); | ||
| 24 | |||
| 25 | gpiod_set_value_cansleep(clk->ena_gpio, 1); | ||
| 26 | gpiod_set_value_cansleep(clk->led_gpio, 1); | ||
| 27 | |||
| 28 | return 0; | ||
| 29 | } | ||
| 30 | |||
| 31 | static void skl_int3472_clk_unprepare(struct clk_hw *hw) | ||
| 32 | { | ||
| 33 | struct int3472_gpio_clock *clk = to_int3472_clk(hw); | ||
| 34 | |||
| 35 | gpiod_set_value_cansleep(clk->ena_gpio, 0); | ||
| 36 | gpiod_set_value_cansleep(clk->led_gpio, 0); | ||
| 37 | } | ||
| 38 | |||
| 39 | static int skl_int3472_clk_enable(struct clk_hw *hw) | ||
| 40 | { | ||
| 41 | /* | ||
| 42 | * We're just turning a GPIO on to enable the clock, which operation | ||
| 43 | * has the potential to sleep. Given .enable() cannot sleep, but | ||
| 44 | * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, | ||
| 45 | * nothing to do here. | ||
| 46 | */ | ||
| 47 | return 0; | ||
| 48 | } | ||
| 49 | |||
| 50 | static void skl_int3472_clk_disable(struct clk_hw *hw) | ||
| 51 | { | ||
| 52 | /* Likewise, nothing to do here... */ | ||
| 53 | } | ||
| 54 | |||
| 55 | static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) | ||
| 56 | { | ||
| 57 | union acpi_object *obj; | ||
| 58 | unsigned int freq; | ||
| 59 | |||
| 60 | obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); | ||
| 61 | if (IS_ERR(obj)) | ||
| 62 | return 0; /* report rate as 0 on error */ | ||
| 63 | |||
| 64 | if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { | ||
| 65 | dev_err(int3472->dev, "The buffer is too small\n"); | ||
| 66 | kfree(obj); | ||
| 67 | return 0; | ||
| 68 | } | ||
| 69 | |||
| 70 | freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); | ||
| 71 | |||
| 72 | kfree(obj); | ||
| 73 | return freq; | ||
| 74 | } | ||
| 75 | |||
| 76 | static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, | ||
| 77 | unsigned long parent_rate) | ||
| 78 | { | ||
| 79 | struct int3472_gpio_clock *clk = to_int3472_clk(hw); | ||
| 80 | |||
| 81 | return clk->frequency; | ||
| 82 | } | ||
| 83 | |||
| 84 | static const struct clk_ops skl_int3472_clock_ops = { | ||
| 85 | .prepare = skl_int3472_clk_prepare, | ||
| 86 | .unprepare = skl_int3472_clk_unprepare, | ||
| 87 | .enable = skl_int3472_clk_enable, | ||
| 88 | .disable = skl_int3472_clk_disable, | ||
| 89 | .recalc_rate = skl_int3472_clk_recalc_rate, | ||
| 90 | }; | ||
| 91 | |||
| 92 | int skl_int3472_register_clock(struct int3472_discrete_device *int3472) | ||
| 93 | { | ||
| 94 | struct clk_init_data init = { | ||
| 95 | .ops = &skl_int3472_clock_ops, | ||
| 96 | .flags = CLK_GET_RATE_NOCACHE, | ||
| 97 | }; | ||
| 98 | int ret; | ||
| 99 | |||
| 100 | init.name = kasprintf(GFP_KERNEL, "%s-clk", | ||
| 101 | acpi_dev_name(int3472->adev)); | ||
| 102 | if (!init.name) | ||
| 103 | return -ENOMEM; | ||
| 104 | |||
| 105 | int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); | ||
| 106 | |||
| 107 | int3472->clock.clk_hw.init = &init; | ||
| 108 | int3472->clock.clk = clk_register(&int3472->adev->dev, | ||
| 109 | &int3472->clock.clk_hw); | ||
| 110 | if (IS_ERR(int3472->clock.clk)) { | ||
| 111 | ret = PTR_ERR(int3472->clock.clk); | ||
| 112 | goto out_free_init_name; | ||
| 113 | } | ||
| 114 | |||
| 115 | int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, | ||
| 116 | int3472->sensor_name); | ||
| 117 | if (!int3472->clock.cl) { | ||
| 118 | ret = -ENOMEM; | ||
| 119 | goto err_unregister_clk; | ||
| 120 | } | ||
| 121 | |||
| 122 | kfree(init.name); | ||
| 123 | return 0; | ||
| 124 | |||
| 125 | err_unregister_clk: | ||
| 126 | clk_unregister(int3472->clock.clk); | ||
| 127 | out_free_init_name: | ||
| 128 | kfree(init.name); | ||
| 129 | |||
| 130 | return ret; | ||
| 131 | } | ||
| 132 | |||
| 133 | void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) | ||
| 134 | { | ||
| 135 | clkdev_drop(int3472->clock.cl); | ||
| 136 | clk_unregister(int3472->clock.clk); | ||
| 137 | } | ||
| 138 | |||
| 139 | int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, | ||
| 140 | struct acpi_resource_gpio *agpio) | ||
| 141 | { | ||
| 142 | const struct int3472_sensor_config *sensor_config; | ||
| 143 | char *path = agpio->resource_source.string_ptr; | ||
| 144 | struct regulator_consumer_supply supply_map; | ||
| 145 | struct regulator_init_data init_data = { }; | ||
| 146 | struct regulator_config cfg = { }; | ||
| 147 | int ret; | ||
| 148 | |||
| 149 | sensor_config = int3472->sensor_config; | ||
| 150 | if (IS_ERR(sensor_config)) { | ||
| 151 | dev_err(int3472->dev, "No sensor module config\n"); | ||
| 152 | return PTR_ERR(sensor_config); | ||
| 153 | } | ||
| 154 | |||
| 155 | if (!sensor_config->supply_map.supply) { | ||
| 156 | dev_err(int3472->dev, "No supply name defined\n"); | ||
| 157 | return -ENODEV; | ||
| 158 | } | ||
| 159 | |||
| 160 | init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; | ||
| 161 | init_data.num_consumer_supplies = 1; | ||
| 162 | supply_map = sensor_config->supply_map; | ||
| 163 | supply_map.dev_name = int3472->sensor_name; | ||
| 164 | init_data.consumer_supplies = &supply_map; | ||
| 165 | |||
| 166 | snprintf(int3472->regulator.regulator_name, | ||
| 167 | sizeof(int3472->regulator.regulator_name), "%s-regulator", | ||
| 168 | acpi_dev_name(int3472->adev)); | ||
| 169 | snprintf(int3472->regulator.supply_name, | ||
| 170 | GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); | ||
| 171 | |||
| 172 | int3472->regulator.rdesc = INT3472_REGULATOR( | ||
| 173 | int3472->regulator.regulator_name, | ||
| 174 | int3472->regulator.supply_name, | ||
| 175 | &int3472_gpio_regulator_ops); | ||
| 176 | |||
| 177 | int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], | ||
| 178 | "int3472,regulator"); | ||
| 179 | if (IS_ERR(int3472->regulator.gpio)) { | ||
| 180 | dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); | ||
| 181 | return PTR_ERR(int3472->regulator.gpio); | ||
| 182 | } | ||
| 183 | |||
| 184 | cfg.dev = &int3472->adev->dev; | ||
| 185 | cfg.init_data = &init_data; | ||
| 186 | cfg.ena_gpiod = int3472->regulator.gpio; | ||
| 187 | |||
| 188 | int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, | ||
| 189 | &cfg); | ||
| 190 | if (IS_ERR(int3472->regulator.rdev)) { | ||
| 191 | ret = PTR_ERR(int3472->regulator.rdev); | ||
| 192 | goto err_free_gpio; | ||
| 193 | } | ||
| 194 | |||
| 195 | return 0; | ||
| 196 | |||
| 197 | err_free_gpio: | ||
| 198 | gpiod_put(int3472->regulator.gpio); | ||
| 199 | |||
| 200 | return ret; | ||
| 201 | } | ||
| 202 | |||
| 203 | void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) | ||
| 204 | { | ||
| 205 | regulator_unregister(int3472->regulator.rdev); | ||
| 206 | gpiod_put(int3472->regulator.gpio); | ||
| 207 | } | ||
| @@ -0,0 +1,106 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* Author: Dan Scally <djrscally@gmail.com> */ | ||
| 3 | |||
| 4 | #include <linux/acpi.h> | ||
| 5 | #include <linux/i2c.h> | ||
| 6 | #include <linux/platform_device.h> | ||
| 7 | #include <linux/slab.h> | ||
| 8 | |||
| 9 | #include "intel_skl_int3472_common.h" | ||
| 10 | |||
| 11 | union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) | ||
| 12 | { | ||
| 13 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 14 | acpi_handle handle = adev->handle; | ||
| 15 | union acpi_object *obj; | ||
| 16 | acpi_status status; | ||
| 17 | |||
| 18 | status = acpi_evaluate_object(handle, id, NULL, &buffer); | ||
| 19 | if (ACPI_FAILURE(status)) | ||
| 20 | return ERR_PTR(-ENODEV); | ||
| 21 | |||
| 22 | obj = buffer.pointer; | ||
| 23 | if (!obj) | ||
| 24 | return ERR_PTR(-ENODEV); | ||
| 25 | |||
| 26 | if (obj->type != ACPI_TYPE_BUFFER) { | ||
| 27 | acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); | ||
| 28 | kfree(obj); | ||
| 29 | return ERR_PTR(-EINVAL); | ||
| 30 | } | ||
| 31 | |||
| 32 | return obj; | ||
| 33 | } | ||
| 34 | |||
| 35 | int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) | ||
| 36 | { | ||
| 37 | union acpi_object *obj; | ||
| 38 | int ret; | ||
| 39 | |||
| 40 | obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); | ||
| 41 | if (IS_ERR(obj)) | ||
| 42 | return PTR_ERR(obj); | ||
| 43 | |||
| 44 | if (obj->buffer.length > sizeof(*cldb)) { | ||
| 45 | acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); | ||
| 46 | ret = -EINVAL; | ||
| 47 | goto out_free_obj; | ||
| 48 | } | ||
| 49 | |||
| 50 | memcpy(cldb, obj->buffer.pointer, obj->buffer.length); | ||
| 51 | ret = 0; | ||
| 52 | |||
| 53 | out_free_obj: | ||
| 54 | kfree(obj); | ||
| 55 | return ret; | ||
| 56 | } | ||
| 57 | |||
| 58 | static const struct acpi_device_id int3472_device_id[] = { | ||
| 59 | { "INT3472", 0 }, | ||
| 60 | { } | ||
| 61 | }; | ||
| 62 | MODULE_DEVICE_TABLE(acpi, int3472_device_id); | ||
| 63 | |||
| 64 | static struct platform_driver int3472_discrete = { | ||
| 65 | .driver = { | ||
| 66 | .name = "int3472-discrete", | ||
| 67 | .acpi_match_table = int3472_device_id, | ||
| 68 | }, | ||
| 69 | .probe = skl_int3472_discrete_probe, | ||
| 70 | .remove = skl_int3472_discrete_remove, | ||
| 71 | }; | ||
| 72 | |||
| 73 | static struct i2c_driver int3472_tps68470 = { | ||
| 74 | .driver = { | ||
| 75 | .name = "int3472-tps68470", | ||
| 76 | .acpi_match_table = int3472_device_id, | ||
| 77 | }, | ||
| 78 | .probe_new = skl_int3472_tps68470_probe, | ||
| 79 | }; | ||
| 80 | |||
| 81 | static int skl_int3472_init(void) | ||
| 82 | { | ||
| 83 | int ret; | ||
| 84 | |||
| 85 | ret = platform_driver_register(&int3472_discrete); | ||
| 86 | if (ret) | ||
| 87 | return ret; | ||
| 88 | |||
| 89 | ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); | ||
| 90 | if (ret) | ||
| 91 | platform_driver_unregister(&int3472_discrete); | ||
| 92 | |||
| 93 | return ret; | ||
| 94 | } | ||
| 95 | module_init(skl_int3472_init); | ||
| 96 | |||
| 97 | static void skl_int3472_exit(void) | ||
| 98 | { | ||
| 99 | platform_driver_unregister(&int3472_discrete); | ||
| 100 | i2c_del_driver(&int3472_tps68470); | ||
| 101 | } | ||
| 102 | module_exit(skl_int3472_exit); | ||
| 103 | |||
| 104 | MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); | ||
| 105 | MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>"); | ||
| 106 | MODULE_LICENSE("GPL v2"); | ||
| @@ -0,0 +1,122 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 */ | ||
| 2 | /* Author: Dan Scally <djrscally@gmail.com> */ | ||
| 3 | |||
| 4 | #ifndef _INTEL_SKL_INT3472_H | ||
| 5 | #define _INTEL_SKL_INT3472_H | ||
| 6 | |||
| 7 | #include <linux/clk-provider.h> | ||
| 8 | #include <linux/gpio/machine.h> | ||
| 9 | #include <linux/regulator/driver.h> | ||
| 10 | #include <linux/regulator/machine.h> | ||
| 11 | #include <linux/types.h> | ||
| 12 | |||
| 13 | /* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ | ||
| 14 | #ifndef I2C_DEV_NAME_FORMAT | ||
| 15 | #define I2C_DEV_NAME_FORMAT "i2c-%s" | ||
| 16 | #endif | ||
| 17 | |||
| 18 | /* PMIC GPIO Types */ | ||
| 19 | #define INT3472_GPIO_TYPE_RESET 0x00 | ||
| 20 | #define INT3472_GPIO_TYPE_POWERDOWN 0x01 | ||
| 21 | #define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b | ||
| 22 | #define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c | ||
| 23 | #define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d | ||
| 24 | |||
| 25 | #define INT3472_PDEV_MAX_NAME_LEN 23 | ||
| 26 | #define INT3472_MAX_SENSOR_GPIOS 3 | ||
| 27 | |||
| 28 | #define GPIO_REGULATOR_NAME_LENGTH 21 | ||
| 29 | #define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 | ||
| 30 | |||
| 31 | #define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 | ||
| 32 | |||
| 33 | #define INT3472_REGULATOR(_name, _supply, _ops) \ | ||
| 34 | (const struct regulator_desc) { \ | ||
| 35 | .name = _name, \ | ||
| 36 | .supply_name = _supply, \ | ||
| 37 | .type = REGULATOR_VOLTAGE, \ | ||
| 38 | .ops = _ops, \ | ||
| 39 | .owner = THIS_MODULE, \ | ||
| 40 | } | ||
| 41 | |||
| 42 | #define to_int3472_clk(hw) \ | ||
| 43 | container_of(hw, struct int3472_gpio_clock, clk_hw) | ||
| 44 | |||
| 45 | #define to_int3472_device(clk) \ | ||
| 46 | container_of(clk, struct int3472_discrete_device, clock) | ||
| 47 | |||
| 48 | struct acpi_device; | ||
| 49 | struct i2c_client; | ||
| 50 | struct platform_device; | ||
| 51 | |||
| 52 | struct int3472_cldb { | ||
| 53 | u8 version; | ||
| 54 | /* | ||
| 55 | * control logic type | ||
| 56 | * 0: UNKNOWN | ||
| 57 | * 1: DISCRETE(CRD-D) | ||
| 58 | * 2: PMIC TPS68470 | ||
| 59 | * 3: PMIC uP6641 | ||
| 60 | */ | ||
| 61 | u8 control_logic_type; | ||
| 62 | u8 control_logic_id; | ||
| 63 | u8 sensor_card_sku; | ||
| 64 | u8 reserved[28]; | ||
| 65 | }; | ||
| 66 | |||
| 67 | struct int3472_gpio_function_remap { | ||
| 68 | const char *documented; | ||
| 69 | const char *actual; | ||
| 70 | }; | ||
| 71 | |||
| 72 | struct int3472_sensor_config { | ||
| 73 | const char *sensor_module_name; | ||
| 74 | struct regulator_consumer_supply supply_map; | ||
| 75 | const struct int3472_gpio_function_remap *function_maps; | ||
| 76 | }; | ||
| 77 | |||
| 78 | struct int3472_discrete_device { | ||
| 79 | struct acpi_device *adev; | ||
| 80 | struct device *dev; | ||
| 81 | struct acpi_device *sensor; | ||
| 82 | const char *sensor_name; | ||
| 83 | |||
| 84 | const struct int3472_sensor_config *sensor_config; | ||
| 85 | |||
| 86 | struct int3472_gpio_regulator { | ||
| 87 | char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; | ||
| 88 | char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; | ||
| 89 | struct gpio_desc *gpio; | ||
| 90 | struct regulator_dev *rdev; | ||
| 91 | struct regulator_desc rdesc; | ||
| 92 | } regulator; | ||
| 93 | |||
| 94 | struct int3472_gpio_clock { | ||
| 95 | struct clk *clk; | ||
| 96 | struct clk_hw clk_hw; | ||
| 97 | struct clk_lookup *cl; | ||
| 98 | struct gpio_desc *ena_gpio; | ||
| 99 | struct gpio_desc *led_gpio; | ||
| 100 | u32 frequency; | ||
| 101 | } clock; | ||
| 102 | |||
| 103 | unsigned int ngpios; /* how many GPIOs have we seen */ | ||
| 104 | unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ | ||
| 105 | struct gpiod_lookup_table gpios; | ||
| 106 | }; | ||
| 107 | |||
| 108 | int skl_int3472_discrete_probe(struct platform_device *pdev); | ||
| 109 | int skl_int3472_discrete_remove(struct platform_device *pdev); | ||
| 110 | int skl_int3472_tps68470_probe(struct i2c_client *client); | ||
| 111 | union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, | ||
| 112 | char *id); | ||
| 113 | int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); | ||
| 114 | |||
| 115 | int skl_int3472_register_clock(struct int3472_discrete_device *int3472); | ||
| 116 | void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); | ||
| 117 | |||
| 118 | int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, | ||
| 119 | struct acpi_resource_gpio *agpio); | ||
| 120 | void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); | ||
| 121 | |||
| 122 | #endif | ||
| @@ -0,0 +1,413 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* Author: Dan Scally <djrscally@gmail.com> */ | ||
| 3 | |||
| 4 | #include <linux/acpi.h> | ||
| 5 | #include <linux/clkdev.h> | ||
| 6 | #include <linux/clk-provider.h> | ||
| 7 | #include <linux/device.h> | ||
| 8 | #include <linux/gpio/consumer.h> | ||
| 9 | #include <linux/gpio/machine.h> | ||
| 10 | #include <linux/i2c.h> | ||
| 11 | #include <linux/kernel.h> | ||
| 12 | #include <linux/module.h> | ||
| 13 | #include <linux/overflow.h> | ||
| 14 | #include <linux/platform_device.h> | ||
| 15 | #include <linux/uuid.h> | ||
| 16 | |||
| 17 | #include "intel_skl_int3472_common.h" | ||
| 18 | |||
| 19 | /* | ||
| 20 | * 79234640-9e10-4fea-a5c1-b5aa8b19756f | ||
| 21 | * This _DSM GUID returns information about the GPIO lines mapped to a | ||
| 22 | * discrete INT3472 device. Function number 1 returns a count of the GPIO | ||
| 23 | * lines that are mapped. Subsequent functions return 32 bit ints encoding | ||
| 24 | * information about the GPIO line, including its purpose. | ||
| 25 | */ | ||
| 26 | static const guid_t int3472_gpio_guid = | ||
| 27 | GUID_INIT(0x79234640, 0x9e10, 0x4fea, | ||
| 28 | 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); | ||
| 29 | |||
| 30 | /* | ||
| 31 | * 822ace8f-2814-4174-a56b-5f029fe079ee | ||
| 32 | * This _DSM GUID returns a string from the sensor device, which acts as a | ||
| 33 | * module identifier. | ||
| 34 | */ | ||
| 35 | static const guid_t cio2_sensor_module_guid = | ||
| 36 | GUID_INIT(0x822ace8f, 0x2814, 0x4174, | ||
| 37 | 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); | ||
| 38 | |||
| 39 | /* | ||
| 40 | * Here follows platform specific mapping information that we can pass to | ||
| 41 | * the functions mapping resources to the sensors. Where the sensors have | ||
| 42 | * a power enable pin defined in DSDT we need to provide a supply name so | ||
| 43 | * the sensor drivers can find the regulator. The device name will be derived | ||
| 44 | * from the sensor's ACPI device within the code. Optionally, we can provide a | ||
| 45 | * NULL terminated array of function name mappings to deal with any platform | ||
| 46 | * specific deviations from the documented behaviour of GPIOs. | ||
| 47 | * | ||
| 48 | * Map a GPIO function name to NULL to prevent the driver from mapping that | ||
| 49 | * GPIO at all. | ||
| 50 | */ | ||
| 51 | |||
| 52 | static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { | ||
| 53 | { "reset", NULL }, | ||
| 54 | { "powerdown", "reset" }, | ||
| 55 | { } | ||
| 56 | }; | ||
| 57 | |||
| 58 | static const struct int3472_sensor_config int3472_sensor_configs[] = { | ||
| 59 | /* Lenovo Miix 510-12ISK - OV2680, Front */ | ||
| 60 | { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, | ||
| 61 | /* Lenovo Miix 510-12ISK - OV5648, Rear */ | ||
| 62 | { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, | ||
| 63 | /* Surface Go 1&2 - OV5693, Front */ | ||
| 64 | { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, | ||
| 65 | }; | ||
| 66 | |||
| 67 | static const struct int3472_sensor_config * | ||
| 68 | skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) | ||
| 69 | { | ||
| 70 | union acpi_object *obj; | ||
| 71 | unsigned int i; | ||
| 72 | |||
| 73 | obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, | ||
| 74 | &cio2_sensor_module_guid, 0x00, | ||
| 75 | 0x01, NULL, ACPI_TYPE_STRING); | ||
| 76 | |||
| 77 | if (!obj) { | ||
| 78 | dev_err(int3472->dev, | ||
| 79 | "Failed to get sensor module string from _DSM\n"); | ||
| 80 | return ERR_PTR(-ENODEV); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (obj->string.type != ACPI_TYPE_STRING) { | ||
| 84 | dev_err(int3472->dev, | ||
| 85 | "Sensor _DSM returned a non-string value\n"); | ||
| 86 | |||
| 87 | ACPI_FREE(obj); | ||
| 88 | return ERR_PTR(-EINVAL); | ||
| 89 | } | ||
| 90 | |||
| 91 | for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { | ||
| 92 | if (!strcmp(int3472_sensor_configs[i].sensor_module_name, | ||
| 93 | obj->string.pointer)) | ||
| 94 | break; | ||
| 95 | } | ||
| 96 | |||
| 97 | ACPI_FREE(obj); | ||
| 98 | |||
| 99 | if (i >= ARRAY_SIZE(int3472_sensor_configs)) | ||
| 100 | return ERR_PTR(-EINVAL); | ||
| 101 | |||
| 102 | return &int3472_sensor_configs[i]; | ||
| 103 | } | ||
| 104 | |||
| 105 | static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, | ||
| 106 | struct acpi_resource_gpio *agpio, | ||
| 107 | const char *func, u32 polarity) | ||
| 108 | { | ||
| 109 | const struct int3472_sensor_config *sensor_config; | ||
| 110 | char *path = agpio->resource_source.string_ptr; | ||
| 111 | struct gpiod_lookup *table_entry; | ||
| 112 | struct acpi_device *adev; | ||
| 113 | acpi_handle handle; | ||
| 114 | acpi_status status; | ||
| 115 | int ret; | ||
| 116 | |||
| 117 | if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { | ||
| 118 | dev_warn(int3472->dev, "Too many GPIOs mapped\n"); | ||
| 119 | return -EINVAL; | ||
| 120 | } | ||
| 121 | |||
| 122 | sensor_config = int3472->sensor_config; | ||
| 123 | if (!IS_ERR(sensor_config) && sensor_config->function_maps) { | ||
| 124 | const struct int3472_gpio_function_remap *remap; | ||
| 125 | |||
| 126 | for (remap = sensor_config->function_maps; remap->documented; remap++) { | ||
| 127 | if (!strcmp(func, remap->documented)) { | ||
| 128 | func = remap->actual; | ||
| 129 | break; | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /* Functions mapped to NULL should not be mapped to the sensor */ | ||
| 135 | if (!func) | ||
| 136 | return 0; | ||
| 137 | |||
| 138 | status = acpi_get_handle(NULL, path, &handle); | ||
| 139 | if (ACPI_FAILURE(status)) | ||
| 140 | return -EINVAL; | ||
| 141 | |||
| 142 | ret = acpi_bus_get_device(handle, &adev); | ||
| 143 | if (ret) | ||
| 144 | return -ENODEV; | ||
| 145 | |||
| 146 | table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; | ||
| 147 | table_entry->key = acpi_dev_name(adev); | ||
| 148 | table_entry->chip_hwnum = agpio->pin_table[0]; | ||
| 149 | table_entry->con_id = func; | ||
| 150 | table_entry->idx = 0; | ||
| 151 | table_entry->flags = polarity; | ||
| 152 | |||
| 153 | int3472->n_sensor_gpios++; | ||
| 154 | |||
| 155 | return 0; | ||
| 156 | } | ||
| 157 | |||
| 158 | static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, | ||
| 159 | struct acpi_resource_gpio *agpio, u8 type) | ||
| 160 | { | ||
| 161 | char *path = agpio->resource_source.string_ptr; | ||
| 162 | u16 pin = agpio->pin_table[0]; | ||
| 163 | struct gpio_desc *gpio; | ||
| 164 | |||
| 165 | switch (type) { | ||
| 166 | case INT3472_GPIO_TYPE_CLK_ENABLE: | ||
| 167 | gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); | ||
| 168 | if (IS_ERR(gpio)) | ||
| 169 | return (PTR_ERR(gpio)); | ||
| 170 | |||
| 171 | int3472->clock.ena_gpio = gpio; | ||
| 172 | break; | ||
| 173 | case INT3472_GPIO_TYPE_PRIVACY_LED: | ||
| 174 | gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); | ||
| 175 | if (IS_ERR(gpio)) | ||
| 176 | return (PTR_ERR(gpio)); | ||
| 177 | |||
| 178 | int3472->clock.led_gpio = gpio; | ||
| 179 | break; | ||
| 180 | default: | ||
| 181 | dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | |||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | /** | ||
| 189 | * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor | ||
| 190 | * @ares: A pointer to a &struct acpi_resource | ||
| 191 | * @data: A pointer to a &struct int3472_discrete_device | ||
| 192 | * | ||
| 193 | * This function handles GPIO resources that are against an INT3472 | ||
| 194 | * ACPI device, by checking the value of the corresponding _DSM entry. | ||
| 195 | * This will return a 32bit int, where the lowest byte represents the | ||
| 196 | * function of the GPIO pin: | ||
| 197 | * | ||
| 198 | * 0x00 Reset | ||
| 199 | * 0x01 Power down | ||
| 200 | * 0x0b Power enable | ||
| 201 | * 0x0c Clock enable | ||
| 202 | * 0x0d Privacy LED | ||
| 203 | * | ||
| 204 | * There are some known platform specific quirks where that does not quite | ||
| 205 | * hold up; for example where a pin with type 0x01 (Power down) is mapped to | ||
| 206 | * a sensor pin that performs a reset function or entries in _CRS and _DSM that | ||
| 207 | * do not actually correspond to a physical connection. These will be handled | ||
| 208 | * by the mapping sub-functions. | ||
| 209 | * | ||
| 210 | * GPIOs will either be mapped directly to the sensor device or else used | ||
| 211 | * to create clocks and regulators via the usual frameworks. | ||
| 212 | * | ||
| 213 | * Return: | ||
| 214 | * * 1 - To continue the loop | ||
| 215 | * * 0 - When all resources found are handled properly. | ||
| 216 | * * -EINVAL - If the resource is not a GPIO IO resource | ||
| 217 | * * -ENODEV - If the resource has no corresponding _DSM entry | ||
| 218 | * * -Other - Errors propagated from one of the sub-functions. | ||
| 219 | */ | ||
| 220 | static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, | ||
| 221 | void *data) | ||
| 222 | { | ||
| 223 | struct int3472_discrete_device *int3472 = data; | ||
| 224 | struct acpi_resource_gpio *agpio; | ||
| 225 | union acpi_object *obj; | ||
| 226 | const char *err_msg; | ||
| 227 | int ret; | ||
| 228 | u8 type; | ||
| 229 | |||
| 230 | if (!acpi_gpio_get_io_resource(ares, &agpio)) | ||
| 231 | return 1; | ||
| 232 | |||
| 233 | /* | ||
| 234 | * ngpios + 2 because the index of this _DSM function is 1-based and | ||
| 235 | * the first function is just a count. | ||
| 236 | */ | ||
| 237 | obj = acpi_evaluate_dsm_typed(int3472->adev->handle, | ||
| 238 | &int3472_gpio_guid, 0x00, | ||
| 239 | int3472->ngpios + 2, | ||
| 240 | NULL, ACPI_TYPE_INTEGER); | ||
| 241 | |||
| 242 | if (!obj) { | ||
| 243 | dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", | ||
| 244 | agpio->pin_table[0]); | ||
| 245 | return 1; | ||
| 246 | } | ||
| 247 | |||
| 248 | type = obj->integer.value & 0xff; | ||
| 249 | |||
| 250 | switch (type) { | ||
| 251 | case INT3472_GPIO_TYPE_RESET: | ||
| 252 | ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", | ||
| 253 | GPIO_ACTIVE_LOW); | ||
| 254 | if (ret) | ||
| 255 | err_msg = "Failed to map reset pin to sensor\n"; | ||
| 256 | |||
| 257 | break; | ||
| 258 | case INT3472_GPIO_TYPE_POWERDOWN: | ||
| 259 | ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", | ||
| 260 | GPIO_ACTIVE_LOW); | ||
| 261 | if (ret) | ||
| 262 | err_msg = "Failed to map powerdown pin to sensor\n"; | ||
| 263 | |||
| 264 | break; | ||
| 265 | case INT3472_GPIO_TYPE_CLK_ENABLE: | ||
| 266 | case INT3472_GPIO_TYPE_PRIVACY_LED: | ||
| 267 | ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); | ||
| 268 | if (ret) | ||
| 269 | err_msg = "Failed to map GPIO to clock\n"; | ||
| 270 | |||
| 271 | break; | ||
| 272 | case INT3472_GPIO_TYPE_POWER_ENABLE: | ||
| 273 | ret = skl_int3472_register_regulator(int3472, agpio); | ||
| 274 | if (ret) | ||
| 275 | err_msg = "Failed to map regulator to sensor\n"; | ||
| 276 | |||
| 277 | break; | ||
| 278 | default: | ||
| 279 | dev_warn(int3472->dev, | ||
| 280 | "GPIO type 0x%02x unknown; the sensor may not work\n", | ||
| 281 | type); | ||
| 282 | ret = 1; | ||
| 283 | break; | ||
| 284 | } | ||
| 285 | |||
| 286 | int3472->ngpios++; | ||
| 287 | ACPI_FREE(obj); | ||
| 288 | |||
| 289 | if (ret < 0) | ||
| 290 | return dev_err_probe(int3472->dev, ret, err_msg); | ||
| 291 | |||
| 292 | return ret; | ||
| 293 | } | ||
| 294 | |||
| 295 | static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) | ||
| 296 | { | ||
| 297 | LIST_HEAD(resource_list); | ||
| 298 | int ret; | ||
| 299 | |||
| 300 | /* | ||
| 301 | * No error check, because not having a sensor config is not necessarily | ||
| 302 | * a failure mode. | ||
| 303 | */ | ||
| 304 | int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); | ||
| 305 | |||
| 306 | ret = acpi_dev_get_resources(int3472->adev, &resource_list, | ||
| 307 | skl_int3472_handle_gpio_resources, | ||
| 308 | int3472); | ||
| 309 | if (ret < 0) | ||
| 310 | return ret; | ||
| 311 | |||
| 312 | acpi_dev_free_resource_list(&resource_list); | ||
| 313 | |||
| 314 | /* | ||
| 315 | * If we find no clock enable GPIO pin then the privacy LED won't work. | ||
| 316 | * We've never seen that situation, but it's possible. Warn the user so | ||
| 317 | * it's clear what's happened. | ||
| 318 | */ | ||
| 319 | if (int3472->clock.ena_gpio) { | ||
| 320 | ret = skl_int3472_register_clock(int3472); | ||
| 321 | if (ret) | ||
| 322 | return ret; | ||
| 323 | } else { | ||
| 324 | if (int3472->clock.led_gpio) | ||
| 325 | dev_warn(int3472->dev, | ||
| 326 | "No clk GPIO. The privacy LED won't work\n"); | ||
| 327 | } | ||
| 328 | |||
| 329 | int3472->gpios.dev_id = int3472->sensor_name; | ||
| 330 | gpiod_add_lookup_table(&int3472->gpios); | ||
| 331 | |||
| 332 | return 0; | ||
| 333 | } | ||
| 334 | |||
| 335 | int skl_int3472_discrete_probe(struct platform_device *pdev) | ||
| 336 | { | ||
| 337 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); | ||
| 338 | struct int3472_discrete_device *int3472; | ||
| 339 | struct int3472_cldb cldb; | ||
| 340 | int ret; | ||
| 341 | |||
| 342 | ret = skl_int3472_fill_cldb(adev, &cldb); | ||
| 343 | if (ret) { | ||
| 344 | dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); | ||
| 345 | return ret; | ||
| 346 | } | ||
| 347 | |||
| 348 | if (cldb.control_logic_type != 1) { | ||
| 349 | dev_err(&pdev->dev, "Unsupported control logic type %u\n", | ||
| 350 | cldb.control_logic_type); | ||
| 351 | return -EINVAL; | ||
| 352 | } | ||
| 353 | |||
| 354 | /* Max num GPIOs we've seen plus a terminator */ | ||
| 355 | int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, | ||
| 356 | INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); | ||
| 357 | if (!int3472) | ||
| 358 | return -ENOMEM; | ||
| 359 | |||
| 360 | int3472->adev = adev; | ||
| 361 | int3472->dev = &pdev->dev; | ||
| 362 | platform_set_drvdata(pdev, int3472); | ||
| 363 | |||
| 364 | int3472->sensor = acpi_dev_get_first_consumer_dev(adev); | ||
| 365 | if (!int3472->sensor) { | ||
| 366 | dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); | ||
| 367 | return -ENODEV; | ||
| 368 | } | ||
| 369 | |||
| 370 | int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, | ||
| 371 | I2C_DEV_NAME_FORMAT, | ||
| 372 | acpi_dev_name(int3472->sensor)); | ||
| 373 | if (!int3472->sensor_name) { | ||
| 374 | ret = -ENOMEM; | ||
| 375 | goto err_put_sensor; | ||
| 376 | } | ||
| 377 | |||
| 378 | /* | ||
| 379 | * Initialising this list means we can call gpiod_remove_lookup_table() | ||
| 380 | * in failure paths without issue. | ||
| 381 | */ | ||
| 382 | INIT_LIST_HEAD(&int3472->gpios.list); | ||
| 383 | |||
| 384 | ret = skl_int3472_parse_crs(int3472); | ||
| 385 | if (ret) { | ||
| 386 | skl_int3472_discrete_remove(pdev); | ||
| 387 | return ret; | ||
| 388 | } | ||
| 389 | |||
| 390 | return 0; | ||
| 391 | |||
| 392 | err_put_sensor: | ||
| 393 | acpi_dev_put(int3472->sensor); | ||
| 394 | |||
| 395 | return ret; | ||
| 396 | } | ||
| 397 | |||
| 398 | int skl_int3472_discrete_remove(struct platform_device *pdev) | ||
| 399 | { | ||
| 400 | struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); | ||
| 401 | |||
| 402 | gpiod_remove_lookup_table(&int3472->gpios); | ||
| 403 | |||
| 404 | if (int3472->clock.ena_gpio) | ||
| 405 | skl_int3472_unregister_clock(int3472); | ||
| 406 | |||
| 407 | gpiod_put(int3472->clock.ena_gpio); | ||
| 408 | gpiod_put(int3472->clock.led_gpio); | ||
| 409 | |||
| 410 | skl_int3472_unregister_regulator(int3472); | ||
| 411 | |||
| 412 | return 0; | ||
| 413 | } | ||
| @@ -0,0 +1,137 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* Author: Dan Scally <djrscally@gmail.com> */ | ||
| 3 | |||
| 4 | #include <linux/i2c.h> | ||
| 5 | #include <linux/mfd/core.h> | ||
| 6 | #include <linux/mfd/tps68470.h> | ||
| 7 | #include <linux/platform_device.h> | ||
| 8 | #include <linux/regmap.h> | ||
| 9 | |||
| 10 | #include "intel_skl_int3472_common.h" | ||
| 11 | |||
| 12 | #define DESIGNED_FOR_CHROMEOS 1 | ||
| 13 | #define DESIGNED_FOR_WINDOWS 2 | ||
| 14 | |||
| 15 | static const struct mfd_cell tps68470_cros[] = { | ||
| 16 | { .name = "tps68470-gpio" }, | ||
| 17 | { .name = "tps68470_pmic_opregion" }, | ||
| 18 | }; | ||
| 19 | |||
| 20 | static const struct mfd_cell tps68470_win[] = { | ||
| 21 | { .name = "tps68470-gpio" }, | ||
| 22 | { .name = "tps68470-clk" }, | ||
| 23 | { .name = "tps68470-regulator" }, | ||
| 24 | }; | ||
| 25 | |||
| 26 | static const struct regmap_config tps68470_regmap_config = { | ||
| 27 | .reg_bits = 8, | ||
| 28 | .val_bits = 8, | ||
| 29 | .max_register = TPS68470_REG_MAX, | ||
| 30 | }; | ||
| 31 | |||
| 32 | static int tps68470_chip_init(struct device *dev, struct regmap *regmap) | ||
| 33 | { | ||
| 34 | unsigned int version; | ||
| 35 | int ret; | ||
| 36 | |||
| 37 | /* Force software reset */ | ||
| 38 | ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); | ||
| 39 | if (ret) | ||
| 40 | return ret; | ||
| 41 | |||
| 42 | ret = regmap_read(regmap, TPS68470_REG_REVID, &version); | ||
| 43 | if (ret) { | ||
| 44 | dev_err(dev, "Failed to read revision register: %d\n", ret); | ||
| 45 | return ret; | ||
| 46 | } | ||
| 47 | |||
| 48 | dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); | ||
| 49 | |||
| 50 | return 0; | ||
| 51 | } | ||
| 52 | |||
| 53 | /** skl_int3472_tps68470_calc_type: Check what platform a device is designed for | ||
| 54 | * @adev: A pointer to a &struct acpi_device | ||
| 55 | * | ||
| 56 | * Check CLDB buffer against the PMIC's adev. If present, then we check | ||
| 57 | * the value of control_logic_type field and follow one of the | ||
| 58 | * following scenarios: | ||
| 59 | * | ||
| 60 | * 1. No CLDB - likely ACPI tables designed for ChromeOS. We | ||
| 61 | * create platform devices for the GPIOs and OpRegion drivers. | ||
| 62 | * | ||
| 63 | * 2. CLDB, with control_logic_type = 2 - probably ACPI tables | ||
| 64 | * made for Windows 2-in-1 platforms. Register pdevs for GPIO, | ||
| 65 | * Clock and Regulator drivers to bind to. | ||
| 66 | * | ||
| 67 | * 3. Any other value in control_logic_type, we should never have | ||
| 68 | * gotten to this point; fail probe and return. | ||
| 69 | * | ||
| 70 | * Return: | ||
| 71 | * * 1 Device intended for ChromeOS | ||
| 72 | * * 2 Device intended for Windows | ||
| 73 | * * -EINVAL Where @adev has an object named CLDB but it does not conform to | ||
| 74 | * our expectations | ||
| 75 | */ | ||
| 76 | static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) | ||
| 77 | { | ||
| 78 | struct int3472_cldb cldb = { 0 }; | ||
| 79 | int ret; | ||
| 80 | |||
| 81 | /* | ||
| 82 | * A CLDB buffer that exists, but which does not match our expectations | ||
| 83 | * should trigger an error so we don't blindly continue. | ||
| 84 | */ | ||
| 85 | ret = skl_int3472_fill_cldb(adev, &cldb); | ||
| 86 | if (ret && ret != -ENODEV) | ||
| 87 | return ret; | ||
| 88 | |||
| 89 | if (ret) | ||
| 90 | return DESIGNED_FOR_CHROMEOS; | ||
| 91 | |||
| 92 | if (cldb.control_logic_type != 2) | ||
| 93 | return -EINVAL; | ||
| 94 | |||
| 95 | return DESIGNED_FOR_WINDOWS; | ||
| 96 | } | ||
| 97 | |||
| 98 | int skl_int3472_tps68470_probe(struct i2c_client *client) | ||
| 99 | { | ||
| 100 | struct acpi_device *adev = ACPI_COMPANION(&client->dev); | ||
| 101 | struct regmap *regmap; | ||
| 102 | int device_type; | ||
| 103 | int ret; | ||
| 104 | |||
| 105 | regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); | ||
| 106 | if (IS_ERR(regmap)) { | ||
| 107 | dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); | ||
| 108 | return PTR_ERR(regmap); | ||
| 109 | } | ||
| 110 | |||
| 111 | i2c_set_clientdata(client, regmap); | ||
| 112 | |||
| 113 | ret = tps68470_chip_init(&client->dev, regmap); | ||
| 114 | if (ret < 0) { | ||
| 115 | dev_err(&client->dev, "TPS68470 init error %d\n", ret); | ||
| 116 | return ret; | ||
| 117 | } | ||
| 118 | |||
| 119 | device_type = skl_int3472_tps68470_calc_type(adev); | ||
| 120 | switch (device_type) { | ||
| 121 | case DESIGNED_FOR_WINDOWS: | ||
| 122 | ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, | ||
| 123 | tps68470_win, ARRAY_SIZE(tps68470_win), | ||
| 124 | NULL, 0, NULL); | ||
| 125 | break; | ||
| 126 | case DESIGNED_FOR_CHROMEOS: | ||
| 127 | ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, | ||
| 128 | tps68470_cros, ARRAY_SIZE(tps68470_cros), | ||
| 129 | NULL, 0, NULL); | ||
| 130 | break; | ||
| 131 | default: | ||
| 132 | dev_err(&client->dev, "Failed to add MFD devices\n"); | ||
| 133 | return device_type; | ||
| 134 | } | ||
| 135 | |||
| 136 | return ret; | ||
| 137 | } | ||
| @@ -829,7 +829,7 @@ static u16 calc_avg_temp(struct ips_driver *ips, u16 *array) · 828 unmodified lines | |||
| 829 | 829 | ||
| 830 | static u16 read_mgtv(struct ips_driver *ips) | 830 | static u16 read_mgtv(struct ips_driver *ips) |
| 831 | { | 831 | { |
| 832 | u16 ret; | 832 | u16 __maybe_unused ret; |
| 833 | u64 slope, offset; | 833 | u64 slope, offset; |
| 834 | u64 val; | 834 | u64 val; |
| 835 | 835 | ||
| @@ -218,7 +218,7 @@ static struct attribute *pmt_crashlog_attrs[] = { · 217 unmodified lines | |||
| 218 | NULL | 218 | NULL |
| 219 | }; | 219 | }; |
| 220 | 220 | ||
| 221 | static struct attribute_group pmt_crashlog_group = { | 221 | static const struct attribute_group pmt_crashlog_group = { |
| 222 | .attrs = pmt_crashlog_attrs, | 222 | .attrs = pmt_crashlog_attrs, |
| 223 | }; | 223 | }; |
| 224 | 224 | ||
| @@ -281,10 +281,69 @@ static int isst_if_get_platform_info(void __user *argp) · 280 unmodified lines | |||
| 281 | struct isst_if_cpu_info { | 281 | struct isst_if_cpu_info { |
| 282 | /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ | 282 | /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ |
| 283 | int bus_info[2]; | 283 | int bus_info[2]; |
| 284 | struct pci_dev *pci_dev[2]; | ||
| 284 | int punit_cpu_id; | 285 | int punit_cpu_id; |
| 286 | int numa_node; | ||
| 285 | }; | 287 | }; |
| 286 | 288 | ||
| 287 | static struct isst_if_cpu_info *isst_cpu_info; | 289 | static struct isst_if_cpu_info *isst_cpu_info; |
| 290 | #define ISST_MAX_PCI_DOMAINS 8 | ||
| 291 | |||
| 292 | static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) | ||
| 293 | { | ||
| 294 | struct pci_dev *matched_pci_dev = NULL; | ||
| 295 | struct pci_dev *pci_dev = NULL; | ||
| 296 | int no_matches = 0; | ||
| 297 | int i, bus_number; | ||
| 298 | |||
| 299 | if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || | ||
| 300 | cpu >= num_possible_cpus()) | ||
| 301 | return NULL; | ||
| 302 | |||
| 303 | bus_number = isst_cpu_info[cpu].bus_info[bus_no]; | ||
| 304 | if (bus_number < 0) | ||
| 305 | return NULL; | ||
| 306 | |||
| 307 | for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) { | ||
| 308 | struct pci_dev *_pci_dev; | ||
| 309 | int node; | ||
| 310 | |||
| 311 | _pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn)); | ||
| 312 | if (!_pci_dev) | ||
| 313 | continue; | ||
| 314 | |||
| 315 | ++no_matches; | ||
| 316 | if (!matched_pci_dev) | ||
| 317 | matched_pci_dev = _pci_dev; | ||
| 318 | |||
| 319 | node = dev_to_node(&_pci_dev->dev); | ||
| 320 | if (node == NUMA_NO_NODE) { | ||
| 321 | pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n", | ||
| 322 | cpu, bus_no, dev, fn); | ||
| 323 | continue; | ||
| 324 | } | ||
| 325 | |||
| 326 | if (node == isst_cpu_info[cpu].numa_node) { | ||
| 327 | pci_dev = _pci_dev; | ||
| 328 | break; | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | /* | ||
| 333 | * If there is no numa matched pci_dev, then there can be following cases: | ||
| 334 | * 1. CONFIG_NUMA is not defined: In this case if there is only single device | ||
| 335 | * match, then we don't need numa information. Simply return last match. | ||
| 336 | * Othewise return NULL. | ||
| 337 | * 2. NUMA information is not exposed via _SEG method. In this case it is similar | ||
| 338 | * to case 1. | ||
| 339 | * 3. Numa information doesn't match with CPU numa node and more than one match | ||
| 340 | * return NULL. | ||
| 341 | */ | ||
| 342 | if (!pci_dev && no_matches == 1) | ||
| 343 | pci_dev = matched_pci_dev; | ||
| 344 | |||
| 345 | return pci_dev; | ||
| 346 | } | ||
| 288 | 347 | ||
| 289 | /** | 348 | /** |
| 290 | * isst_if_get_pci_dev() - Get the PCI device instance for a CPU | 349 | * 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; · 9 unmodified lines | |||
| 300 | */ | 359 | */ |
| 301 | struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) | 360 | struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) |
| 302 | { | 361 | { |
| 303 | int bus_number; | 362 | struct pci_dev *pci_dev; |
| 304 | 363 | ||
| 305 | if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || | 364 | if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || |
| 306 | cpu >= num_possible_cpus()) | 365 | cpu >= num_possible_cpus()) |
| 307 | return NULL; | 366 | return NULL; |
| 308 | 367 | ||
| 309 | bus_number = isst_cpu_info[cpu].bus_info[bus_no]; | 368 | pci_dev = isst_cpu_info[cpu].pci_dev[bus_no]; |
| 310 | if (bus_number < 0) | 369 | |
| 311 | return NULL; | 370 | if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn)) |
| 371 | return pci_dev; | ||
| 312 | 372 | ||
| 313 | return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn)); | 373 | return _isst_if_get_pci_dev(cpu, bus_no, dev, fn); |
| 314 | } | 374 | } |
| 315 | EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); | 375 | EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); |
| 316 | 376 | ||
| @@ -327,6 +387,8 @@ static int isst_if_cpu_online(unsigned int cpu) · 10 unmodified lines | |||
| 327 | } else { | 387 | } else { |
| 328 | isst_cpu_info[cpu].bus_info[0] = data & 0xff; | 388 | isst_cpu_info[cpu].bus_info[0] = data & 0xff; |
| 329 | isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; | 389 | isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; |
| 390 | isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1); | ||
| 391 | isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1); | ||
| 330 | } | 392 | } |
| 331 | 393 | ||
| 332 | ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); | 394 | ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); |
| @@ -335,6 +397,7 @@ static int isst_if_cpu_online(unsigned int cpu) · 2 unmodified lines | |||
| 335 | return ret; | 397 | return ret; |
| 336 | } | 398 | } |
| 337 | isst_cpu_info[cpu].punit_cpu_id = data; | 399 | isst_cpu_info[cpu].punit_cpu_id = data; |
| 400 | isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); | ||
| 338 | 401 | ||
| 339 | isst_restore_msr_local(cpu); | 402 | isst_restore_msr_local(cpu); |
| 340 | 403 | ||
| @@ -388,7 +388,7 @@ MODULE_PARM_DESC(force, · 387 unmodified lines | |||
| 388 | "Disable the DMI check and forces the driver to be loaded"); | 388 | "Disable the DMI check and forces the driver to be loaded"); |
| 389 | 389 | ||
| 390 | static bool debug; | 390 | static bool debug; |
| 391 | module_param(debug, bool, S_IRUGO | S_IWUSR); | 391 | module_param(debug, bool, 0644); |
| 392 | MODULE_PARM_DESC(debug, "Debug enabled or not"); | 392 | MODULE_PARM_DESC(debug, "Debug enabled or not"); |
| 393 | 393 | ||
| 394 | static int sabi_command(struct samsung_laptop *samsung, u16 command, | 394 | static int sabi_command(struct samsung_laptop *samsung, u16 command, |
| @@ -705,7 +705,7 @@ static ssize_t set_performance_level(struct device *dev, · 310 unmodified lines | |||
| 705 | return count; | 705 | return count; |
| 706 | } | 706 | } |
| 707 | 707 | ||
| 708 | static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, | 708 | static DEVICE_ATTR(performance_level, 0644, |
| 709 | get_performance_level, set_performance_level); | 709 | get_performance_level, set_performance_level); |
| 710 | 710 | ||
| 711 | static int read_battery_life_extender(struct samsung_laptop *samsung) | 711 | static int read_battery_life_extender(struct samsung_laptop *samsung) |
| @@ -774,7 +774,7 @@ static ssize_t set_battery_life_extender(struct device *dev, · 62 unmodified lines | |||
| 774 | return count; | 774 | return count; |
| 775 | } | 775 | } |
| 776 | 776 | ||
| 777 | static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, | 777 | static DEVICE_ATTR(battery_life_extender, 0644, |
| 778 | get_battery_life_extender, set_battery_life_extender); | 778 | get_battery_life_extender, set_battery_life_extender); |
| 779 | 779 | ||
| 780 | static int read_usb_charge(struct samsung_laptop *samsung) | 780 | static int read_usb_charge(struct samsung_laptop *samsung) |
| @@ -843,7 +843,7 @@ static ssize_t set_usb_charge(struct device *dev, · 62 unmodified lines | |||
| 843 | return count; | 843 | return count; |
| 844 | } | 844 | } |
| 845 | 845 | ||
| 846 | static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, | 846 | static DEVICE_ATTR(usb_charge, 0644, |
| 847 | get_usb_charge, set_usb_charge); | 847 | get_usb_charge, set_usb_charge); |
| 848 | 848 | ||
| 849 | static int read_lid_handling(struct samsung_laptop *samsung) | 849 | static int read_lid_handling(struct samsung_laptop *samsung) |
| @@ -908,7 +908,7 @@ static ssize_t set_lid_handling(struct device *dev, · 58 unmodified lines | |||
| 908 | return count; | 908 | return count; |
| 909 | } | 909 | } |
| 910 | 910 | ||
| 911 | static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO, | 911 | static DEVICE_ATTR(lid_handling, 0644, |
| 912 | get_lid_handling, set_lid_handling); | 912 | get_lid_handling, set_lid_handling); |
| 913 | 913 | ||
| 914 | static struct attribute *platform_attributes[] = { | 914 | static struct attribute *platform_attributes[] = { |
| @@ -1291,24 +1291,17 @@ static void samsung_debugfs_init(struct samsung_laptop *samsung) · 376 unmodified lines | |||
| 1291 | samsung->debug.sdiag_wrapper.data = samsung->sdiag; | 1291 | samsung->debug.sdiag_wrapper.data = samsung->sdiag; |
| 1292 | samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); | 1292 | samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); |
| 1293 | 1293 | ||
| 1294 | debugfs_create_u16("command", S_IRUGO | S_IWUSR, root, | 1294 | debugfs_create_u16("command", 0644, root, &samsung->debug.command); |
| 1295 | &samsung->debug.command); | 1295 | debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0); |
| 1296 | debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root, | 1296 | debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1); |
| 1297 | &samsung->debug.data.d0); | 1297 | debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2); |
| 1298 | debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root, | 1298 | debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3); |
| 1299 | &samsung->debug.data.d1); | 1299 | debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper); |
| 1300 | debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root, | 1300 | debugfs_create_blob("f0000_segment", 0400, root, |
| 1301 | &samsung->debug.data.d2); | ||
| 1302 | debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root, | ||
| 1303 | &samsung->debug.data.d3); | ||
| 1304 | debugfs_create_blob("data", S_IRUGO | S_IWUSR, root, | ||
| 1305 | &samsung->debug.data_wrapper); | ||
| 1306 | debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root, | ||
| 1307 | &samsung->debug.f0000_wrapper); | 1301 | &samsung->debug.f0000_wrapper); |
| 1308 | debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung, | 1302 | debugfs_create_file("call", 0444, root, samsung, |
| 1309 | &samsung_laptop_call_fops); | 1303 | &samsung_laptop_call_fops); |
| 1310 | debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root, | 1304 | debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper); |
| 1311 | &samsung->debug.sdiag_wrapper); | ||
| 1312 | } | 1305 | } |
| 1313 | 1306 | ||
| 1314 | static void samsung_sabi_exit(struct samsung_laptop *samsung) | 1307 | static void samsung_sabi_exit(struct samsung_laptop *samsung) |
| @@ -156,7 +156,7 @@ static struct attribute *tc1100_attributes[] = { · 155 unmodified lines | |||
| 156 | NULL | 156 | NULL |
| 157 | }; | 157 | }; |
| 158 | 158 | ||
| 159 | static struct attribute_group tc1100_attribute_group = { | 159 | static const struct attribute_group tc1100_attribute_group = { |
| 160 | .attrs = tc1100_attributes, | 160 | .attrs = tc1100_attributes, |
| 161 | }; | 161 | }; |
| 162 | 162 | ||
| @@ -0,0 +1,904 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | /* | ||
| 3 | * Think LMI BIOS configuration driver | ||
| 4 | * | ||
| 5 | * Copyright(C) 2019-2021 Lenovo | ||
| 6 | * | ||
| 7 | * Original code from Thinkpad-wmi project https://github.com/iksaif/thinkpad-wmi | ||
| 8 | * Copyright(C) 2017 Corentin Chary <corentin.chary@gmail.com> | ||
| 9 | * Distributed under the GPL-2.0 license | ||
| 10 | */ | ||
| 11 | |||
| 12 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
| 13 | |||
| 14 | #include <linux/acpi.h> | ||
| 15 | #include <linux/errno.h> | ||
| 16 | #include <linux/fs.h> | ||
| 17 | #include <linux/string.h> | ||
| 18 | #include <linux/types.h> | ||
| 19 | #include <linux/wmi.h> | ||
| 20 | #include "firmware_attributes_class.h" | ||
| 21 | #include "think-lmi.h" | ||
| 22 | |||
| 23 | /* | ||
| 24 | * Name: | ||
| 25 | * Lenovo_BiosSetting | ||
| 26 | * Description: | ||
| 27 | * Get item name and settings for current LMI instance. | ||
| 28 | * Type: | ||
| 29 | * Query | ||
| 30 | * Returns: | ||
| 31 | * "Item,Value" | ||
| 32 | * Example: | ||
| 33 | * "WakeOnLAN,Enable" | ||
| 34 | */ | ||
| 35 | #define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7" | ||
| 36 | |||
| 37 | /* | ||
| 38 | * Name: | ||
| 39 | * Lenovo_SetBiosSetting | ||
| 40 | * Description: | ||
| 41 | * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting | ||
| 42 | * class. To save the settings, use the Lenovo_SaveBiosSetting class. | ||
| 43 | * BIOS settings and values are case sensitive. | ||
| 44 | * After making changes to the BIOS settings, you must reboot the computer | ||
| 45 | * before the changes will take effect. | ||
| 46 | * Type: | ||
| 47 | * Method | ||
| 48 | * Arguments: | ||
| 49 | * "Item,Value,Password,Encoding,KbdLang;" | ||
| 50 | * Example: | ||
| 51 | * "WakeOnLAN,Disable,pa55w0rd,ascii,us;" | ||
| 52 | */ | ||
| 53 | #define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1" | ||
| 54 | |||
| 55 | /* | ||
| 56 | * Name: | ||
| 57 | * Lenovo_SaveBiosSettings | ||
| 58 | * Description: | ||
| 59 | * Save any pending changes in settings. | ||
| 60 | * Type: | ||
| 61 | * Method | ||
| 62 | * Arguments: | ||
| 63 | * "Password,Encoding,KbdLang;" | ||
| 64 | * Example: | ||
| 65 | * "pa55w0rd,ascii,us;" | ||
| 66 | */ | ||
| 67 | #define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3" | ||
| 68 | |||
| 69 | /* | ||
| 70 | * Name: | ||
| 71 | * Lenovo_BiosPasswordSettings | ||
| 72 | * Description: | ||
| 73 | * Return BIOS Password settings | ||
| 74 | * Type: | ||
| 75 | * Query | ||
| 76 | * Returns: | ||
| 77 | * PasswordMode, PasswordState, MinLength, MaxLength, | ||
| 78 | * SupportedEncoding, SupportedKeyboard | ||
| 79 | */ | ||
| 80 | #define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246" | ||
| 81 | |||
| 82 | /* | ||
| 83 | * Name: | ||
| 84 | * Lenovo_SetBiosPassword | ||
| 85 | * Description: | ||
| 86 | * Change a specific password. | ||
| 87 | * - BIOS settings cannot be changed at the same boot as power-on | ||
| 88 | * passwords (POP) and hard disk passwords (HDP). If you want to change | ||
| 89 | * BIOS settings and POP or HDP, you must reboot the system after changing | ||
| 90 | * one of them. | ||
| 91 | * - A password cannot be set using this method when one does not already | ||
| 92 | * exist. Passwords can only be updated or cleared. | ||
| 93 | * Type: | ||
| 94 | * Method | ||
| 95 | * Arguments: | ||
| 96 | * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" | ||
| 97 | * Example: | ||
| 98 | * "pop,pa55w0rd,newpa55w0rd,ascii,us;” | ||
| 99 | */ | ||
| 100 | #define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7" | ||
| 101 | |||
| 102 | /* | ||
| 103 | * Name: | ||
| 104 | * Lenovo_GetBiosSelections | ||
| 105 | * Description: | ||
| 106 | * Return a list of valid settings for a given item. | ||
| 107 | * Type: | ||
| 108 | * Method | ||
| 109 | * Arguments: | ||
| 110 | * "Item" | ||
| 111 | * Returns: | ||
| 112 | * "Value1,Value2,Value3,..." | ||
| 113 | * Example: | ||
| 114 | * -> "FlashOverLAN" | ||
| 115 | * <- "Enabled,Disabled" | ||
| 116 | */ | ||
| 117 | #define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" | ||
| 118 | |||
| 119 | #define TLMI_POP_PWD (1 << 0) | ||
| 120 | #define TLMI_PAP_PWD (1 << 1) | ||
| 121 | #define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) | ||
| 122 | #define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) | ||
| 123 | |||
| 124 | static const struct tlmi_err_codes tlmi_errs[] = { | ||
| 125 | {"Success", 0}, | ||
| 126 | {"Not Supported", -EOPNOTSUPP}, | ||
| 127 | {"Invalid Parameter", -EINVAL}, | ||
| 128 | {"Access Denied", -EACCES}, | ||
| 129 | {"System Busy", -EBUSY}, | ||
| 130 | }; | ||
| 131 | |||
| 132 | static const char * const encoding_options[] = { | ||
| 133 | [TLMI_ENCODING_ASCII] = "ascii", | ||
| 134 | [TLMI_ENCODING_SCANCODE] = "scancode", | ||
| 135 | }; | ||
| 136 | static struct think_lmi tlmi_priv; | ||
| 137 | static struct class *fw_attr_class; | ||
| 138 | |||
| 139 | /* ------ Utility functions ------------*/ | ||
| 140 | /* Convert BIOS WMI error string to suitable error code */ | ||
| 141 | static int tlmi_errstr_to_err(const char *errstr) | ||
| 142 | { | ||
| 143 | int i; | ||
| 144 | |||
| 145 | for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) { | ||
| 146 | if (!strcmp(tlmi_errs[i].err_str, errstr)) | ||
| 147 | return tlmi_errs[i].err_code; | ||
| 148 | } | ||
| 149 | return -EPERM; | ||
| 150 | } | ||
| 151 | |||
| 152 | /* Extract error string from WMI return buffer */ | ||
| 153 | static int tlmi_extract_error(const struct acpi_buffer *output) | ||
| 154 | { | ||
| 155 | const union acpi_object *obj; | ||
| 156 | |||
| 157 | obj = output->pointer; | ||
| 158 | if (!obj) | ||
| 159 | return -ENOMEM; | ||
| 160 | if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) | ||
| 161 | return -EIO; | ||
| 162 | |||
| 163 | return tlmi_errstr_to_err(obj->string.pointer); | ||
| 164 | } | ||
| 165 | |||
| 166 | /* Utility function to execute WMI call to BIOS */ | ||
| 167 | static int tlmi_simple_call(const char *guid, const char *arg) | ||
| 168 | { | ||
| 169 | const struct acpi_buffer input = { strlen(arg), (char *)arg }; | ||
| 170 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 171 | acpi_status status; | ||
| 172 | int i, err; | ||
| 173 | |||
| 174 | /* | ||
| 175 | * Duplicated call required to match BIOS workaround for behavior | ||
| 176 | * seen when WMI accessed via scripting on other OS. | ||
| 177 | */ | ||
| 178 | for (i = 0; i < 2; i++) { | ||
| 179 | /* (re)initialize output buffer to default state */ | ||
| 180 | output.length = ACPI_ALLOCATE_BUFFER; | ||
| 181 | output.pointer = NULL; | ||
| 182 | |||
| 183 | status = wmi_evaluate_method(guid, 0, 0, &input, &output); | ||
| 184 | if (ACPI_FAILURE(status)) { | ||
| 185 | kfree(output.pointer); | ||
| 186 | return -EIO; | ||
| 187 | } | ||
| 188 | err = tlmi_extract_error(&output); | ||
| 189 | kfree(output.pointer); | ||
| 190 | if (err) | ||
| 191 | return err; | ||
| 192 | } | ||
| 193 | return 0; | ||
| 194 | } | ||
| 195 | |||
| 196 | /* Extract output string from WMI return buffer */ | ||
| 197 | static int tlmi_extract_output_string(const struct acpi_buffer *output, | ||
| 198 | char **string) | ||
| 199 | { | ||
| 200 | const union acpi_object *obj; | ||
| 201 | char *s; | ||
| 202 | |||
| 203 | obj = output->pointer; | ||
| 204 | if (!obj) | ||
| 205 | return -ENOMEM; | ||
| 206 | if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) | ||
| 207 | return -EIO; | ||
| 208 | |||
| 209 | s = kstrdup(obj->string.pointer, GFP_KERNEL); | ||
| 210 | if (!s) | ||
| 211 | return -ENOMEM; | ||
| 212 | *string = s; | ||
| 213 | return 0; | ||
| 214 | } | ||
| 215 | |||
| 216 | /* ------ Core interface functions ------------*/ | ||
| 217 | |||
| 218 | /* Get password settings from BIOS */ | ||
| 219 | static int tlmi_get_pwd_settings(struct tlmi_pwdcfg *pwdcfg) | ||
| 220 | { | ||
| 221 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 222 | const union acpi_object *obj; | ||
| 223 | acpi_status status; | ||
| 224 | |||
| 225 | if (!tlmi_priv.can_get_password_settings) | ||
| 226 | return -EOPNOTSUPP; | ||
| 227 | |||
| 228 | status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0, | ||
| 229 | &output); | ||
| 230 | if (ACPI_FAILURE(status)) | ||
| 231 | return -EIO; | ||
| 232 | |||
| 233 | obj = output.pointer; | ||
| 234 | if (!obj) | ||
| 235 | return -ENOMEM; | ||
| 236 | if (obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer) { | ||
| 237 | kfree(obj); | ||
| 238 | return -EIO; | ||
| 239 | } | ||
| 240 | /* | ||
| 241 | * The size of thinkpad_wmi_pcfg on ThinkStation is larger than ThinkPad. | ||
| 242 | * To make the driver compatible on different brands, we permit it to get | ||
| 243 | * the data in below case. | ||
| 244 | */ | ||
| 245 | if (obj->buffer.length < sizeof(struct tlmi_pwdcfg)) { | ||
| 246 | pr_warn("Unknown pwdcfg buffer length %d\n", obj->buffer.length); | ||
| 247 | kfree(obj); | ||
| 248 | return -EIO; | ||
| 249 | } | ||
| 250 | memcpy(pwdcfg, obj->buffer.pointer, sizeof(struct tlmi_pwdcfg)); | ||
| 251 | kfree(obj); | ||
| 252 | return 0; | ||
| 253 | } | ||
| 254 | |||
| 255 | static int tlmi_save_bios_settings(const char *password) | ||
| 256 | { | ||
| 257 | return tlmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID, | ||
| 258 | password); | ||
| 259 | } | ||
| 260 | |||
| 261 | static int tlmi_setting(int item, char **value, const char *guid_string) | ||
| 262 | { | ||
| 263 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 264 | acpi_status status; | ||
| 265 | int ret; | ||
| 266 | |||
| 267 | status = wmi_query_block(guid_string, item, &output); | ||
| 268 | if (ACPI_FAILURE(status)) { | ||
| 269 | kfree(output.pointer); | ||
| 270 | return -EIO; | ||
| 271 | } | ||
| 272 | |||
| 273 | ret = tlmi_extract_output_string(&output, value); | ||
| 274 | kfree(output.pointer); | ||
| 275 | return ret; | ||
| 276 | } | ||
| 277 | |||
| 278 | static int tlmi_get_bios_selections(const char *item, char **value) | ||
| 279 | { | ||
| 280 | const struct acpi_buffer input = { strlen(item), (char *)item }; | ||
| 281 | struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; | ||
| 282 | acpi_status status; | ||
| 283 | int ret; | ||
| 284 | |||
| 285 | status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID, | ||
| 286 | 0, 0, &input, &output); | ||
| 287 | |||
| 288 | if (ACPI_FAILURE(status)) { | ||
| 289 | kfree(output.pointer); | ||
| 290 | return -EIO; | ||
| 291 | } | ||
| 292 | |||
| 293 | ret = tlmi_extract_output_string(&output, value); | ||
| 294 | kfree(output.pointer); | ||
| 295 | return ret; | ||
| 296 | } | ||
| 297 | |||
| 298 | /* ---- Authentication sysfs --------------------------------------------------------- */ | ||
| 299 | static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 300 | char *buf) | ||
| 301 | { | ||
| 302 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 303 | |||
| 304 | return sysfs_emit(buf, "%d\n", setting->valid); | ||
| 305 | } | ||
| 306 | |||
| 307 | static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled); | ||
| 308 | |||
| 309 | static ssize_t current_password_store(struct kobject *kobj, | ||
| 310 | struct kobj_attribute *attr, | ||
| 311 | const char *buf, size_t count) | ||
| 312 | { | ||
| 313 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 314 | size_t pwdlen; | ||
| 315 | char *p; | ||
| 316 | |||
| 317 | pwdlen = strlen(buf); | ||
| 318 | /* pwdlen == 0 is allowed to clear the password */ | ||
| 319 | if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) | ||
| 320 | return -EINVAL; | ||
| 321 | |||
| 322 | strscpy(setting->password, buf, setting->maxlen); | ||
| 323 | /* Strip out CR if one is present, setting password won't work if it is present */ | ||
| 324 | p = strchrnul(setting->password, '\n'); | ||
| 325 | *p = '\0'; | ||
| 326 | return count; | ||
| 327 | } | ||
| 328 | |||
| 329 | static struct kobj_attribute auth_current_password = __ATTR_WO(current_password); | ||
| 330 | |||
| 331 | static ssize_t new_password_store(struct kobject *kobj, | ||
| 332 | struct kobj_attribute *attr, | ||
| 333 | const char *buf, size_t count) | ||
| 334 | { | ||
| 335 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 336 | char *auth_str, *new_pwd, *p; | ||
| 337 | size_t pwdlen; | ||
| 338 | int ret; | ||
| 339 | |||
| 340 | if (!capable(CAP_SYS_ADMIN)) | ||
| 341 | return -EPERM; | ||
| 342 | |||
| 343 | if (!tlmi_priv.can_set_bios_password) | ||
| 344 | return -EOPNOTSUPP; | ||
| 345 | |||
| 346 | new_pwd = kstrdup(buf, GFP_KERNEL); | ||
| 347 | if (!new_pwd) | ||
| 348 | return -ENOMEM; | ||
| 349 | |||
| 350 | /* Strip out CR if one is present, setting password won't work if it is present */ | ||
| 351 | p = strchrnul(new_pwd, '\n'); | ||
| 352 | *p = '\0'; | ||
| 353 | |||
| 354 | pwdlen = strlen(new_pwd); | ||
| 355 | /* pwdlen == 0 is allowed to clear the password */ | ||
| 356 | if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) { | ||
| 357 | ret = -EINVAL; | ||
| 358 | goto out; | ||
| 359 | } | ||
| 360 | |||
| 361 | /* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */ | ||
| 362 | auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s,%s;", | ||
| 363 | setting->pwd_type, setting->password, new_pwd, | ||
| 364 | encoding_options[setting->encoding], setting->kbdlang); | ||
| 365 | if (!auth_str) { | ||
| 366 | ret = -ENOMEM; | ||
| 367 | goto out; | ||
| 368 | } | ||
| 369 | ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str); | ||
| 370 | kfree(auth_str); | ||
| 371 | out: | ||
| 372 | kfree(new_pwd); | ||
| 373 | return ret ?: count; | ||
| 374 | } | ||
| 375 | |||
| 376 | static struct kobj_attribute auth_new_password = __ATTR_WO(new_password); | ||
| 377 | |||
| 378 | static ssize_t min_password_length_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 379 | char *buf) | ||
| 380 | { | ||
| 381 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 382 | |||
| 383 | return sysfs_emit(buf, "%d\n", setting->minlen); | ||
| 384 | } | ||
| 385 | |||
| 386 | static struct kobj_attribute auth_min_pass_length = __ATTR_RO(min_password_length); | ||
| 387 | |||
| 388 | static ssize_t max_password_length_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 389 | char *buf) | ||
| 390 | { | ||
| 391 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 392 | |||
| 393 | return sysfs_emit(buf, "%d\n", setting->maxlen); | ||
| 394 | } | ||
| 395 | static struct kobj_attribute auth_max_pass_length = __ATTR_RO(max_password_length); | ||
| 396 | |||
| 397 | static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 398 | char *buf) | ||
| 399 | { | ||
| 400 | return sysfs_emit(buf, "password\n"); | ||
| 401 | } | ||
| 402 | static struct kobj_attribute auth_mechanism = __ATTR_RO(mechanism); | ||
| 403 | |||
| 404 | static ssize_t encoding_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 405 | char *buf) | ||
| 406 | { | ||
| 407 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 408 | |||
| 409 | return sysfs_emit(buf, "%s\n", encoding_options[setting->encoding]); | ||
| 410 | } | ||
| 411 | |||
| 412 | static ssize_t encoding_store(struct kobject *kobj, | ||
| 413 | struct kobj_attribute *attr, | ||
| 414 | const char *buf, size_t count) | ||
| 415 | { | ||
| 416 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 417 | int i; | ||
| 418 | |||
| 419 | /* Scan for a matching profile */ | ||
| 420 | i = sysfs_match_string(encoding_options, buf); | ||
| 421 | if (i < 0) | ||
| 422 | return -EINVAL; | ||
| 423 | |||
| 424 | setting->encoding = i; | ||
| 425 | return count; | ||
| 426 | } | ||
| 427 | |||
| 428 | static struct kobj_attribute auth_encoding = __ATTR_RW(encoding); | ||
| 429 | |||
| 430 | static ssize_t kbdlang_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 431 | char *buf) | ||
| 432 | { | ||
| 433 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 434 | |||
| 435 | return sysfs_emit(buf, "%s\n", setting->kbdlang); | ||
| 436 | } | ||
| 437 | |||
| 438 | static ssize_t kbdlang_store(struct kobject *kobj, | ||
| 439 | struct kobj_attribute *attr, | ||
| 440 | const char *buf, size_t count) | ||
| 441 | { | ||
| 442 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 443 | int length; | ||
| 444 | |||
| 445 | /* Calculate length till '\n' or terminating 0 */ | ||
| 446 | length = strchrnul(buf, '\n') - buf; | ||
| 447 | if (!length || length >= TLMI_LANG_MAXLEN) | ||
| 448 | return -EINVAL; | ||
| 449 | |||
| 450 | memcpy(setting->kbdlang, buf, length); | ||
| 451 | setting->kbdlang[length] = '\0'; | ||
| 452 | return count; | ||
| 453 | } | ||
| 454 | |||
| 455 | static struct kobj_attribute auth_kbdlang = __ATTR_RW(kbdlang); | ||
| 456 | |||
| 457 | static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 458 | char *buf) | ||
| 459 | { | ||
| 460 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 461 | |||
| 462 | return sysfs_emit(buf, "%s\n", setting->role); | ||
| 463 | } | ||
| 464 | static struct kobj_attribute auth_role = __ATTR_RO(role); | ||
| 465 | |||
| 466 | static struct attribute *auth_attrs[] = { | ||
| 467 | &auth_is_pass_set.attr, | ||
| 468 | &auth_min_pass_length.attr, | ||
| 469 | &auth_max_pass_length.attr, | ||
| 470 | &auth_current_password.attr, | ||
| 471 | &auth_new_password.attr, | ||
| 472 | &auth_role.attr, | ||
| 473 | &auth_mechanism.attr, | ||
| 474 | &auth_encoding.attr, | ||
| 475 | &auth_kbdlang.attr, | ||
| 476 | NULL | ||
| 477 | }; | ||
| 478 | |||
| 479 | static const struct attribute_group auth_attr_group = { | ||
| 480 | .attrs = auth_attrs, | ||
| 481 | }; | ||
| 482 | |||
| 483 | /* ---- Attributes sysfs --------------------------------------------------------- */ | ||
| 484 | static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, | ||
| 485 | char *buf) | ||
| 486 | { | ||
| 487 | struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); | ||
| 488 | |||
| 489 | return sysfs_emit(buf, "%s\n", setting->display_name); | ||
| 490 | } | ||
| 491 | |||
| 492 | static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) | ||
| 493 | { | ||
| 494 | struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); | ||
| 495 | char *item, *value; | ||
| 496 | int ret; | ||
| 497 | |||
| 498 | ret = tlmi_setting(setting->index, &item, LENOVO_BIOS_SETTING_GUID); | ||
| 499 | if (ret) | ||
| 500 | return ret; | ||
| 501 | |||
| 502 | /* validate and split from `item,value` -> `value` */ | ||
| 503 | value = strpbrk(item, ","); | ||
| 504 | if (!value || value == item || !strlen(value + 1)) | ||
| 505 | return -EINVAL; | ||
| 506 | |||
| 507 | ret = sysfs_emit(buf, "%s\n", value + 1); | ||
| 508 | kfree(item); | ||
| 509 | return ret; | ||
| 510 | } | ||
| 511 | |||
| 512 | static ssize_t possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) | ||
| 513 | { | ||
| 514 | struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); | ||
| 515 | |||
| 516 | if (!tlmi_priv.can_get_bios_selections) | ||
| 517 | return -EOPNOTSUPP; | ||
| 518 | |||
| 519 | return sysfs_emit(buf, "%s\n", setting->possible_values); | ||
| 520 | } | ||
| 521 | |||
| 522 | static ssize_t current_value_store(struct kobject *kobj, | ||
| 523 | struct kobj_attribute *attr, | ||
| 524 | const char *buf, size_t count) | ||
| 525 | { | ||
| 526 | struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); | ||
| 527 | char *set_str = NULL, *new_setting = NULL; | ||
| 528 | char *auth_str = NULL; | ||
| 529 | char *p; | ||
| 530 | int ret; | ||
| 531 | |||
| 532 | if (!tlmi_priv.can_set_bios_settings) | ||
| 533 | return -EOPNOTSUPP; | ||
| 534 | |||
| 535 | new_setting = kstrdup(buf, GFP_KERNEL); | ||
| 536 | if (!new_setting) | ||
| 537 | return -ENOMEM; | ||
| 538 | |||
| 539 | /* Strip out CR if one is present */ | ||
| 540 | p = strchrnul(new_setting, '\n'); | ||
| 541 | *p = '\0'; | ||
| 542 | |||
| 543 | if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { | ||
| 544 | auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", | ||
| 545 | tlmi_priv.pwd_admin->password, | ||
| 546 | encoding_options[tlmi_priv.pwd_admin->encoding], | ||
| 547 | tlmi_priv.pwd_admin->kbdlang); | ||
| 548 | if (!auth_str) { | ||
| 549 | ret = -ENOMEM; | ||
| 550 | goto out; | ||
| 551 | } | ||
| 552 | } | ||
| 553 | |||
| 554 | if (auth_str) | ||
| 555 | set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, | ||
| 556 | new_setting, auth_str); | ||
| 557 | else | ||
| 558 | set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, | ||
| 559 | new_setting); | ||
| 560 | if (!set_str) { | ||
| 561 | ret = -ENOMEM; | ||
| 562 | goto out; | ||
| 563 | } | ||
| 564 | |||
| 565 | ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); | ||
| 566 | if (ret) | ||
| 567 | goto out; | ||
| 568 | |||
| 569 | if (auth_str) | ||
| 570 | ret = tlmi_save_bios_settings(auth_str); | ||
| 571 | else | ||
| 572 | ret = tlmi_save_bios_settings(""); | ||
| 573 | |||
| 574 | out: | ||
| 575 | kfree(auth_str); | ||
| 576 | kfree(set_str); | ||
| 577 | kfree(new_setting); | ||
| 578 | return ret ?: count; | ||
| 579 | } | ||
| 580 | |||
| 581 | static struct kobj_attribute attr_displ_name = __ATTR_RO(display_name); | ||
| 582 | |||
| 583 | static struct kobj_attribute attr_possible_values = __ATTR_RO(possible_values); | ||
| 584 | |||
| 585 | static struct kobj_attribute attr_current_val = __ATTR_RW_MODE(current_value, 0600); | ||
| 586 | |||
| 587 | static struct attribute *tlmi_attrs[] = { | ||
| 588 | &attr_displ_name.attr, | ||
| 589 | &attr_current_val.attr, | ||
| 590 | &attr_possible_values.attr, | ||
| 591 | NULL | ||
| 592 | }; | ||
| 593 | |||
| 594 | static const struct attribute_group tlmi_attr_group = { | ||
| 595 | .attrs = tlmi_attrs, | ||
| 596 | }; | ||
| 597 | |||
| 598 | static ssize_t tlmi_attr_show(struct kobject *kobj, struct attribute *attr, | ||
| 599 | char *buf) | ||
| 600 | { | ||
| 601 | struct kobj_attribute *kattr; | ||
| 602 | |||
| 603 | kattr = container_of(attr, struct kobj_attribute, attr); | ||
| 604 | if (kattr->show) | ||
| 605 | return kattr->show(kobj, kattr, buf); | ||
| 606 | return -EIO; | ||
| 607 | } | ||
| 608 | |||
| 609 | static ssize_t tlmi_attr_store(struct kobject *kobj, struct attribute *attr, | ||
| 610 | const char *buf, size_t count) | ||
| 611 | { | ||
| 612 | struct kobj_attribute *kattr; | ||
| 613 | |||
| 614 | kattr = container_of(attr, struct kobj_attribute, attr); | ||
| 615 | if (kattr->store) | ||
| 616 | return kattr->store(kobj, kattr, buf, count); | ||
| 617 | return -EIO; | ||
| 618 | } | ||
| 619 | |||
| 620 | static const struct sysfs_ops tlmi_kobj_sysfs_ops = { | ||
| 621 | .show = tlmi_attr_show, | ||
| 622 | .store = tlmi_attr_store, | ||
| 623 | }; | ||
| 624 | |||
| 625 | static void tlmi_attr_setting_release(struct kobject *kobj) | ||
| 626 | { | ||
| 627 | struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); | ||
| 628 | |||
| 629 | kfree(setting->possible_values); | ||
| 630 | kfree(setting); | ||
| 631 | } | ||
| 632 | |||
| 633 | static void tlmi_pwd_setting_release(struct kobject *kobj) | ||
| 634 | { | ||
| 635 | struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); | ||
| 636 | |||
| 637 | kfree(setting); | ||
| 638 | } | ||
| 639 | |||
| 640 | static struct kobj_type tlmi_attr_setting_ktype = { | ||
| 641 | .release = &tlmi_attr_setting_release, | ||
| 642 | .sysfs_ops = &tlmi_kobj_sysfs_ops, | ||
| 643 | }; | ||
| 644 | |||
| 645 | static struct kobj_type tlmi_pwd_setting_ktype = { | ||
| 646 | .release = &tlmi_pwd_setting_release, | ||
| 647 | .sysfs_ops = &tlmi_kobj_sysfs_ops, | ||
| 648 | }; | ||
| 649 | |||
| 650 | /* ---- Initialisation --------------------------------------------------------- */ | ||
| 651 | static void tlmi_release_attr(void) | ||
| 652 | { | ||
| 653 | int i; | ||
| 654 | |||
| 655 | /* Attribute structures */ | ||
| 656 | for (i = 0; i < TLMI_SETTINGS_COUNT; i++) { | ||
| 657 | if (tlmi_priv.setting[i]) { | ||
| 658 | sysfs_remove_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group); | ||
| 659 | kobject_put(&tlmi_priv.setting[i]->kobj); | ||
| 660 | } | ||
| 661 | } | ||
| 662 | kset_unregister(tlmi_priv.attribute_kset); | ||
| 663 | |||
| 664 | /* Authentication structures */ | ||
| 665 | sysfs_remove_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group); | ||
| 666 | kobject_put(&tlmi_priv.pwd_admin->kobj); | ||
| 667 | sysfs_remove_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group); | ||
| 668 | kobject_put(&tlmi_priv.pwd_power->kobj); | ||
| 669 | kset_unregister(tlmi_priv.authentication_kset); | ||
| 670 | } | ||
| 671 | |||
| 672 | static int tlmi_sysfs_init(void) | ||
| 673 | { | ||
| 674 | int i, ret; | ||
| 675 | |||
| 676 | ret = fw_attributes_class_get(&fw_attr_class); | ||
| 677 | if (ret) | ||
| 678 | return ret; | ||
| 679 | |||
| 680 | tlmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), | ||
| 681 | NULL, "%s", "thinklmi"); | ||
| 682 | if (IS_ERR(tlmi_priv.class_dev)) { | ||
| 683 | ret = PTR_ERR(tlmi_priv.class_dev); | ||
| 684 | goto fail_class_created; | ||
| 685 | } | ||
| 686 | |||
| 687 | tlmi_priv.attribute_kset = kset_create_and_add("attributes", NULL, | ||
| 688 | &tlmi_priv.class_dev->kobj); | ||
| 689 | if (!tlmi_priv.attribute_kset) { | ||
| 690 | ret = -ENOMEM; | ||
| 691 | goto fail_device_created; | ||
| 692 | } | ||
| 693 | |||
| 694 | for (i = 0; i < TLMI_SETTINGS_COUNT; i++) { | ||
| 695 | /* Check if index is a valid setting - skip if it isn't */ | ||
| 696 | if (!tlmi_priv.setting[i]) | ||
| 697 | continue; | ||
| 698 | |||
| 699 | /* check for duplicate or reserved values */ | ||
| 700 | if (kset_find_obj(tlmi_priv.attribute_kset, tlmi_priv.setting[i]->display_name) || | ||
| 701 | !strcmp(tlmi_priv.setting[i]->display_name, "Reserved")) { | ||
| 702 | pr_debug("duplicate or reserved attribute name found - %s\n", | ||
| 703 | tlmi_priv.setting[i]->display_name); | ||
| 704 | kfree(tlmi_priv.setting[i]->possible_values); | ||
| 705 | kfree(tlmi_priv.setting[i]); | ||
| 706 | tlmi_priv.setting[i] = NULL; | ||
| 707 | continue; | ||
| 708 | } | ||
| 709 | |||
| 710 | /* Build attribute */ | ||
| 711 | tlmi_priv.setting[i]->kobj.kset = tlmi_priv.attribute_kset; | ||
| 712 | ret = kobject_init_and_add(&tlmi_priv.setting[i]->kobj, &tlmi_attr_setting_ktype, | ||
| 713 | NULL, "%s", tlmi_priv.setting[i]->display_name); | ||
| 714 | if (ret) | ||
| 715 | goto fail_create_attr; | ||
| 716 | |||
| 717 | ret = sysfs_create_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group); | ||
| 718 | if (ret) | ||
| 719 | goto fail_create_attr; | ||
| 720 | } | ||
| 721 | |||
| 722 | /* Create authentication entries */ | ||
| 723 | tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, | ||
| 724 | &tlmi_priv.class_dev->kobj); | ||
| 725 | if (!tlmi_priv.authentication_kset) { | ||
| 726 | ret = -ENOMEM; | ||
| 727 | goto fail_create_attr; | ||
| 728 | } | ||
| 729 | tlmi_priv.pwd_admin->kobj.kset = tlmi_priv.authentication_kset; | ||
| 730 | ret = kobject_init_and_add(&tlmi_priv.pwd_admin->kobj, &tlmi_pwd_setting_ktype, | ||
| 731 | NULL, "%s", "Admin"); | ||
| 732 | if (ret) | ||
| 733 | goto fail_create_attr; | ||
| 734 | |||
| 735 | ret = sysfs_create_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group); | ||
| 736 | if (ret) | ||
| 737 | goto fail_create_attr; | ||
| 738 | |||
| 739 | tlmi_priv.pwd_power->kobj.kset = tlmi_priv.authentication_kset; | ||
| 740 | ret = kobject_init_and_add(&tlmi_priv.pwd_power->kobj, &tlmi_pwd_setting_ktype, | ||
| 741 | NULL, "%s", "System"); | ||
| 742 | if (ret) | ||
| 743 | goto fail_create_attr; | ||
| 744 | |||
| 745 | ret = sysfs_create_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group); | ||
| 746 | if (ret) | ||
| 747 | goto fail_create_attr; | ||
| 748 | |||
| 749 | return ret; | ||
| 750 | |||
| 751 | fail_create_attr: | ||
| 752 | tlmi_release_attr(); | ||
| 753 | fail_device_created: | ||
| 754 | device_destroy(fw_attr_class, MKDEV(0, 0)); | ||
| 755 | fail_class_created: | ||
| 756 | fw_attributes_class_put(); | ||
| 757 | return ret; | ||
| 758 | } | ||
| 759 | |||
| 760 | /* ---- Base Driver -------------------------------------------------------- */ | ||
| 761 | static int tlmi_analyze(void) | ||
| 762 | { | ||
| 763 | struct tlmi_pwdcfg pwdcfg; | ||
| 764 | acpi_status status; | ||
| 765 | int i, ret; | ||
| 766 | |||
| 767 | if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) && | ||
| 768 | wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID)) | ||
| 769 | tlmi_priv.can_set_bios_settings = true; | ||
| 770 | |||
| 771 | if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID)) | ||
| 772 | tlmi_priv.can_get_bios_selections = true; | ||
| 773 | |||
| 774 | if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID)) | ||
| 775 | tlmi_priv.can_set_bios_password = true; | ||
| 776 | |||
| 777 | if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID)) | ||
| 778 | tlmi_priv.can_get_password_settings = true; | ||
| 779 | |||
| 780 | /* | ||
| 781 | * Try to find the number of valid settings of this machine | ||
| 782 | * and use it to create sysfs attributes. | ||
| 783 | */ | ||
| 784 | for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) { | ||
| 785 | struct tlmi_attr_setting *setting; | ||
| 786 | char *item = NULL; | ||
| 787 | char *p; | ||
| 788 | |||
| 789 | tlmi_priv.setting[i] = NULL; | ||
| 790 | status = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID); | ||
| 791 | if (ACPI_FAILURE(status)) | ||
| 792 | break; | ||
| 793 | if (!item) | ||
| 794 | break; | ||
| 795 | if (!*item) | ||
| 796 | continue; | ||
| 797 | |||
| 798 | /* It is not allowed to have '/' for file name. Convert it into '\'. */ | ||
| 799 | strreplace(item, '/', '\\'); | ||
| 800 | |||
| 801 | /* Remove the value part */ | ||
| 802 | p = strchrnul(item, ','); | ||
| 803 | *p = '\0'; | ||
| 804 | |||
| 805 | /* Create a setting entry */ | ||
| 806 | setting = kzalloc(sizeof(*setting), GFP_KERNEL); | ||
| 807 | if (!setting) { | ||
| 808 | ret = -ENOMEM; | ||
| 809 | goto fail_clear_attr; | ||
| 810 | } | ||
| 811 | setting->index = i; | ||
| 812 | strscpy(setting->display_name, item, TLMI_SETTINGS_MAXLEN); | ||
| 813 | /* If BIOS selections supported, load those */ | ||
| 814 | if (tlmi_priv.can_get_bios_selections) { | ||
| 815 | ret = tlmi_get_bios_selections(setting->display_name, | ||
| 816 | &setting->possible_values); | ||
| 817 | if (ret || !setting->possible_values) | ||
| 818 | pr_info("Error retrieving possible values for %d : %s\n", | ||
| 819 | i, setting->display_name); | ||
| 820 | } | ||
| 821 | tlmi_priv.setting[i] = setting; | ||
| 822 | tlmi_priv.settings_count++; | ||
| 823 | kfree(item); | ||
| 824 | } | ||
| 825 | |||
| 826 | /* Create password setting structure */ | ||
| 827 | ret = tlmi_get_pwd_settings(&pwdcfg); | ||
| 828 | if (ret) | ||
| 829 | goto fail_clear_attr; | ||
| 830 | |||
| 831 | tlmi_priv.pwd_admin = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL); | ||
| 832 | if (!tlmi_priv.pwd_admin) { | ||
| 833 | ret = -ENOMEM; | ||
| 834 | goto fail_clear_attr; | ||
| 835 | } | ||
| 836 | strscpy(tlmi_priv.pwd_admin->kbdlang, "us", TLMI_LANG_MAXLEN); | ||
| 837 | tlmi_priv.pwd_admin->encoding = TLMI_ENCODING_ASCII; | ||
| 838 | tlmi_priv.pwd_admin->pwd_type = "pap"; | ||
| 839 | tlmi_priv.pwd_admin->role = "bios-admin"; | ||
| 840 | tlmi_priv.pwd_admin->minlen = pwdcfg.min_length; | ||
| 841 | if (WARN_ON(pwdcfg.max_length >= TLMI_PWD_BUFSIZE)) | ||
| 842 | pwdcfg.max_length = TLMI_PWD_BUFSIZE - 1; | ||
| 843 | tlmi_priv.pwd_admin->maxlen = pwdcfg.max_length; | ||
| 844 | if (pwdcfg.password_state & TLMI_PAP_PWD) | ||
| 845 | tlmi_priv.pwd_admin->valid = true; | ||
| 846 | |||
| 847 | tlmi_priv.pwd_power = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL); | ||
| 848 | if (!tlmi_priv.pwd_power) { | ||
| 849 | ret = -ENOMEM; | ||
| 850 | goto fail_clear_attr; | ||
| 851 | } | ||
| 852 | strscpy(tlmi_priv.pwd_power->kbdlang, "us", TLMI_LANG_MAXLEN); | ||
| 853 | tlmi_priv.pwd_power->encoding = TLMI_ENCODING_ASCII; | ||
| 854 | tlmi_priv.pwd_power->pwd_type = "pop"; | ||
| 855 | tlmi_priv.pwd_power->role = "power-on"; | ||
| 856 | tlmi_priv.pwd_power->minlen = pwdcfg.min_length; | ||
| 857 | tlmi_priv.pwd_power->maxlen = pwdcfg.max_length; | ||
| 858 | |||
| 859 | if (pwdcfg.password_state & TLMI_POP_PWD) | ||
| 860 | tlmi_priv.pwd_power->valid = true; | ||
| 861 | |||
| 862 | return 0; | ||
| 863 | |||
| 864 | fail_clear_attr: | ||
| 865 | for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) | ||
| 866 | kfree(tlmi_priv.setting[i]); | ||
| 867 | return ret; | ||
| 868 | } | ||
| 869 | |||
| 870 | static void tlmi_remove(struct wmi_device *wdev) | ||
| 871 | { | ||
| 872 | tlmi_release_attr(); | ||
| 873 | device_destroy(fw_attr_class, MKDEV(0, 0)); | ||
| 874 | fw_attributes_class_put(); | ||
| 875 | } | ||
| 876 | |||
| 877 | static int tlmi_probe(struct wmi_device *wdev, const void *context) | ||
| 878 | { | ||
| 879 | tlmi_analyze(); | ||
| 880 | return tlmi_sysfs_init(); | ||
| 881 | } | ||
| 882 | |||
| 883 | static const struct wmi_device_id tlmi_id_table[] = { | ||
| 884 | { .guid_string = LENOVO_BIOS_SETTING_GUID }, | ||
| 885 | { } | ||
| 886 | }; | ||
| 887 | MODULE_DEVICE_TABLE(wmi, tlmi_id_table); | ||
| 888 | |||
| 889 | static struct wmi_driver tlmi_driver = { | ||
| 890 | .driver = { | ||
| 891 | .name = "think-lmi", | ||
| 892 | }, | ||
| 893 | .id_table = tlmi_id_table, | ||
| 894 | .probe = tlmi_probe, | ||
| 895 | .remove = tlmi_remove, | ||
| 896 | }; | ||
| 897 | |||
| 898 | MODULE_AUTHOR("Sugumaran L <slacshiminar@lenovo.com>"); | ||
| 899 | MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>"); | ||
| 900 | MODULE_AUTHOR("Corentin Chary <corentin.chary@gmail.com>"); | ||
| 901 | MODULE_DESCRIPTION("ThinkLMI Driver"); | ||
| 902 | MODULE_LICENSE("GPL"); | ||
| 903 | |||
| 904 | module_wmi_driver(tlmi_driver); | ||
| @@ -0,0 +1,72 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||
| 2 | |||
| 3 | #ifndef _THINK_LMI_H_ | ||
| 4 | #define _THINK_LMI_H_ | ||
| 5 | |||
| 6 | #include <linux/types.h> | ||
| 7 | |||
| 8 | #define TLMI_SETTINGS_COUNT 256 | ||
| 9 | #define TLMI_SETTINGS_MAXLEN 512 | ||
| 10 | #define TLMI_PWD_BUFSIZE 129 | ||
| 11 | #define TLMI_LANG_MAXLEN 4 | ||
| 12 | |||
| 13 | /* Possible error values */ | ||
| 14 | struct tlmi_err_codes { | ||
| 15 | const char *err_str; | ||
| 16 | int err_code; | ||
| 17 | }; | ||
| 18 | |||
| 19 | enum encoding_option { | ||
| 20 | TLMI_ENCODING_ASCII, | ||
| 21 | TLMI_ENCODING_SCANCODE, | ||
| 22 | }; | ||
| 23 | |||
| 24 | /* password configuration details */ | ||
| 25 | struct tlmi_pwdcfg { | ||
| 26 | uint32_t password_mode; | ||
| 27 | uint32_t password_state; | ||
| 28 | uint32_t min_length; | ||
| 29 | uint32_t max_length; | ||
| 30 | uint32_t supported_encodings; | ||
| 31 | uint32_t supported_keyboard; | ||
| 32 | }; | ||
| 33 | |||
| 34 | /* password setting details */ | ||
| 35 | struct tlmi_pwd_setting { | ||
| 36 | struct kobject kobj; | ||
| 37 | bool valid; | ||
| 38 | char password[TLMI_PWD_BUFSIZE]; | ||
| 39 | const char *pwd_type; | ||
| 40 | const char *role; | ||
| 41 | int minlen; | ||
| 42 | int maxlen; | ||
| 43 | enum encoding_option encoding; | ||
| 44 | char kbdlang[TLMI_LANG_MAXLEN]; | ||
| 45 | }; | ||
| 46 | |||
| 47 | /* Attribute setting details */ | ||
| 48 | struct tlmi_attr_setting { | ||
| 49 | struct kobject kobj; | ||
| 50 | int index; | ||
| 51 | char display_name[TLMI_SETTINGS_MAXLEN]; | ||
| 52 | char *possible_values; | ||
| 53 | }; | ||
| 54 | |||
| 55 | struct think_lmi { | ||
| 56 | struct wmi_device *wmi_device; | ||
| 57 | |||
| 58 | int settings_count; | ||
| 59 | bool can_set_bios_settings; | ||
| 60 | bool can_get_bios_selections; | ||
| 61 | bool can_set_bios_password; | ||
| 62 | bool can_get_password_settings; | ||
| 63 | |||
| 64 | struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; | ||
| 65 | struct device *class_dev; | ||
| 66 | struct kset *attribute_kset; | ||
| 67 | struct kset *authentication_kset; | ||
| 68 | struct tlmi_pwd_setting *pwd_admin; | ||
| 69 | struct tlmi_pwd_setting *pwd_power; | ||
| 70 | }; | ||
| 71 | |||
| 72 | #endif /* !_THINK_LMI_H_ */ | ||
| @@ -7938,7 +7938,7 @@ static int volume_write(char *buf) · 7937 unmodified lines | |||
| 7938 | continue; | 7938 | continue; |
| 7939 | } else if (sscanf(cmd, "level %u", &l) == 1 && | 7939 | } else if (sscanf(cmd, "level %u", &l) == 1 && |
| 7940 | l >= 0 && l <= TP_EC_VOLUME_MAX) { | 7940 | l >= 0 && l <= TP_EC_VOLUME_MAX) { |
| 7941 | new_level = l; | 7941 | new_level = l; |
| 7942 | continue; | 7942 | continue; |
| 7943 | } | 7943 | } |
| 7944 | } | 7944 | } |
| @@ -2831,6 +2831,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) · 2830 unmodified lines | |||
| 2831 | 2831 | ||
| 2832 | if (!dev->info_supported && !dev->system_event_supported) { | 2832 | if (!dev->info_supported && !dev->system_event_supported) { |
| 2833 | pr_warn("No hotkey query interface found\n"); | 2833 | pr_warn("No hotkey query interface found\n"); |
| 2834 | error = -EINVAL; | ||
| 2834 | goto err_remove_filter; | 2835 | goto err_remove_filter; |
| 2835 | } | 2836 | } |
| 2836 | 2837 | ||
| @@ -131,7 +131,7 @@ static const struct attribute_group haps_attr_group = { · 130 unmodified lines | |||
| 131 | */ | 131 | */ |
| 132 | static void toshiba_haps_notify(struct acpi_device *device, u32 event) | 132 | static void toshiba_haps_notify(struct acpi_device *device, u32 event) |
| 133 | { | 133 | { |
| 134 | pr_debug("Received event: 0x%x", event); | 134 | pr_debug("Received event: 0x%x\n", event); |
| 135 | 135 | ||
| 136 | acpi_bus_generate_netlink_event(device->pnp.device_class, | 136 | acpi_bus_generate_netlink_event(device->pnp.device_class, |
| 137 | dev_name(&device->dev), | 137 | dev_name(&device->dev), |
| @@ -299,6 +299,35 @@ static const struct ts_dmi_data estar_beauty_hd_data = { · 298 unmodified lines | |||
| 299 | .properties = estar_beauty_hd_props, | 299 | .properties = estar_beauty_hd_props, |
| 300 | }; | 300 | }; |
| 301 | 301 | ||
| 302 | /* Generic props + data for upside-down mounted GDIX1001 touchscreens */ | ||
| 303 | static const struct property_entry gdix1001_upside_down_props[] = { | ||
| 304 | PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), | ||
| 305 | PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), | ||
| 306 | { } | ||
| 307 | }; | ||
| 308 | |||
| 309 | static const struct ts_dmi_data gdix1001_00_upside_down_data = { | ||
| 310 | .acpi_name = "GDIX1001:00", | ||
| 311 | .properties = gdix1001_upside_down_props, | ||
| 312 | }; | ||
| 313 | |||
| 314 | static const struct ts_dmi_data gdix1001_01_upside_down_data = { | ||
| 315 | .acpi_name = "GDIX1001:01", | ||
| 316 | .properties = gdix1001_upside_down_props, | ||
| 317 | }; | ||
| 318 | |||
| 319 | static const struct property_entry glavey_tm800a550l_props[] = { | ||
| 320 | PROPERTY_ENTRY_STRING("firmware-name", "gt912-glavey-tm800a550l.fw"), | ||
| 321 | PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-glavey-tm800a550l.cfg"), | ||
| 322 | PROPERTY_ENTRY_U32("goodix,main-clk", 54), | ||
| 323 | { } | ||
| 324 | }; | ||
| 325 | |||
| 326 | static const struct ts_dmi_data glavey_tm800a550l_data = { | ||
| 327 | .acpi_name = "GDIX1001:00", | ||
| 328 | .properties = glavey_tm800a550l_props, | ||
| 329 | }; | ||
| 330 | |||
| 302 | static const struct property_entry gp_electronic_t701_props[] = { | 331 | static const struct property_entry gp_electronic_t701_props[] = { |
| 303 | PROPERTY_ENTRY_U32("touchscreen-size-x", 960), | 332 | PROPERTY_ENTRY_U32("touchscreen-size-x", 960), |
| 304 | PROPERTY_ENTRY_U32("touchscreen-size-y", 640), | 333 | PROPERTY_ENTRY_U32("touchscreen-size-y", 640), |
| @@ -942,7 +971,7 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 637 unmodified lines | |||
| 942 | }, | 971 | }, |
| 943 | }, | 972 | }, |
| 944 | { | 973 | { |
| 945 | /* Chuwi Hi10 Prus (CWI597) */ | 974 | /* Chuwi Hi10 Pro (CWI529) */ |
| 946 | .driver_data = (void *)&chuwi_hi10_pro_data, | 975 | .driver_data = (void *)&chuwi_hi10_pro_data, |
| 947 | .matches = { | 976 | .matches = { |
| 948 | DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), | 977 | DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), |
| @@ -1038,6 +1067,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 89 unmodified lines | |||
| 1038 | DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"), | 1067 | DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"), |
| 1039 | }, | 1068 | }, |
| 1040 | }, | 1069 | }, |
| 1070 | { /* Glavey TM800A550L */ | ||
| 1071 | .driver_data = (void *)&glavey_tm800a550l_data, | ||
| 1072 | .matches = { | ||
| 1073 | DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), | ||
| 1074 | DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), | ||
| 1075 | /* Above strings are too generic, also match on BIOS version */ | ||
| 1076 | DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"), | ||
| 1077 | }, | ||
| 1078 | }, | ||
| 1041 | { | 1079 | { |
| 1042 | /* GP-electronic T701 */ | 1080 | /* GP-electronic T701 */ |
| 1043 | .driver_data = (void *)&gp_electronic_t701_data, | 1081 | .driver_data = (void *)&gp_electronic_t701_data, |
| @@ -1330,6 +1368,24 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 286 unmodified lines | |||
| 1330 | DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"), | 1368 | DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"), |
| 1331 | }, | 1369 | }, |
| 1332 | }, | 1370 | }, |
| 1371 | { | ||
| 1372 | /* Teclast X89 (Android version / BIOS) */ | ||
| 1373 | .driver_data = (void *)&gdix1001_00_upside_down_data, | ||
| 1374 | .matches = { | ||
| 1375 | DMI_MATCH(DMI_BOARD_VENDOR, "WISKY"), | ||
| 1376 | DMI_MATCH(DMI_BOARD_NAME, "3G062i"), | ||
| 1377 | }, | ||
| 1378 | }, | ||
| 1379 | { | ||
| 1380 | /* Teclast X89 (Windows version / BIOS) */ | ||
| 1381 | .driver_data = (void *)&gdix1001_01_upside_down_data, | ||
| 1382 | .matches = { | ||
| 1383 | /* tPAD is too generic, also match on bios date */ | ||
| 1384 | DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), | ||
| 1385 | DMI_MATCH(DMI_BOARD_NAME, "tPAD"), | ||
| 1386 | DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"), | ||
| 1387 | }, | ||
| 1388 | }, | ||
| 1333 | { | 1389 | { |
| 1334 | /* Teclast X98 Plus II */ | 1390 | /* Teclast X98 Plus II */ |
| 1335 | .driver_data = (void *)&teclast_x98plus2_data, | 1391 | .driver_data = (void *)&teclast_x98plus2_data, |
| @@ -1338,6 +1394,19 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 2 unmodified lines | |||
| 1338 | DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"), | 1394 | DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"), |
| 1339 | }, | 1395 | }, |
| 1340 | }, | 1396 | }, |
| 1397 | { | ||
| 1398 | /* Teclast X98 Pro */ | ||
| 1399 | .driver_data = (void *)&gdix1001_00_upside_down_data, | ||
| 1400 | .matches = { | ||
| 1401 | /* | ||
| 1402 | * Only match BIOS date, because the manufacturers | ||
| 1403 | * BIOS does not report the board name at all | ||
| 1404 | * (sometimes)... | ||
| 1405 | */ | ||
| 1406 | DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), | ||
| 1407 | DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"), | ||
| 1408 | }, | ||
| 1409 | }, | ||
| 1341 | { | 1410 | { |
| 1342 | /* Trekstor Primebook C11 */ | 1411 | /* Trekstor Primebook C11 */ |
| 1343 | .driver_data = (void *)&trekstor_primebook_c11_data, | 1412 | .driver_data = (void *)&trekstor_primebook_c11_data, |
| @@ -1413,6 +1482,22 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 69 unmodified lines | |||
| 1413 | DMI_MATCH(DMI_PRODUCT_NAME, "VINGA Twizzle J116"), | 1482 | DMI_MATCH(DMI_PRODUCT_NAME, "VINGA Twizzle J116"), |
| 1414 | }, | 1483 | }, |
| 1415 | }, | 1484 | }, |
| 1485 | { | ||
| 1486 | /* "WinBook TW100" */ | ||
| 1487 | .driver_data = (void *)&gdix1001_00_upside_down_data, | ||
| 1488 | .matches = { | ||
| 1489 | DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), | ||
| 1490 | DMI_MATCH(DMI_PRODUCT_NAME, "TW100") | ||
| 1491 | } | ||
| 1492 | }, | ||
| 1493 | { | ||
| 1494 | /* WinBook TW700 */ | ||
| 1495 | .driver_data = (void *)&gdix1001_00_upside_down_data, | ||
| 1496 | .matches = { | ||
| 1497 | DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), | ||
| 1498 | DMI_MATCH(DMI_PRODUCT_NAME, "TW700") | ||
| 1499 | }, | ||
| 1500 | }, | ||
| 1416 | { | 1501 | { |
| 1417 | /* Yours Y8W81, same case and touchscreen as Chuwi Vi8 */ | 1502 | /* Yours Y8W81, same case and touchscreen as Chuwi Vi8 */ |
| 1418 | .driver_data = (void *)&chuwi_vi8_data, | 1503 | .driver_data = (void *)&chuwi_vi8_data, |
| @@ -778,7 +778,7 @@ static struct attribute *base_attrs[] = { · 777 unmodified lines | |||
| 778 | NULL, | 778 | NULL, |
| 779 | }; | 779 | }; |
| 780 | 780 | ||
| 781 | static struct attribute_group base_attr_group = { | 781 | static const struct attribute_group base_attr_group = { |
| 782 | .attrs = base_attrs | 782 | .attrs = base_attrs |
| 783 | }; | 783 | }; |
| 784 | 784 | ||
| @@ -823,7 +823,7 @@ static struct attribute *hubless_base_attrs[] = { · 38 unmodified lines | |||
| 823 | NULL, | 823 | NULL, |
| 824 | }; | 824 | }; |
| 825 | 825 | ||
| 826 | static struct attribute_group hubless_base_attr_group = { | 826 | static const struct attribute_group hubless_base_attr_group = { |
| 827 | .attrs = hubless_base_attrs | 827 | .attrs = hubless_base_attrs |
| 828 | }; | 828 | }; |
| 829 | 829 | ||
| @@ -0,0 +1,103 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 2 | /* | ||
| 3 | * Airplane mode button for AMD, HP & Xiaomi laptops | ||
| 4 | * | ||
| 5 | * Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com> | ||
| 6 | * Copyright (C) 2021 Advanced Micro Devices | ||
| 7 | */ | ||
| 8 | |||
| 9 | #include <linux/kernel.h> | ||
| 10 | #include <linux/module.h> | ||
| 11 | #include <linux/init.h> | ||
| 12 | #include <linux/input.h> | ||
| 13 | #include <linux/platform_device.h> | ||
| 14 | #include <linux/acpi.h> | ||
| 15 | #include <acpi/acpi_bus.h> | ||
| 16 | |||
| 17 | MODULE_LICENSE("GPL"); | ||
| 18 | MODULE_AUTHOR("Alex Hung"); | ||
| 19 | MODULE_ALIAS("acpi*:HPQ6001:*"); | ||
| 20 | MODULE_ALIAS("acpi*:WSTADEF:*"); | ||
| 21 | MODULE_ALIAS("acpi*:AMDI0051:*"); | ||
| 22 | |||
| 23 | static struct input_dev *wl_input_dev; | ||
| 24 | |||
| 25 | static const struct acpi_device_id wl_ids[] = { | ||
| 26 | {"HPQ6001", 0}, | ||
| 27 | {"WSTADEF", 0}, | ||
| 28 | {"AMDI0051", 0}, | ||
| 29 | {"", 0}, | ||
| 30 | }; | ||
| 31 | |||
| 32 | static int wireless_input_setup(void) | ||
| 33 | { | ||
| 34 | int err; | ||
| 35 | |||
| 36 | wl_input_dev = input_allocate_device(); | ||
| 37 | if (!wl_input_dev) | ||
| 38 | return -ENOMEM; | ||
| 39 | |||
| 40 | wl_input_dev->name = "Wireless hotkeys"; | ||
| 41 | wl_input_dev->phys = "hpq6001/input0"; | ||
| 42 | wl_input_dev->id.bustype = BUS_HOST; | ||
| 43 | wl_input_dev->evbit[0] = BIT(EV_KEY); | ||
| 44 | set_bit(KEY_RFKILL, wl_input_dev->keybit); | ||
| 45 | |||
| 46 | err = input_register_device(wl_input_dev); | ||
| 47 | if (err) | ||
| 48 | goto err_free_dev; | ||
| 49 | |||
| 50 | return 0; | ||
| 51 | |||
| 52 | err_free_dev: | ||
| 53 | input_free_device(wl_input_dev); | ||
| 54 | return err; | ||
| 55 | } | ||
| 56 | |||
| 57 | static void wireless_input_destroy(void) | ||
| 58 | { | ||
| 59 | input_unregister_device(wl_input_dev); | ||
| 60 | } | ||
| 61 | |||
| 62 | static void wl_notify(struct acpi_device *acpi_dev, u32 event) | ||
| 63 | { | ||
| 64 | if (event != 0x80) { | ||
| 65 | pr_info("Received unknown event (0x%x)\n", event); | ||
| 66 | return; | ||
| 67 | } | ||
| 68 | |||
| 69 | input_report_key(wl_input_dev, KEY_RFKILL, 1); | ||
| 70 | input_sync(wl_input_dev); | ||
| 71 | input_report_key(wl_input_dev, KEY_RFKILL, 0); | ||
| 72 | input_sync(wl_input_dev); | ||
| 73 | } | ||
| 74 | |||
| 75 | static int wl_add(struct acpi_device *device) | ||
| 76 | { | ||
| 77 | int err; | ||
| 78 | |||
| 79 | err = wireless_input_setup(); | ||
| 80 | if (err) | ||
| 81 | pr_err("Failed to setup hp wireless hotkeys\n"); | ||
| 82 | |||
| 83 | return err; | ||
| 84 | } | ||
| 85 | |||
| 86 | static int wl_remove(struct acpi_device *device) | ||
| 87 | { | ||
| 88 | wireless_input_destroy(); | ||
| 89 | return 0; | ||
| 90 | } | ||
| 91 | |||
| 92 | static struct acpi_driver wl_driver = { | ||
| 93 | .name = "wireless-hotkey", | ||
| 94 | .owner = THIS_MODULE, | ||
| 95 | .ids = wl_ids, | ||
| 96 | .ops = { | ||
| 97 | .add = wl_add, | ||
| 98 | .remove = wl_remove, | ||
| 99 | .notify = wl_notify, | ||
| 100 | }, | ||
| 101 | }; | ||
| 102 | |||
| 103 | module_acpi_driver(wl_driver); | ||
| @@ -1097,6 +1097,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c · 1096 unmodified lines | |||
| 1097 | #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) | 1097 | #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) |
| 1098 | bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, | 1098 | bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, |
| 1099 | struct acpi_resource_gpio **agpio); | 1099 | struct acpi_resource_gpio **agpio); |
| 1100 | bool acpi_gpio_get_io_resource(struct acpi_resource *ares, | ||
| 1101 | struct acpi_resource_gpio **agpio); | ||
| 1100 | int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); | 1102 | int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); |
| 1101 | #else | 1103 | #else |
| 1102 | static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, | 1104 | 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, · 1 unmodified line | |||
| 1104 | { | 1106 | { |
| 1105 | return false; | 1107 | return false; |
| 1106 | } | 1108 | } |
| 1109 | static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, | ||
| 1110 | struct acpi_resource_gpio **agpio) | ||
| 1111 | { | ||
| 1112 | return false; | ||
| 1113 | } | ||
| 1107 | static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, | 1114 | static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, |
| 1108 | const char *name, int index) | 1115 | const char *name, int index) |
| 1109 | { | 1116 | { |
| @@ -51,4 +51,29 @@ static inline int devm_delayed_work_autocancel(struct device *dev, · 50 unmodified lines | |||
| 51 | return devm_add_action(dev, devm_delayed_work_drop, w); | 51 | return devm_add_action(dev, devm_delayed_work_drop, w); |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | static inline void devm_work_drop(void *res) | ||
| 55 | { | ||
| 56 | cancel_work_sync(res); | ||
| 57 | } | ||
| 58 | |||
| 59 | /** | ||
| 60 | * devm_work_autocancel - Resource-managed work allocation | ||
| 61 | * @dev: Device which lifetime work is bound to | ||
| 62 | * @w: Work to be added (and automatically cancelled) | ||
| 63 | * @worker: Worker function | ||
| 64 | * | ||
| 65 | * Initialize work which is automatically cancelled when driver is detached. | ||
| 66 | * A few drivers need to queue work which must be cancelled before driver | ||
| 67 | * is detached to avoid accessing removed resources. | ||
| 68 | * devm_work_autocancel() can be used to omit the explicit | ||
| 69 | * cancelleation when driver is detached. | ||
| 70 | */ | ||
| 71 | static inline int devm_work_autocancel(struct device *dev, | ||
| 72 | struct work_struct *w, | ||
| 73 | work_func_t worker) | ||
| 74 | { | ||
| 75 | INIT_WORK(w, worker); | ||
| 76 | return devm_add_action(dev, devm_work_drop, w); | ||
| 77 | } | ||
| 78 | |||
| 54 | #endif | 79 | #endif |
| @@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, · 691 unmodified lines | |||
| 692 | const struct acpi_gpio_mapping *gpios); | 692 | const struct acpi_gpio_mapping *gpios); |
| 693 | void devm_acpi_dev_remove_driver_gpios(struct device *dev); | 693 | void devm_acpi_dev_remove_driver_gpios(struct device *dev); |
| 694 | 694 | ||
| 695 | struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label); | ||
| 696 | |||
| 695 | #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ | 697 | #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ |
| 696 | 698 | ||
| 697 | struct acpi_device; | 699 | struct acpi_device; |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | |||
| 6 | * managing access and communication to and from the SSAM EC, as well as main | 6 | * managing access and communication to and from the SSAM EC, as well as main |
| 7 | * communication structures and definitions. | 7 | * communication structures and definitions. |
| 8 | * | 8 | * |
| 9 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 9 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 10 | */ | 10 | */ |
| 11 | 11 | ||
| 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H | 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H |
| @@ -795,6 +795,20 @@ enum ssam_event_mask { · 782 unmodified lines | |||
| 795 | #define SSAM_EVENT_REGISTRY_REG \ | 795 | #define SSAM_EVENT_REGISTRY_REG \ |
| 796 | SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) | 796 | SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) |
| 797 | 797 | ||
| 798 | /** | ||
| 799 | * enum ssam_event_notifier_flags - Flags for event notifiers. | ||
| 800 | * @SSAM_EVENT_NOTIFIER_OBSERVER: | ||
| 801 | * The corresponding notifier acts as observer. Registering a notifier | ||
| 802 | * with this flag set will not attempt to enable any event. Equally, | ||
| 803 | * unregistering will not attempt to disable any event. Note that a | ||
| 804 | * notifier with this flag may not even correspond to a certain event at | ||
| 805 | * all, only to a specific event target category. Event matching will not | ||
| 806 | * be influenced by this flag. | ||
| 807 | */ | ||
| 808 | enum ssam_event_notifier_flags { | ||
| 809 | SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), | ||
| 810 | }; | ||
| 811 | |||
| 798 | /** | 812 | /** |
| 799 | * struct ssam_event_notifier - Notifier block for SSAM events. | 813 | * struct ssam_event_notifier - Notifier block for SSAM events. |
| 800 | * @base: The base notifier block with callback function and priority. | 814 | * @base: The base notifier block with callback function and priority. |
| @@ -803,6 +817,7 @@ enum ssam_event_mask { · 2 unmodified lines | |||
| 803 | * @event.id: ID specifying the event. | 817 | * @event.id: ID specifying the event. |
| 804 | * @event.mask: Flags determining how events are matched to the notifier. | 818 | * @event.mask: Flags determining how events are matched to the notifier. |
| 805 | * @event.flags: Flags used for enabling the event. | 819 | * @event.flags: Flags used for enabling the event. |
| 820 | * @flags: Notifier flags (see &enum ssam_event_notifier_flags). | ||
| 806 | */ | 821 | */ |
| 807 | struct ssam_event_notifier { | 822 | struct ssam_event_notifier { |
| 808 | struct ssam_notifier_block base; | 823 | struct ssam_notifier_block base; |
| @@ -813,6 +828,8 @@ struct ssam_event_notifier { · 4 unmodified lines | |||
| 813 | enum ssam_event_mask mask; | 828 | enum ssam_event_mask mask; |
| 814 | u8 flags; | 829 | u8 flags; |
| 815 | } event; | 830 | } event; |
| 831 | |||
| 832 | unsigned long flags; | ||
| 816 | }; | 833 | }; |
| 817 | 834 | ||
| 818 | int ssam_notifier_register(struct ssam_controller *ctrl, | 835 | int ssam_notifier_register(struct ssam_controller *ctrl, |
| @@ -821,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl, · 2 unmodified lines | |||
| 821 | int ssam_notifier_unregister(struct ssam_controller *ctrl, | 838 | int ssam_notifier_unregister(struct ssam_controller *ctrl, |
| 822 | struct ssam_event_notifier *n); | 839 | struct ssam_event_notifier *n); |
| 823 | 840 | ||
| 841 | int ssam_controller_event_enable(struct ssam_controller *ctrl, | ||
| 842 | struct ssam_event_registry reg, | ||
| 843 | struct ssam_event_id id, u8 flags); | ||
| 844 | |||
| 845 | int ssam_controller_event_disable(struct ssam_controller *ctrl, | ||
| 846 | struct ssam_event_registry reg, | ||
| 847 | struct ssam_event_id id, u8 flags); | ||
| 848 | |||
| 824 | #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ | 849 | #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ |
| @@ -7,7 +7,7 @@ · 6 unmodified lines | |||
| 7 | * Provides support for non-platform/non-ACPI SSAM clients via dedicated | 7 | * Provides support for non-platform/non-ACPI SSAM clients via dedicated |
| 8 | * subsystem. | 8 | * subsystem. |
| 9 | * | 9 | * |
| 10 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 10 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 11 | */ | 11 | */ |
| 12 | 12 | ||
| 13 | #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H | 13 | #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | |||
| 6 | * Surface System Aggregator Module (SSAM). Provides the interface for basic | 6 | * Surface System Aggregator Module (SSAM). Provides the interface for basic |
| 7 | * packet- and request-based communication with the SSAM EC via SSH. | 7 | * packet- and request-based communication with the SSAM EC via SSH. |
| 8 | * | 8 | * |
| 9 | * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com> | 9 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 10 | */ | 10 | */ |
| 11 | 11 | ||
| 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H | 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | |||
| 6 | * device. This device provides direct user-space access to the SSAM EC. | 6 | * device. This device provides direct user-space access to the SSAM EC. |
| 7 | * Intended for debugging and development. | 7 | * Intended for debugging and development. |
| 8 | * | 8 | * |
| 9 | * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com> | 9 | * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com> |
| 10 | */ | 10 | */ |
| 11 | 11 | ||
| 12 | #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H | 12 | #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H |
| @@ -73,6 +73,75 @@ struct ssam_cdev_request { · 60 unmodified lines | |||
| 73 | } response; | 73 | } response; |
| 74 | } __attribute__((__packed__)); | 74 | } __attribute__((__packed__)); |
| 75 | 75 | ||
| 76 | #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) | 76 | /** |
| 77 | * struct ssam_cdev_notifier_desc - Notifier descriptor. | ||
| 78 | * @priority: Priority value determining the order in which notifier | ||
| 79 | * callbacks will be called. A higher value means higher | ||
| 80 | * priority, i.e. the associated callback will be executed | ||
| 81 | * earlier than other (lower priority) callbacks. | ||
| 82 | * @target_category: The event target category for which this notifier should | ||
| 83 | * receive events. | ||
| 84 | * | ||
| 85 | * Specifies the notifier that should be registered or unregistered, | ||
| 86 | * specifically with which priority and for which target category of events. | ||
| 87 | */ | ||
| 88 | struct ssam_cdev_notifier_desc { | ||
| 89 | __s32 priority; | ||
| 90 | __u8 target_category; | ||
| 91 | } __attribute__((__packed__)); | ||
| 92 | |||
| 93 | /** | ||
| 94 | * struct ssam_cdev_event_desc - Event descriptor. | ||
| 95 | * @reg: Registry via which the event will be enabled/disabled. | ||
| 96 | * @reg.target_category: Target category for the event registry requests. | ||
| 97 | * @reg.target_id: Target ID for the event registry requests. | ||
| 98 | * @reg.cid_enable: Command ID for the event-enable request. | ||
| 99 | * @reg.cid_disable: Command ID for the event-disable request. | ||
| 100 | * @id: ID specifying the event. | ||
| 101 | * @id.target_category: Target category of the event source. | ||
| 102 | * @id.instance: Instance ID of the event source. | ||
| 103 | * @flags: Flags used for enabling the event. | ||
| 104 | * | ||
| 105 | * Specifies which event should be enabled/disabled and how to do that. | ||
| 106 | */ | ||
| 107 | struct ssam_cdev_event_desc { | ||
| 108 | struct { | ||
| 109 | __u8 target_category; | ||
| 110 | __u8 target_id; | ||
| 111 | __u8 cid_enable; | ||
| 112 | __u8 cid_disable; | ||
| 113 | } reg; | ||
| 114 | |||
| 115 | struct { | ||
| 116 | __u8 target_category; | ||
| 117 | __u8 instance; | ||
| 118 | } id; | ||
| 119 | |||
| 120 | __u8 flags; | ||
| 121 | } __attribute__((__packed__)); | ||
| 122 | |||
| 123 | /** | ||
| 124 | * struct ssam_cdev_event - SSAM event sent by the EC. | ||
| 125 | * @target_category: Target category of the event source. See &enum ssam_ssh_tc. | ||
| 126 | * @target_id: Target ID of the event source. | ||
| 127 | * @command_id: Command ID of the event. | ||
| 128 | * @instance_id: Instance ID of the event source. | ||
| 129 | * @length: Length of the event payload in bytes. | ||
| 130 | * @data: Event payload data. | ||
| 131 | */ | ||
| 132 | struct ssam_cdev_event { | ||
| 133 | __u8 target_category; | ||
| 134 | __u8 target_id; | ||
| 135 | __u8 command_id; | ||
| 136 | __u8 instance_id; | ||
| 137 | __u16 length; | ||
| 138 | __u8 data[]; | ||
| 139 | } __attribute__((__packed__)); | ||
| 140 | |||
| 141 | #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) | ||
| 142 | #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) | ||
| 143 | #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) | ||
| 144 | #define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) | ||
| 145 | #define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) | ||
| 77 | 146 | ||
| 78 | #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ | 147 | #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ |
| @@ -15,7 +15,7 @@ struct process_cmd_struct { · 14 unmodified lines | |||
| 15 | int arg; | 15 | int arg; |
| 16 | }; | 16 | }; |
| 17 | 17 | ||
| 18 | static const char *version_str = "v1.9"; | 18 | static const char *version_str = "v1.10"; |
| 19 | static const int supported_api_ver = 1; | 19 | static const int supported_api_ver = 1; |
| 20 | static struct isst_if_platform_info isst_platform_info; | 20 | static struct isst_if_platform_info isst_platform_info; |
| 21 | static char *progname; | 21 | static char *progname; |
| @@ -106,6 +106,22 @@ int is_skx_based_platform(void) · 84 unmodified lines | |||
| 106 | return 0; | 106 | return 0; |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | int is_spr_platform(void) | ||
| 110 | { | ||
| 111 | if (cpu_model == 0x8F) | ||
| 112 | return 1; | ||
| 113 | |||
| 114 | return 0; | ||
| 115 | } | ||
| 116 | |||
| 117 | int is_icx_platform(void) | ||
| 118 | { | ||
| 119 | if (cpu_model == 0x6A || cpu_model == 0x6C) | ||
| 120 | return 1; | ||
| 121 | |||
| 122 | return 0; | ||
| 123 | } | ||
| 124 | |||
| 109 | static int update_cpu_model(void) | 125 | static int update_cpu_model(void) |
| 110 | { | 126 | { |
| 111 | unsigned int ebx, ecx, edx; | 127 | unsigned int ebx, ecx, edx; |
| @@ -201,6 +201,7 @@ void isst_get_uncore_mem_freq(int cpu, int config_index, · 200 unmodified lines | |||
| 201 | { | 201 | { |
| 202 | unsigned int resp; | 202 | unsigned int resp; |
| 203 | int ret; | 203 | int ret; |
| 204 | |||
| 204 | ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, | 205 | ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, |
| 205 | 0, config_index, &resp); | 206 | 0, config_index, &resp); |
| 206 | if (ret) { | 207 | if (ret) { |
| @@ -209,6 +210,20 @@ void isst_get_uncore_mem_freq(int cpu, int config_index, · 2 unmodified lines | |||
| 209 | } | 210 | } |
| 210 | 211 | ||
| 211 | ctdp_level->mem_freq = resp & GENMASK(7, 0); | 212 | ctdp_level->mem_freq = resp & GENMASK(7, 0); |
| 213 | if (is_spr_platform()) { | ||
| 214 | ctdp_level->mem_freq *= 200; | ||
| 215 | } else if (is_icx_platform()) { | ||
| 216 | if (ctdp_level->mem_freq < 7) { | ||
| 217 | ctdp_level->mem_freq = (12 - ctdp_level->mem_freq) * 133.33 * 2 * 10; | ||
| 218 | ctdp_level->mem_freq /= 10; | ||
| 219 | if (ctdp_level->mem_freq % 10 > 5) | ||
| 220 | ctdp_level->mem_freq++; | ||
| 221 | } else { | ||
| 222 | ctdp_level->mem_freq = 0; | ||
| 223 | } | ||
| 224 | } else { | ||
| 225 | ctdp_level->mem_freq = 0; | ||
| 226 | } | ||
| 212 | debug_printf( | 227 | debug_printf( |
| 213 | "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", | 228 | "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", |
| 214 | cpu, config_index, resp, ctdp_level->mem_freq); | 229 | cpu, config_index, resp, ctdp_level->mem_freq); |
| @@ -446,7 +446,7 @@ void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level, · 445 unmodified lines | |||
| 446 | if (ctdp_level->mem_freq) { | 446 | if (ctdp_level->mem_freq) { |
| 447 | snprintf(header, sizeof(header), "mem-frequency(MHz)"); | 447 | snprintf(header, sizeof(header), "mem-frequency(MHz)"); |
| 448 | snprintf(value, sizeof(value), "%d", | 448 | snprintf(value, sizeof(value), "%d", |
| 449 | ctdp_level->mem_freq * DISP_FREQ_MULTIPLIER); | 449 | ctdp_level->mem_freq); |
| 450 | format_and_print(outf, level + 2, header, value); | 450 | format_and_print(outf, level + 2, header, value); |
| 451 | } | 451 | } |
| 452 | 452 | ||
| @@ -257,5 +257,7 @@ extern int get_cpufreq_base_freq(int cpu); · 256 unmodified lines | |||
| 257 | extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap); | 257 | extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap); |
| 258 | extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); | 258 | extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); |
| 259 | extern int is_skx_based_platform(void); | 259 | extern int is_skx_based_platform(void); |
| 260 | extern int is_spr_platform(void); | ||
| 261 | extern int is_icx_platform(void); | ||
| 260 | extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl); | 262 | extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl); |
| 261 | #endif | 263 | #endif |