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 | 197 | Drivers may emit a CHANGE uevent when a password is set or unset |
| 198 | 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 | 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 | 219 | What: /sys/class/firmware-attributes/*/attributes/pending_reboot |
| 204 | 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 | 1 | .. SPDX-License-Identifier: GPL-2.0+ |
| 2 | 2 | |
| 3 | −.. |u8| replace:: :c:type:`u8 <u8>` | |
| 4 | −.. |u16| replace:: :c:type:`u16 <u16>` | |
| 5 | 3 | .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>` |
| 6 | 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 | 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 | 22 | A small python library and scripts for accessing this interface can be found |
| 24 | 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 | 60 | Controller IOCTLs |
| 28 | 61 | ================= |
| @@ -45,9 +78,33 @@ The following IOCTLs are provided: · 16 unmodified lines | ||
| 45 | 78 | - ``REQUEST`` |
| 46 | 79 | - Perform synchronous SAM request. |
| 47 | 80 | |
| 81 | + * - ``0xA5`` | |
| 82 | + - ``2`` | |
| 83 | + - ``W`` | |
| 84 | + - ``NOTIF_REGISTER`` | |
| 85 | + - Register event notifier. | |
| 48 | 86 | |
| 49 | −``REQUEST`` | |
| 50 | −----------- | |
| 87 | + * - ``0xA5`` | |
| 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 | 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 | 139 | inside the IOCTL, but the request ``status`` member may still be negative in |
| 83 | 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 | 204 | .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h |
| @@ -325,7 +325,7 @@ Code Seq# Include File Comments · 324 unmodified lines | ||
| 325 | 325 | 0xA3 90-9F linux/dtlk.h |
| 326 | 326 | 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem |
| 327 | 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 | 329 | <mailto:luzmaximilian@gmail.com> |
| 330 | 330 | 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver |
| 331 | 331 | <mailto:luzmaximilian@gmail.com> |
| @@ -5187,7 +5187,14 @@ DELL WMI NOTIFICATIONS DRIVER · 5186 unmodified lines | ||
| 5187 | 5187 | M: Matthew Garrett <mjg59@srcf.ucam.org> |
| 5188 | 5188 | M: Pali Rohár <pali@kernel.org> |
| 5189 | 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 | 5199 | DELTA ST MEDIA DRIVER |
| 5193 | 5200 | M: Hugues Fruchet <hugues.fruchet@foss.st.com> |
| @@ -9397,6 +9404,11 @@ S: Maintained · 4203 unmodified lines | ||
| 9397 | 9404 | F: arch/x86/include/asm/intel_scu_ipc.h |
| 9398 | 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 | 9412 | INTEL SPEED SELECT TECHNOLOGY |
| 9401 | 9413 | M: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> |
| 9402 | 9414 | L: platform-driver-x86@vger.kernel.org |
| @@ -9417,7 +9429,7 @@ F: include/linux/firmware/intel/stratix10-smc.h · 14 unmodified lines | ||
| 9417 | 9429 | F: include/linux/firmware/intel/stratix10-svc-client.h |
| 9418 | 9430 | |
| 9419 | 9431 | INTEL TELEMETRY DRIVER |
| 9420 | −M: Rajneesh Bhardwaj <rajneesh.bhardwaj@linux.intel.com> | |
| 9432 | +M: Rajneesh Bhardwaj <irenic.rajneesh@gmail.com> | |
| 9421 | 9433 | M: "David E. Box" <david.e.box@linux.intel.com> |
| 9422 | 9434 | L: platform-driver-x86@vger.kernel.org |
| 9423 | 9435 | S: Maintained |
| @@ -12209,7 +12221,7 @@ M: Maximilian Luz <luzmaximilian@gmail.com> · 2785 unmodified lines | ||
| 12209 | 12221 | L: platform-driver-x86@vger.kernel.org |
| 12210 | 12222 | S: Maintained |
| 12211 | 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 | 12225 | F: Documentation/driver-api/surface_aggregator/ |
| 12214 | 12226 | F: drivers/platform/surface/aggregator/ |
| 12215 | 12227 | F: drivers/platform/surface/surface_acpi_notify.c |
| @@ -18215,6 +18227,13 @@ W: http://thinkwiki.org/wiki/Ibm-acpi · 5999 unmodified lines | ||
| 18215 | 18227 | T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git |
| 18216 | 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 | 18237 | THUNDERBOLT DMA TRAFFIC TEST DRIVER |
| 18219 | 18238 | M: Isaac Hazan <isaac.hazan@intel.com> |
| 18220 | 18239 | L: linux-usb@vger.kernel.org |
| @@ -52,7 +52,7 @@ endif # PMIC_OPREGION · 51 unmodified lines | ||
| 52 | 52 | |
| 53 | 53 | config TPS68470_PMIC_OPREGION |
| 54 | 54 | bool "ACPI operation region support for TPS68470 PMIC" |
| 55 | − depends on MFD_TPS68470 | |
| 55 | + depends on INTEL_SKL_INT3472 | |
| 56 | 56 | help |
| 57 | 57 | This config adds ACPI operation region support for TI TPS68470 PMIC. |
| 58 | 58 | TPS68470 device is an advanced power management unit that powers |
| @@ -6,6 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | // Chanwoo Choi <cw00.choi@samsung.com> |
| 7 | 7 | // Krzysztof Kozlowski <krzk@kernel.org> |
| 8 | 8 | |
| 9 | +#include <linux/devm-helpers.h> | |
| 9 | 10 | #include <linux/kernel.h> |
| 10 | 11 | #include <linux/module.h> |
| 11 | 12 | #include <linux/i2c.h> |
| @@ -673,7 +674,10 @@ static int max14577_muic_probe(struct platform_device *pdev) · 661 unmodified lines | ||
| 673 | 674 | platform_set_drvdata(pdev, info); |
| 674 | 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 | 682 | switch (max14577->dev_type) { |
| 679 | 683 | case MAXIM_DEVICE_TYPE_MAX77836: |
| @@ -766,15 +770,6 @@ static int max14577_muic_probe(struct platform_device *pdev) · 86 unmodified lines | ||
| 766 | 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 | 773 | static const struct platform_device_id max14577_muic_id[] = { |
| 779 | 774 | { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, |
| 780 | 775 | { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, |
| @@ -797,7 +792,6 @@ static struct platform_driver max14577_muic_driver = { · 16 unmodified lines | ||
| 797 | 792 | .of_match_table = of_max14577_muic_dt_match, |
| 798 | 793 | }, |
| 799 | 794 | .probe = max14577_muic_probe, |
| 800 | − .remove = max14577_muic_remove, | |
| 801 | 795 | .id_table = max14577_muic_id, |
| 802 | 796 | }; |
| 803 | 797 | |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | ||
| 5 | 5 | // Copyright (C) 2012 Samsung Electrnoics |
| 6 | 6 | // Chanwoo Choi <cw00.choi@samsung.com> |
| 7 | 7 | |
| 8 | +#include <linux/devm-helpers.h> | |
| 8 | 9 | #include <linux/kernel.h> |
| 9 | 10 | #include <linux/module.h> |
| 10 | 11 | #include <linux/i2c.h> |
| @@ -1127,7 +1128,10 @@ static int max77693_muic_probe(struct platform_device *pdev) · 1116 unmodified lines | ||
| 1127 | 1128 | platform_set_drvdata(pdev, info); |
| 1128 | 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 | 1136 | /* Support irq domain for MAX77693 MUIC device */ |
| 1133 | 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 | 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 | 1261 | static struct platform_driver max77693_muic_driver = { |
| 1268 | 1262 | .driver = { |
| 1269 | 1263 | .name = DEV_NAME, |
| 1270 | 1264 | }, |
| 1271 | 1265 | .probe = max77693_muic_probe, |
| 1272 | − .remove = max77693_muic_remove, | |
| 1273 | 1266 | }; |
| 1274 | 1267 | |
| 1275 | 1268 | module_platform_driver(max77693_muic_driver); |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | ||
| 5 | 5 | // Copyright (C) 2012 Samsung Electronics |
| 6 | 6 | // Donggeun Kim <dg77.kim@samsung.com> |
| 7 | 7 | |
| 8 | +#include <linux/devm-helpers.h> | |
| 8 | 9 | #include <linux/kernel.h> |
| 9 | 10 | #include <linux/module.h> |
| 10 | 11 | #include <linux/i2c.h> |
| @@ -650,27 +651,30 @@ static int max8997_muic_probe(struct platform_device *pdev) · 639 unmodified lines | ||
| 650 | 651 | mutex_init(&info->mutex); |
| 651 | 652 | |
| 652 | 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 | 659 | for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { |
| 655 | 660 | struct max8997_muic_irq *muic_irq = &muic_irqs[i]; |
| 656 | 661 | unsigned int virq = 0; |
| 657 | 662 | |
| 658 | 663 | virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq); |
| 659 | − if (!virq) { | |
| 660 | − ret = -EINVAL; | |
| 661 | − goto err_irq; | |
| 662 | − } | |
| 664 | + if (!virq) | |
| 665 | + return -EINVAL; | |
| 666 | + | |
| 663 | 667 | muic_irq->virq = virq; |
| 664 | 668 | |
| 665 | − ret = request_threaded_irq(virq, NULL, | |
| 666 | − max8997_muic_irq_handler, | |
| 667 | − IRQF_NO_SUSPEND, | |
| 668 | − muic_irq->name, info); | |
| 669 | + ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, | |
| 670 | + max8997_muic_irq_handler, | |
| 671 | + IRQF_NO_SUSPEND, | |
| 672 | + muic_irq->name, info); | |
| 669 | 673 | if (ret) { |
| 670 | 674 | dev_err(&pdev->dev, |
| 671 | 675 | "failed: irq request (IRQ: %d, error :%d)\n", |
| 672 | 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 | 682 | info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); |
| 679 | 683 | if (IS_ERR(info->edev)) { |
| 680 | 684 | dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); |
| 681 | − ret = PTR_ERR(info->edev); | |
| 682 | − goto err_irq; | |
| 685 | + return PTR_ERR(info->edev); | |
| 683 | 686 | } |
| 684 | 687 | |
| 685 | 688 | ret = devm_extcon_dev_register(&pdev->dev, info->edev); |
| 686 | 689 | if (ret) { |
| 687 | 690 | dev_err(&pdev->dev, "failed to register extcon device\n"); |
| 688 | − goto err_irq; | |
| 691 | + return ret; | |
| 689 | 692 | } |
| 690 | 693 | |
| 691 | 694 | if (pdata && pdata->muic_pdata) { |
| @@ -756,23 +759,6 @@ static int max8997_muic_probe(struct platform_device *pdev) · 64 unmodified lines | ||
| 756 | 759 | delay_jiffies); |
| 757 | 760 | |
| 758 | 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 | 764 | static struct platform_driver max8997_muic_driver = { |
| @@ -780,7 +766,6 @@ static struct platform_driver max8997_muic_driver = { · 1 unmodified line | ||
| 780 | 766 | .name = DEV_NAME, |
| 781 | 767 | }, |
| 782 | 768 | .probe = max8997_muic_probe, |
| 783 | − .remove = max8997_muic_remove, | |
| 784 | 769 | }; |
| 785 | 770 | |
| 786 | 771 | module_platform_driver(max8997_muic_driver); |
| @@ -1367,7 +1367,7 @@ config GPIO_TPS65912 · 1366 unmodified lines | ||
| 1367 | 1367 | |
| 1368 | 1368 | config GPIO_TPS68470 |
| 1369 | 1369 | bool "TPS68470 GPIO" |
| 1370 | − depends on MFD_TPS68470 | |
| 1370 | + depends on INTEL_SKL_INT3472 | |
| 1371 | 1371 | help |
| 1372 | 1372 | Select this option to enable GPIO driver for the TPS68470 |
| 1373 | 1373 | chip family. |
| @@ -339,8 +339,6 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) · 338 unmodified lines | ||
| 339 | 339 | if (!cg) |
| 340 | 340 | return -ENOMEM; |
| 341 | 341 | |
| 342 | − platform_set_drvdata(pdev, cg); | |
| 343 | − | |
| 344 | 342 | mutex_init(&cg->buslock); |
| 345 | 343 | cg->chip.label = KBUILD_MODNAME; |
| 346 | 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 | 370 | return retval; |
| 373 | 371 | } |
| 374 | 372 | |
| 375 | − retval = 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; | |
| 373 | + return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); | |
| 382 | 374 | } |
| 383 | 375 | |
| 384 | 376 | static struct platform_driver crystalcove_gpio_driver = { |
| @@ -99,19 +99,14 @@ struct wcove_gpio { · 98 unmodified lines | ||
| 99 | 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 | 106 | if (gpio >= WCOVE_GPIO_NUM) |
| 107 | 107 | return -EOPNOTSUPP; |
| 108 | 108 | |
| 109 | − if (reg_type == CTRL_IN) | |
| 110 | − reg = GPIO_IN_CTRL_BASE + gpio; | |
| 111 | − else | |
| 112 | − reg = GPIO_OUT_CTRL_BASE + gpio; | |
| 113 | − | |
| 114 | − return reg; | |
| 109 | + return reg + gpio; | |
| 115 | 110 | } |
| 116 | 111 | |
| 117 | 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 | 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 | 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 | 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 | 139 | int reg = to_reg(gpio, CTRL_IN); |
| 145 | 140 | |
| 146 | − if (reg < 0) | |
| 147 | − return; | |
| 148 | − | |
| 149 | 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 | 241 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 250 | 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 | 246 | return 0; |
| 254 | 247 | |
| 255 | 248 | switch (type) { |
| @@ -286,7 +279,7 @@ static void wcove_bus_sync_unlock(struct irq_data *data) · 30 unmodified lines | ||
| 286 | 279 | { |
| 287 | 280 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 288 | 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 | 284 | if (wg->update & UPDATE_IRQ_TYPE) |
| 292 | 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 | 295 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 303 | 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 | 300 | return; |
| 307 | 301 | |
| 308 | 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 | 308 | struct gpio_chip *chip = irq_data_get_irq_chip_data(data); |
| 315 | 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 | 313 | return; |
| 319 | 314 | |
| 320 | 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 | 364 | return IRQ_HANDLED; |
| 370 | 365 | } |
| 371 | 366 | |
| 372 | −static void wcove_gpio_dbg_show(struct seq_file *s, | |
| 373 | − struct gpio_chip *chip) | |
| 367 | +static void wcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) | |
| 374 | 368 | { |
| 375 | 369 | unsigned int ctlo, ctli, irq_mask, irq_status; |
| 376 | 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 | 373 | for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) { |
| 380 | 374 | ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo); |
| 381 | 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 | 381 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask); |
| 383 | 382 | ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status); |
| 384 | 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 | 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 | 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 | 159 | static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) |
| 132 | 160 | { |
| 133 | 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 | 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 | 222 | static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, |
| 172 | 223 | struct acpi_gpio_event *event) |
| 173 | 224 | { |
| @@ -178,51 +178,6 @@ static const unsigned long goodix_irq_flags[] = { · 177 unmodified lines | ||
| 178 | 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 | 181 | static const struct dmi_system_id nine_bytes_report[] = { |
| 227 | 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 | 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 | 1081 | if (dmi_check_system(nine_bytes_report)) { |
| 1134 | 1082 | ts->contact_size = 9; |
| 1135 | 1083 | |
| @@ -1499,24 +1499,6 @@ config MFD_TPS65217 · 1498 unmodified lines | ||
| 1499 | 1499 | This driver can also be built as a module. If so, the module |
| 1500 | 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 | 1502 | config MFD_TI_LP873X |
| 1521 | 1503 | tristate "TI LP873X Power Management IC" |
| 1522 | 1504 | depends on I2C |
| @@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o · 104 unmodified lines | ||
| 105 | 105 | obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o |
| 106 | 106 | obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o |
| 107 | 107 | obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o |
| 108 | −obj-$(CONFIG_MFD_TPS68470) += tps68470.o | |
| 109 | 108 | obj-$(CONFIG_MFD_TPS80031) += tps80031.o |
| 110 | 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 | 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 | 4 | menuconfig SURFACE_AGGREGATOR |
| 5 | 5 | tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" |
| @@ -1,5 +1,5 @@ | ||
| 1 | 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 | 4 | # For include/trace/define_trace.h to include trace.h |
| 5 | 5 | CFLAGS_core.o = -I$(src) |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #include <linux/device.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_BUS_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 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 | 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 | 436 | * ssam_nf_refcount_empty() - Test if the notification system has any |
| 412 | 437 | * enabled/active events. |
| @@ -2122,14 +2147,123 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) · 1709 unmodified lines | ||
| 2122 | 2147 | |
| 2123 | 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 | 2254 | * ssam_notifier_register() - Register an event notifier. |
| 2127 | 2255 | * @ctrl: The controller to register the notifier on. |
| 2128 | 2256 | * @n: The event notifier to register. |
| 2129 | 2257 | * |
| 2130 | − * Register an event notifier and increment the usage counter of the | |
| 2131 | − * associated SAM event. If the event was previously not enabled, it will be | |
| 2132 | − * enabled during this call. | |
| 2258 | + * Register an event notifier. Increment the usage counter of the associated | |
| 2259 | + * SAM event if the notifier is not marked as an observer. If the event is not | |
| 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 | 2268 | * Return: Returns zero on success, %-ENOSPC if there have already been |
| 2135 | 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 | 2272 | * for the specific associated event, returns the status of the event-enable |
| 2139 | 2273 | * EC-command. |
| 2140 | 2274 | */ |
| 2141 | −int ssam_notifier_register(struct ssam_controller *ctrl, | |
| 2142 | − struct ssam_event_notifier *n) | |
| 2275 | +int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) | |
| 2143 | 2276 | { |
| 2144 | 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 | 2279 | struct ssam_nf_head *nf_head; |
| 2147 | 2280 | struct ssam_nf *nf; |
| 2148 | 2281 | int status; |
| @@ -2155,44 +2288,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, · 6 unmodified lines | ||
| 2155 | 2288 | |
| 2156 | 2289 | mutex_lock(&nf->lock); |
| 2157 | 2290 | |
| 2158 | − entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); | |
| 2159 | − if (IS_ERR(entry)) { | |
| 2160 | − mutex_unlock(&nf->lock); | |
| 2161 | − return PTR_ERR(entry); | |
| 2291 | + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { | |
| 2292 | + entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); | |
| 2293 | + if (IS_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 | 2299 | status = ssam_nfblk_insert(nf_head, &n->base); |
| 2169 | 2300 | if (status) { |
| 2170 | − entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); | |
| 2171 | − if (entry->refcount == 0) | |
| 2172 | − kfree(entry); | |
| 2301 | + if (entry) | |
| 2302 | + ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); | |
| 2173 | 2303 | |
| 2174 | 2304 | mutex_unlock(&nf->lock); |
| 2175 | 2305 | return status; |
| 2176 | 2306 | } |
| 2177 | 2307 | |
| 2178 | − if (entry->refcount == 1) { | |
| 2179 | − status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, | |
| 2180 | − n->event.flags); | |
| 2308 | + if (entry) { | |
| 2309 | + status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); | |
| 2181 | 2310 | if (status) { |
| 2182 | 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 | 2313 | mutex_unlock(&nf->lock); |
| 2185 | 2314 | synchronize_srcu(&nf_head->srcu); |
| 2186 | 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 | 2319 | mutex_unlock(&nf->lock); |
| @@ -2205,17 +2326,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); · 6 unmodified lines | ||
| 2205 | 2326 | * @ctrl: The controller the notifier has been registered on. |
| 2206 | 2327 | * @n: The event notifier to unregister. |
| 2207 | 2328 | * |
| 2208 | − * Unregister an event notifier and decrement the usage counter of the | |
| 2209 | − * associated SAM event. If the usage counter reaches zero, the event will be | |
| 2210 | − * disabled. | |
| 2329 | + * Unregister an event notifier. Decrement the usage counter of the associated | |
| 2330 | + * SAM event if the notifier is not marked as an observer. If the usage counter | |
| 2331 | + * reaches zero, the event will be disabled. | |
| 2211 | 2332 | * |
| 2212 | 2333 | * Return: Returns zero on success, %-ENOENT if the given notifier block has |
| 2213 | 2334 | * not been registered on the controller. If the given notifier block was the |
| 2214 | 2335 | * last one associated with its specific event, returns the status of the |
| 2215 | 2336 | * event-disable EC-command. |
| 2216 | 2337 | */ |
| 2217 | −int ssam_notifier_unregister(struct ssam_controller *ctrl, | |
| 2218 | − struct ssam_event_notifier *n) | |
| 2338 | +int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) | |
| 2219 | 2339 | { |
| 2220 | 2340 | u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); |
| 2221 | 2341 | struct ssam_nf_refcount_entry *entry; |
| @@ -2236,33 +2356,24 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, · 14 unmodified lines | ||
| 2236 | 2356 | return -ENOENT; |
| 2237 | 2357 | } |
| 2238 | 2358 | |
| 2239 | − entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); | |
| 2240 | − if (WARN_ON(!entry)) { | |
| 2241 | − /* | |
| 2242 | − * If this does not return an entry, there's a logic error | |
| 2243 | − * somewhere: The notifier block is registered, but the event | |
| 2244 | − * refcount entry is not there. Remove the notifier block | |
| 2245 | − * anyways. | |
| 2246 | − */ | |
| 2247 | − status = -ENOENT; | |
| 2248 | − goto remove; | |
| 2249 | − } | |
| 2250 | − | |
| 2251 | − ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", | |
| 2252 | − n->event.reg.target_category, n->event.id.target_category, | |
| 2253 | − n->event.id.instance, entry->refcount); | |
| 2254 | − | |
| 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 | − } | |
| 2359 | + /* | |
| 2360 | + * If this is an observer notifier, do not attempt to disable the | |
| 2361 | + * event, just remove it. | |
| 2362 | + */ | |
| 2363 | + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { | |
| 2364 | + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); | |
| 2365 | + if (WARN_ON(!entry)) { | |
| 2366 | + /* | |
| 2367 | + * If this does not return an entry, there's a logic | |
| 2368 | + * error somewhere: The notifier block is registered, | |
| 2369 | + * but the event refcount entry is not there. Remove | |
| 2370 | + * the notifier block anyways. | |
| 2371 | + */ | |
| 2372 | + status = -ENOENT; | |
| 2373 | + goto remove; | |
| 2374 | + } | |
| 2261 | 2375 | |
| 2262 | − if (entry->refcount == 0) { | |
| 2263 | − status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, | |
| 2264 | − n->event.flags); | |
| 2265 | − kfree(entry); | |
| 2376 | + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); | |
| 2266 | 2377 | } |
| 2267 | 2378 | |
| 2268 | 2379 | remove: |
| @@ -2274,6 +2385,105 @@ remove: · 5 unmodified lines | ||
| 2274 | 2385 | } |
| 2275 | 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 | 2488 | * ssam_notifier_disable_registered() - Disable events for all registered |
| 2279 | 2489 | * notifiers. |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H |
| @@ -7,7 +7,7 @@ · 6 unmodified lines | ||
| 7 | 7 | * Handles communication via requests as well as enabling, disabling, and |
| 8 | 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 | 13 | #include <linux/acpi.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #include <asm/unaligned.h> |
| @@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) · 1558 unmodified lines | ||
| 1567 | 1567 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); |
| 1568 | 1568 | |
| 1569 | 1569 | atomic_dec(&ptl->pending.count); |
| 1570 | − list_del(&p->pending_node); | |
| 1571 | − | |
| 1572 | − list_add_tail(&p->pending_node, &claimed); | |
| 1570 | + list_move_tail(&p->pending_node, &claimed); | |
| 1573 | 1571 | } |
| 1574 | 1572 | |
| 1575 | 1573 | spin_unlock(&ptl->pending.lock); |
| @@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) · 381 unmodified lines | ||
| 1957 | 1955 | smp_mb__before_atomic(); |
| 1958 | 1956 | clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); |
| 1959 | 1957 | |
| 1960 | − list_del(&p->queue_node); | |
| 1961 | − list_add_tail(&p->queue_node, &complete_q); | |
| 1958 | + list_move_tail(&p->queue_node, &complete_q); | |
| 1962 | 1959 | } |
| 1963 | 1960 | spin_unlock(&ptl->queue.lock); |
| 1964 | 1961 | |
| @@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) · 5 unmodified lines | ||
| 1970 | 1967 | smp_mb__before_atomic(); |
| 1971 | 1968 | clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); |
| 1972 | 1969 | |
| 1973 | − list_del(&p->pending_node); | |
| 1974 | − list_add_tail(&p->pending_node, &complete_q); | |
| 1970 | + list_move_tail(&p->pending_node, &complete_q); | |
| 1975 | 1971 | } |
| 1976 | 1972 | atomic_set(&ptl->pending.count, 0); |
| 1977 | 1973 | spin_unlock(&ptl->pending.lock); |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #include <asm/unaligned.h> |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #include <asm/unaligned.h> |
| @@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) · 854 unmodified lines | ||
| 863 | 863 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); |
| 864 | 864 | |
| 865 | 865 | atomic_dec(&rtl->pending.count); |
| 866 | − list_del(&r->node); | |
| 867 | − | |
| 868 | − list_add_tail(&r->node, &claimed); | |
| 866 | + list_move_tail(&r->node, &claimed); | |
| 869 | 867 | } |
| 870 | 868 | spin_unlock(&rtl->pending.lock); |
| 871 | 869 | |
| @@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) · 332 unmodified lines | ||
| 1204 | 1202 | smp_mb__before_atomic(); |
| 1205 | 1203 | clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); |
| 1206 | 1204 | |
| 1207 | − list_del(&r->node); | |
| 1208 | − list_add_tail(&r->node, &claimed); | |
| 1205 | + list_move_tail(&r->node, &claimed); | |
| 1209 | 1206 | } |
| 1210 | 1207 | spin_unlock(&rtl->queue.lock); |
| 1211 | 1208 | |
| @@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) · 26 unmodified lines | ||
| 1238 | 1235 | smp_mb__before_atomic(); |
| 1239 | 1236 | clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); |
| 1240 | 1237 | |
| 1241 | − list_del(&r->node); | |
| 1242 | − list_add_tail(&r->node, &claimed); | |
| 1238 | + list_move_tail(&r->node, &claimed); | |
| 1243 | 1239 | } |
| 1244 | 1240 | spin_unlock(&rtl->pending.lock); |
| 1245 | 1241 | } |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H |
| @@ -2,7 +2,7 @@ · 1 unmodified line | ||
| 2 | 2 | /* |
| 3 | 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 | 8 | #undef TRACE_SYSTEM |
| @@ -3,29 +3,69 @@ · 2 unmodified lines | ||
| 3 | 3 | * Provides user-space access to the SSAM EC via the /dev/surface/aggregator |
| 4 | 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 | 9 | #include <linux/fs.h> |
| 10 | +#include <linux/ioctl.h> | |
| 10 | 11 | #include <linux/kernel.h> |
| 12 | +#include <linux/kfifo.h> | |
| 11 | 13 | #include <linux/kref.h> |
| 12 | 14 | #include <linux/miscdevice.h> |
| 13 | 15 | #include <linux/module.h> |
| 14 | 16 | #include <linux/platform_device.h> |
| 17 | +#include <linux/poll.h> | |
| 15 | 18 | #include <linux/rwsem.h> |
| 16 | 19 | #include <linux/slab.h> |
| 17 | 20 | #include <linux/uaccess.h> |
| 21 | +#include <linux/vmalloc.h> | |
| 18 | 22 | |
| 19 | 23 | #include <linux/surface_aggregator/cdev.h> |
| 20 | 24 | #include <linux/surface_aggregator/controller.h> |
| 25 | +#include <linux/surface_aggregator/serial_hub.h> | |
| 21 | 26 | |
| 22 | 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 | 36 | struct ssam_cdev { |
| 25 | 37 | struct kref kref; |
| 26 | 38 | struct rw_semaphore lock; |
| 39 | + | |
| 40 | + struct device *dev; | |
| 27 | 41 | struct ssam_controller *ctrl; |
| 28 | 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 | 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 | 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; | |
| 53 | − struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); | |
| 95 | + struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); | |
| 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); | |
| 56 | − return stream_open(inode, filp); | |
| 128 | + /* | |
| 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); | |
| 62 | − return 0; | |
| 137 | + const u16 rqid = ssh_tc_to_rqid(tc); | |
| 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 | 257 | struct ssam_cdev_request rqst; |
| 69 | 258 | struct ssam_request spec = {}; |
| 70 | 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 | 261 | void __user *rspdata; |
| 73 | 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 | 266 | ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); |
| 77 | 267 | if (ret) |
| 78 | 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 | 344 | /* Perform request. */ |
| 155 | − status = ssam_request_sync(cdev->ctrl, &spec, &rsp); | |
| 345 | + status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); | |
| 156 | 346 | if (status) |
| 157 | 347 | goto out; |
| 158 | 348 | |
| @@ -177,48 +367,315 @@ out: · 18 unmodified lines | ||
| 177 | 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 | 528 | unsigned long arg) |
| 182 | 529 | { |
| 530 | + lockdep_assert_held_read(&client->cdev->lock); | |
| 531 | + | |
| 183 | 532 | switch (cmd) { |
| 184 | 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 | 550 | default: |
| 188 | 551 | return -ENOTTY; |
| 189 | 552 | } |
| 190 | 553 | } |
| 191 | 554 | |
| 192 | −static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, | |
| 193 | − unsigned long arg) | |
| 555 | +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |
| 194 | 556 | { |
| 195 | − struct ssam_cdev *cdev = file->private_data; | |
| 557 | + struct ssam_cdev_client *client = file->private_data; | |
| 196 | 558 | long status; |
| 197 | 559 | |
| 198 | 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 | 582 | if (down_read_killable(&cdev->lock)) |
| 200 | 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 | 587 | up_read(&cdev->lock); |
| 204 | 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 | 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 | 664 | static const struct file_operations ssam_controller_fops = { |
| 214 | 665 | .owner = THIS_MODULE, |
| 215 | 666 | .open = ssam_cdev_device_open, |
| 216 | 667 | .release = ssam_cdev_device_release, |
| 668 | + .read = ssam_cdev_read, | |
| 669 | + .poll = ssam_cdev_poll, | |
| 670 | + .fasync = ssam_cdev_fasync, | |
| 217 | 671 | .unlocked_ioctl = ssam_cdev_device_ioctl, |
| 218 | 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 | 679 | static int ssam_dbg_device_probe(struct platform_device *pdev) |
| 223 | 680 | { |
| 224 | 681 | struct ssam_controller *ctrl; |
| @@ -236,6 +693,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) · 11 unmodified lines | ||
| 236 | 693 | kref_init(&cdev->kref); |
| 237 | 694 | init_rwsem(&cdev->lock); |
| 238 | 695 | cdev->ctrl = ctrl; |
| 696 | + cdev->dev = &pdev->dev; | |
| 239 | 697 | |
| 240 | 698 | cdev->mdev.parent = &pdev->dev; |
| 241 | 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 | 701 | cdev->mdev.nodename = "surface/aggregator"; |
| 244 | 702 | cdev->mdev.fops = &ssam_controller_fops; |
| 245 | 703 | |
| 704 | + init_rwsem(&cdev->client_lock); | |
| 705 | + INIT_LIST_HEAD(&cdev->client_list); | |
| 706 | + | |
| 246 | 707 | status = misc_register(&cdev->mdev); |
| 247 | 708 | if (status) { |
| 248 | 709 | kfree(cdev); |
| @@ -256,8 +717,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) · 7 unmodified lines | ||
| 256 | 717 | static int ssam_dbg_device_remove(struct platform_device *pdev) |
| 257 | 718 | { |
| 258 | 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 | 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 | 752 | down_write(&cdev->lock); |
| 268 | 753 | cdev->ctrl = NULL; |
| 754 | + cdev->dev = NULL; | |
| 269 | 755 | up_write(&cdev->lock); |
| 270 | 756 | |
| 757 | + misc_deregister(&cdev->mdev); | |
| 758 | + | |
| 271 | 759 | ssam_cdev_put(cdev); |
| 272 | 760 | return 0; |
| 273 | 761 | } |
| @@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { · 118 unmodified lines | ||
| 119 | 119 | .parent = &ssam_node_hub_base, |
| 120 | 120 | }; |
| 121 | 121 | |
| 122 | −/* Devices for Surface Book 2. */ | |
| 123 | −static const struct software_node *ssam_node_group_sb2[] = { | |
| 122 | +/* | |
| 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 | 129 | &ssam_node_root, |
| 125 | 130 | &ssam_node_tmp_pprof, |
| 126 | 131 | NULL, |
| @@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { · 15 unmodified lines | ||
| 142 | 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 | 150 | /* Devices for Surface Laptop 3 and 4. */ |
| 160 | 151 | static const struct software_node *ssam_node_group_sl3[] = { |
| 161 | 152 | &ssam_node_root, |
| @@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { · 15 unmodified lines | ||
| 177 | 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 | 171 | /* Devices for Surface Pro 7 and Surface Pro 7+. */ |
| 195 | 172 | static const struct software_node *ssam_node_group_sp7[] = { |
| 196 | 173 | &ssam_node_root, |
| @@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { · 298 unmodified lines | ||
| 495 | 472 | |
| 496 | 473 | static const struct acpi_device_id ssam_platform_hub_match[] = { |
| 497 | 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 | 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 | 480 | /* Surface Pro 7 */ |
| 504 | 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 | 484 | { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, |
| 508 | 485 | |
| 509 | 486 | /* Surface Book 2 */ |
| 510 | − { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, | |
| 487 | + { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, | |
| 511 | 488 | |
| 512 | 489 | /* Surface Book 3 */ |
| 513 | 490 | { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, |
| 514 | 491 | |
| 515 | 492 | /* Surface Laptop 1 */ |
| 516 | − { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, | |
| 493 | + { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, | |
| 517 | 494 | |
| 518 | 495 | /* Surface Laptop 2 */ |
| 519 | − { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, | |
| 496 | + { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, | |
| 520 | 497 | |
| 521 | 498 | /* Surface Laptop 3 (13", Intel) */ |
| 522 | 499 | { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, |
| @@ -415,16 +415,17 @@ config HP_ACCEL · 414 unmodified lines | ||
| 415 | 415 | To compile this driver as a module, choose M here: the module will |
| 416 | 416 | be called hp_accel. |
| 417 | 417 | |
| 418 | −config HP_WIRELESS | |
| 419 | − tristate "HP wireless button" | |
| 418 | +config WIRELESS_HOTKEY | |
| 419 | + tristate "Wireless hotkey button" | |
| 420 | 420 | depends on ACPI |
| 421 | 421 | depends on INPUT |
| 422 | 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 | 425 | On such systems the driver should load automatically (via ACPI alias). |
| 425 | 426 | |
| 426 | 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 | 430 | config HP_WMI |
| 430 | 431 | tristate "HP WMI extras" |
| @@ -639,6 +640,19 @@ config THINKPAD_ACPI_HOTKEY_POLL · 208 unmodified lines | ||
| 639 | 640 | If you are not sure, say Y here. The driver enables polling only if |
| 640 | 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 | 656 | config INTEL_ATOMISP2_LED |
| 643 | 657 | tristate "Intel AtomISP2 camera LED driver" |
| 644 | 658 | depends on GPIOLIB && LEDS_GPIO |
| @@ -673,30 +687,6 @@ config INTEL_ATOMISP2_PM · 28 unmodified lines | ||
| 673 | 687 | To compile this driver as a module, choose M here: the module |
| 674 | 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 | 690 | config INTEL_HID_EVENT |
| 701 | 691 | tristate "INTEL HID Event" |
| 702 | 692 | depends on ACPI |
| @@ -1076,6 +1066,9 @@ config TOUCHSCREEN_DMI · 373 unmodified lines | ||
| 1076 | 1066 | the OS-image for the device. This option supplies the missing info. |
| 1077 | 1067 | Enable this for x86 tablets with Silead or Chipone touchscreens. |
| 1078 | 1068 | |
| 1069 | +config FW_ATTR_CLASS | |
| 1070 | + tristate | |
| 1071 | + | |
| 1079 | 1072 | config INTEL_IMR |
| 1080 | 1073 | bool "Intel Isolated Memory Region support" |
| 1081 | 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 | 53 | # Hewlett Packard |
| 54 | 54 | obj-$(CONFIG_HP_ACCEL) += hp_accel.o |
| 55 | −obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o | |
| 56 | 55 | obj-$(CONFIG_HP_WMI) += hp-wmi.o |
| 57 | 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 | 63 | obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o |
| 65 | 64 | obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o |
| 66 | 65 | obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o |
| 66 | +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o | |
| 67 | 67 | |
| 68 | 68 | # Intel |
| 69 | +obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ | |
| 70 | + | |
| 69 | 71 | obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o |
| 70 | 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 | 73 | obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o |
| 76 | 74 | obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o |
| 77 | 75 | obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o |
| @@ -112,9 +110,11 @@ obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o · 34 unmodified lines | ||
| 112 | 110 | obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o |
| 113 | 111 | |
| 114 | 112 | # Platform drivers |
| 113 | +obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o | |
| 115 | 114 | obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o |
| 116 | 115 | obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o |
| 117 | 116 | obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o |
| 117 | +obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o | |
| 118 | 118 | |
| 119 | 119 | # Intel uncore drivers |
| 120 | 120 | obj-$(CONFIG_INTEL_IPS) += intel_ips.o |
| @@ -110,11 +110,6 @@ static struct quirk_entry quirk_asus_forceals = { · 109 unmodified lines | ||
| 110 | 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 | 113 | static struct quirk_entry quirk_asus_use_kbd_dock_devid = { |
| 119 | 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 | 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 | 424 | .callback = dmi_matched, |
| 502 | 425 | .ident = "Asus Transformer T100TA / T100HA / T100CHI", |
| @@ -5,7 +5,6 @@ · 4 unmodified lines | ||
| 5 | 5 | |
| 6 | 6 | menuconfig X86_PLATFORM_DRIVERS_DELL |
| 7 | 7 | bool "Dell X86 Platform Specific Device Drivers" |
| 8 | − default n | |
| 9 | 8 | depends on X86_PLATFORM_DEVICES |
| 10 | 9 | help |
| 11 | 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 | 52 | depends on BACKLIGHT_CLASS_DEVICE |
| 54 | 53 | depends on ACPI_VIDEO || ACPI_VIDEO = n |
| 55 | 54 | depends on RFKILL || RFKILL = n |
| 55 | + depends on DELL_WMI || DELL_WMI = n | |
| 56 | 56 | depends on SERIO_I8042 |
| 57 | 57 | depends on DELL_SMBIOS |
| 58 | 58 | select POWER_SUPPLY |
| @@ -164,6 +164,14 @@ config DELL_WMI · 105 unmodified lines | ||
| 164 | 164 | To compile this driver as a module, choose M here: the module will |
| 165 | 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 | 175 | config DELL_WMI_AIO |
| 168 | 176 | tristate "WMI Hotkeys for Dell All-In-One series" |
| 169 | 177 | default m |
| @@ -197,6 +205,7 @@ config DELL_WMI_SYSMAN · 27 unmodified lines | ||
| 197 | 205 | depends on ACPI_WMI |
| 198 | 206 | depends on DMI |
| 199 | 207 | select NLS |
| 208 | + select FW_ATTR_CLASS | |
| 200 | 209 | help |
| 201 | 210 | This driver allows changing BIOS settings on many Dell machines from |
| 202 | 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 | 15 | dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o |
| 16 | 16 | obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o |
| 17 | 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 | 20 | obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o |
| 19 | 21 | obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o |
| 20 | 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 | 395 | /* wait a few to see if it executed */ |
| 396 | 396 | num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; |
| 397 | − while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) | |
| 398 | − == ESM_STATUS_CMD_UNSUCCESSFUL) { | |
| 397 | + while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) { | |
| 399 | 398 | num_ticks--; |
| 400 | 399 | if (num_ticks == EXPIRED_TIMER) |
| 401 | 400 | return -ETIME; |
| @@ -31,6 +31,8 @@ · 30 unmodified lines | ||
| 31 | 31 | #include "dell-rbtn.h" |
| 32 | 32 | #include "dell-smbios.h" |
| 33 | 33 | |
| 34 | +#include "dell-wmi-privacy.h" | |
| 35 | + | |
| 34 | 36 | struct quirk_entry { |
| 35 | 37 | bool touchpad_led; |
| 36 | 38 | bool kbd_led_not_present; |
| @@ -90,6 +92,7 @@ static struct rfkill *wifi_rfkill; · 53 unmodified lines | ||
| 90 | 92 | static struct rfkill *bluetooth_rfkill; |
| 91 | 93 | static struct rfkill *wwan_rfkill; |
| 92 | 94 | static bool force_rfkill; |
| 95 | +static bool micmute_led_registered; | |
| 93 | 96 | |
| 94 | 97 | module_param(force_rfkill, bool, 0444); |
| 95 | 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 | 2208 | dell_laptop_register_notifier(&dell_laptop_notifier); |
| 2206 | 2209 | |
| 2207 | 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 | 2213 | micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); |
| 2210 | 2214 | ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); |
| 2211 | 2215 | if (ret < 0) |
| 2212 | 2216 | goto fail_led; |
| 2217 | + micmute_led_registered = true; | |
| 2213 | 2218 | } |
| 2214 | 2219 | |
| 2215 | 2220 | if (acpi_video_get_backlight_type() != acpi_backlight_vendor) |
| @@ -2257,7 +2262,8 @@ static int __init dell_init(void) · 41 unmodified lines | ||
| 2257 | 2262 | fail_get_brightness: |
| 2258 | 2263 | backlight_device_unregister(dell_backlight_device); |
| 2259 | 2264 | fail_backlight: |
| 2260 | − led_classdev_unregister(&micmute_led_cdev); | |
| 2265 | + if (micmute_led_registered) | |
| 2266 | + led_classdev_unregister(&micmute_led_cdev); | |
| 2261 | 2267 | fail_led: |
| 2262 | 2268 | dell_cleanup_rfkill(); |
| 2263 | 2269 | fail_rfkill: |
| @@ -2278,7 +2284,8 @@ static void __exit dell_exit(void) · 14 unmodified lines | ||
| 2278 | 2284 | touchpad_led_exit(); |
| 2279 | 2285 | kbd_led_exit(); |
| 2280 | 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 | 2289 | dell_cleanup_rfkill(); |
| 2283 | 2290 | if (platform_device) { |
| 2284 | 2291 | platform_device_unregister(platform_device); |
| @@ -27,6 +27,7 @@ · 26 unmodified lines | ||
| 27 | 27 | #include <acpi/video.h> |
| 28 | 28 | #include "dell-smbios.h" |
| 29 | 29 | #include "dell-wmi-descriptor.h" |
| 30 | +#include "dell-wmi-privacy.h" | |
| 30 | 31 | |
| 31 | 32 | MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); |
| 32 | 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 | 429 | switch (buffer_entry[1]) { |
| 429 | 430 | case 0x0000: /* One key pressed or event occurred */ |
| 430 | − case 0x0012: /* Event with extended data occurred */ | |
| 431 | 431 | if (len > 2) |
| 432 | 432 | dell_wmi_process_key(wdev, buffer_entry[1], |
| 433 | 433 | buffer_entry[2]); |
| @@ -439,6 +439,13 @@ static void dell_wmi_notify(struct wmi_device *wdev, · 5 unmodified lines | ||
| 439 | 439 | dell_wmi_process_key(wdev, buffer_entry[1], |
| 440 | 440 | buffer_entry[i]); |
| 441 | 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 | 449 | default: /* Unknown event */ |
| 443 | 450 | pr_info("Unknown WMI event type 0x%x\n", |
| 444 | 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 | 761 | return wmi_driver_register(&dell_wmi_driver); |
| 751 | 762 | } |
| 752 | 763 | late_initcall(dell_wmi_init); |
| @@ -757,6 +768,7 @@ static void __exit dell_wmi_exit(void) · 4 unmodified lines | ||
| 757 | 768 | dell_wmi_events_set_enabled(false); |
| 758 | 769 | |
| 759 | 770 | wmi_driver_unregister(&dell_wmi_driver); |
| 771 | + dell_privacy_unregister_driver(); | |
| 760 | 772 | } |
| 761 | 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 | 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 | 158 | union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); |
| 156 | 159 | int get_instance_count(const char *guid_string); |
| 157 | 160 | void strlcpy_attr(char *dest, char *src); |
| 158 | 161 | |
| 159 | 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 | 164 | int alloc_enum_data(void); |
| 162 | 165 | void exit_enum_attributes(void); |
| 163 | 166 | |
| @@ -132,39 +132,68 @@ int alloc_enum_data(void) · 131 unmodified lines | ||
| 132 | 132 | * @enumeration_obj: ACPI object with enumeration data |
| 133 | 133 | * @instance_id: The instance to enumerate |
| 134 | 134 | * @attr_name_kobj: The parent kernel object |
| 135 | + * @enum_property_count: Total properties count under enumeration type | |
| 135 | 136 | */ |
| 136 | 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 | 140 | int i, next_obj, value_modifier_count, possible_values_count; |
| 140 | 141 | |
| 141 | 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 | 145 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, |
| 143 | 146 | enumeration_obj[ATTR_NAME].string.pointer); |
| 147 | + if (check_property_type(enumeration, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | |
| 148 | + return -EINVAL; | |
| 144 | 149 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, |
| 145 | 150 | enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 151 | + if (check_property_type(enumeration, DISPLAY_NAME, ACPI_TYPE_STRING)) | |
| 152 | + return -EINVAL; | |
| 146 | 153 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, |
| 147 | 154 | enumeration_obj[DISPLAY_NAME].string.pointer); |
| 155 | + if (check_property_type(enumeration, DEFAULT_VAL, ACPI_TYPE_STRING)) | |
| 156 | + return -EINVAL; | |
| 148 | 157 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, |
| 149 | 158 | enumeration_obj[DEFAULT_VAL].string.pointer); |
| 159 | + if (check_property_type(enumeration, MODIFIER, ACPI_TYPE_STRING)) | |
| 160 | + return -EINVAL; | |
| 150 | 161 | strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, |
| 151 | 162 | enumeration_obj[MODIFIER].string.pointer); |
| 152 | 163 | |
| 153 | 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 | 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 | 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 | 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 | 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 | 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 | 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 | 141 | struct kobject *attr_name_kobj) |
| 142 | 142 | { |
| 143 | 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 | 146 | strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, |
| 145 | 147 | integer_obj[ATTR_NAME].string.pointer); |
| 148 | + if (check_property_type(integer, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | |
| 149 | + return -EINVAL; | |
| 146 | 150 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, |
| 147 | 151 | integer_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 152 | + if (check_property_type(integer, DISPLAY_NAME, ACPI_TYPE_STRING)) | |
| 153 | + return -EINVAL; | |
| 148 | 154 | strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, |
| 149 | 155 | integer_obj[DISPLAY_NAME].string.pointer); |
| 156 | + if (check_property_type(integer, DEFAULT_VAL, ACPI_TYPE_INTEGER)) | |
| 157 | + return -EINVAL; | |
| 150 | 158 | wmi_priv.integer_data[instance_id].default_value = |
| 151 | 159 | (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; |
| 160 | + if (check_property_type(integer, MODIFIER, ACPI_TYPE_STRING)) | |
| 161 | + return -EINVAL; | |
| 152 | 162 | strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, |
| 153 | 163 | integer_obj[MODIFIER].string.pointer); |
| 164 | + if (check_property_type(integer, MIN_VALUE, ACPI_TYPE_INTEGER)) | |
| 165 | + return -EINVAL; | |
| 154 | 166 | wmi_priv.integer_data[instance_id].min_value = |
| 155 | 167 | (uintptr_t)integer_obj[MIN_VALUE].string.pointer; |
| 168 | + if (check_property_type(integer, MAX_VALUE, ACPI_TYPE_INTEGER)) | |
| 169 | + return -EINVAL; | |
| 156 | 170 | wmi_priv.integer_data[instance_id].max_value = |
| 157 | 171 | (uintptr_t)integer_obj[MAX_VALUE].string.pointer; |
| 172 | + if (check_property_type(integer, SCALAR_INCR, ACPI_TYPE_INTEGER)) | |
| 173 | + return -EINVAL; | |
| 158 | 174 | wmi_priv.integer_data[instance_id].scalar_increment = |
| 159 | 175 | (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; |
| 160 | 176 | |
| @@ -159,10 +159,16 @@ int alloc_po_data(void) · 158 unmodified lines | ||
| 159 | 159 | int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) |
| 160 | 160 | { |
| 161 | 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 | 164 | strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, |
| 163 | 165 | po_obj[ATTR_NAME].string.pointer); |
| 166 | + if (check_property_type(po, MIN_PASS_LEN, ACPI_TYPE_INTEGER)) | |
| 167 | + return -EINVAL; | |
| 164 | 168 | wmi_priv.po_data[instance_id].min_password_length = |
| 165 | 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 | 172 | wmi_priv.po_data[instance_id].max_password_length = |
| 167 | 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 | 96 | print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); |
| 97 | 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 | 99 | if (!ret) |
| 100 | − memset(current_password, 0, MAX_BUFF); | |
| 100 | + strscpy(current_password, new, MAX_BUFF); | |
| 101 | 101 | /* explain to user the detailed failure reason */ |
| 102 | 102 | else if (ret == -EOPNOTSUPP) |
| 103 | 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 | 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 | 122 | * @instance_id: The instance to enumerate |
| 123 | 123 | * @attr_name_kobj: The parent kernel object |
| 124 | 124 | */ |
| 125 | 125 | int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) |
| 126 | 126 | { |
| 127 | 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 | 130 | strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, |
| 129 | 131 | str_obj[ATTR_NAME].string.pointer); |
| 132 | + if (check_property_type(str, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) | |
| 133 | + return -EINVAL; | |
| 130 | 134 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, |
| 131 | 135 | str_obj[DISPL_NAME_LANG_CODE].string.pointer); |
| 136 | + if (check_property_type(str, DISPLAY_NAME, ACPI_TYPE_STRING)) | |
| 137 | + return -EINVAL; | |
| 132 | 138 | strlcpy_attr(wmi_priv.str_data[instance_id].display_name, |
| 133 | 139 | str_obj[DISPLAY_NAME].string.pointer); |
| 140 | + if (check_property_type(str, DEFAULT_VAL, ACPI_TYPE_STRING)) | |
| 141 | + return -EINVAL; | |
| 134 | 142 | strlcpy_attr(wmi_priv.str_data[instance_id].default_value, |
| 135 | 143 | str_obj[DEFAULT_VAL].string.pointer); |
| 144 | + if (check_property_type(str, MODIFIER, ACPI_TYPE_STRING)) | |
| 145 | + return -EINVAL; | |
| 136 | 146 | strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, |
| 137 | 147 | str_obj[MODIFIER].string.pointer); |
| 148 | + if (check_property_type(str, MIN_LEN, ACPI_TYPE_INTEGER)) | |
| 149 | + return -EINVAL; | |
| 138 | 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 | 153 | wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; |
| 140 | 154 | |
| 141 | 155 | return sysfs_create_group(attr_name_kobj, &str_attr_group); |
| @@ -13,14 +13,11 @@ · 12 unmodified lines | ||
| 13 | 13 | #include <linux/kernel.h> |
| 14 | 14 | #include <linux/wmi.h> |
| 15 | 15 | #include "dell-wmi-sysman.h" |
| 16 | +#include "../../firmware_attributes_class.h" | |
| 16 | 17 | |
| 17 | 18 | #define MAX_TYPES 4 |
| 18 | 19 | #include <linux/nls.h> |
| 19 | 20 | |
| 20 | −static struct class firmware_attributes_class = { | |
| 21 | − .name = "firmware-attributes", | |
| 22 | −}; | |
| 23 | − | |
| 24 | 21 | struct wmi_sysman_priv wmi_priv = { |
| 25 | 22 | .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), |
| 26 | 23 | }; |
| @@ -28,6 +25,7 @@ struct wmi_sysman_priv wmi_priv = { · 1 unmodified line | ||
| 28 | 25 | /* reset bios to defaults */ |
| 29 | 26 | static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; |
| 30 | 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 | 479 | /* enumerate all of this attribute */ |
| 482 | 480 | switch (attr_type) { |
| 483 | 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 | 484 | break; |
| 486 | 485 | case INT: |
| 487 | 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 | 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 | 544 | if (ret) |
| 546 | 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 | 548 | NULL, "%s", DRIVER_NAME); |
| 550 | 549 | if (IS_ERR(wmi_priv.class_dev)) { |
| 551 | 550 | ret = PTR_ERR(wmi_priv.class_dev); |
| @@ -602,10 +601,10 @@ err_release_attributes_data: · 50 unmodified lines | ||
| 602 | 601 | release_attributes_data(); |
| 603 | 602 | |
| 604 | 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 | 606 | err_unregister_class: |
| 608 | − class_unregister(&firmware_attributes_class); | |
| 607 | + fw_attributes_class_put(); | |
| 609 | 608 | |
| 610 | 609 | err_exit_bios_attr_pass_interface: |
| 611 | 610 | exit_bios_attr_pass_interface(); |
| @@ -619,8 +618,8 @@ err_exit_bios_attr_set_interface: · 7 unmodified lines | ||
| 619 | 618 | static void __exit sysman_exit(void) |
| 620 | 619 | { |
| 621 | 620 | release_attributes_data(); |
| 622 | − device_destroy(&firmware_attributes_class, MKDEV(0, 0)); | |
| 623 | − class_unregister(&firmware_attributes_class); | |
| 621 | + device_destroy(fw_attr_class, MKDEV(0, 0)); | |
| 622 | + fw_attributes_class_put(); | |
| 624 | 623 | exit_bios_attr_set_interface(); |
| 625 | 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 | 462 | NULL, |
| 463 | 463 | }; |
| 464 | 464 | |
| 465 | −static struct attribute_group hdaps_attribute_group = { | |
| 465 | +static const struct attribute_group hdaps_attribute_group = { | |
| 466 | 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 | 1408 | case 6: |
| 1409 | 1409 | ideapad_input_report(priv, bit); |
| 1410 | 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 | 1423 | case 9: |
| 1412 | 1424 | ideapad_sync_rfk_state(priv); |
| 1413 | 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 | 168 | return -ENODEV; |
| 169 | 169 | } |
| 170 | 170 | |
| 171 | − /* Then the DP child device node */ | |
| 172 | − data->dp = device_get_named_child_node(&pdev->dev, "DD02"); | |
| 171 | + /* Then the DP-2 child device node */ | |
| 172 | + data->dp = device_get_named_child_node(&pdev->dev, "DD04"); | |
| 173 | 173 | pci_dev_put(pdev); |
| 174 | 174 | if (!data->dp) |
| 175 | 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 | 830 | static u16 read_mgtv(struct ips_driver *ips) |
| 831 | 831 | { |
| 832 | − u16 ret; | |
| 832 | + u16 __maybe_unused ret; | |
| 833 | 833 | u64 slope, offset; |
| 834 | 834 | u64 val; |
| 835 | 835 | |
| @@ -218,7 +218,7 @@ static struct attribute *pmt_crashlog_attrs[] = { · 217 unmodified lines | ||
| 218 | 218 | NULL |
| 219 | 219 | }; |
| 220 | 220 | |
| 221 | −static struct attribute_group pmt_crashlog_group = { | |
| 221 | +static const struct attribute_group pmt_crashlog_group = { | |
| 222 | 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 | 281 | struct isst_if_cpu_info { |
| 282 | 282 | /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ |
| 283 | 283 | int bus_info[2]; |
| 284 | + struct pci_dev *pci_dev[2]; | |
| 284 | 285 | int punit_cpu_id; |
| 286 | + int numa_node; | |
| 285 | 287 | }; |
| 286 | 288 | |
| 287 | 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 | 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 | 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 | 364 | if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || |
| 306 | 365 | cpu >= num_possible_cpus()) |
| 307 | 366 | return NULL; |
| 308 | 367 | |
| 309 | − bus_number = isst_cpu_info[cpu].bus_info[bus_no]; | |
| 310 | − if (bus_number < 0) | |
| 311 | − return NULL; | |
| 368 | + pci_dev = isst_cpu_info[cpu].pci_dev[bus_no]; | |
| 369 | + | |
| 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 | 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 | 387 | } else { |
| 328 | 388 | isst_cpu_info[cpu].bus_info[0] = data & 0xff; |
| 329 | 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 | 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 | 397 | return ret; |
| 336 | 398 | } |
| 337 | 399 | isst_cpu_info[cpu].punit_cpu_id = data; |
| 400 | + isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); | |
| 338 | 401 | |
| 339 | 402 | isst_restore_msr_local(cpu); |
| 340 | 403 | |
| @@ -388,7 +388,7 @@ MODULE_PARM_DESC(force, · 387 unmodified lines | ||
| 388 | 388 | "Disable the DMI check and forces the driver to be loaded"); |
| 389 | 389 | |
| 390 | 390 | static bool debug; |
| 391 | −module_param(debug, bool, S_IRUGO | S_IWUSR); | |
| 391 | +module_param(debug, bool, 0644); | |
| 392 | 392 | MODULE_PARM_DESC(debug, "Debug enabled or not"); |
| 393 | 393 | |
| 394 | 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 | 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 | 709 | get_performance_level, set_performance_level); |
| 710 | 710 | |
| 711 | 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 | 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 | 778 | get_battery_life_extender, set_battery_life_extender); |
| 779 | 779 | |
| 780 | 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 | 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 | 847 | get_usb_charge, set_usb_charge); |
| 848 | 848 | |
| 849 | 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 | 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 | 912 | get_lid_handling, set_lid_handling); |
| 913 | 913 | |
| 914 | 914 | static struct attribute *platform_attributes[] = { |
| @@ -1291,24 +1291,17 @@ static void samsung_debugfs_init(struct samsung_laptop *samsung) · 376 unmodified lines | ||
| 1291 | 1291 | samsung->debug.sdiag_wrapper.data = samsung->sdiag; |
| 1292 | 1292 | samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); |
| 1293 | 1293 | |
| 1294 | − debugfs_create_u16("command", S_IRUGO | S_IWUSR, root, | |
| 1295 | − &samsung->debug.command); | |
| 1296 | − debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root, | |
| 1297 | − &samsung->debug.data.d0); | |
| 1298 | − debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root, | |
| 1299 | − &samsung->debug.data.d1); | |
| 1300 | − debugfs_create_u16("d2", S_IRUGO | S_IWUSR, 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, | |
| 1294 | + debugfs_create_u16("command", 0644, root, &samsung->debug.command); | |
| 1295 | + debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0); | |
| 1296 | + debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1); | |
| 1297 | + debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2); | |
| 1298 | + debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3); | |
| 1299 | + debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper); | |
| 1300 | + debugfs_create_blob("f0000_segment", 0400, root, | |
| 1307 | 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 | 1303 | &samsung_laptop_call_fops); |
| 1310 | − debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root, | |
| 1311 | − &samsung->debug.sdiag_wrapper); | |
| 1304 | + debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper); | |
| 1312 | 1305 | } |
| 1313 | 1306 | |
| 1314 | 1307 | static void samsung_sabi_exit(struct samsung_laptop *samsung) |
| @@ -156,7 +156,7 @@ static struct attribute *tc1100_attributes[] = { · 155 unmodified lines | ||
| 156 | 156 | NULL |
| 157 | 157 | }; |
| 158 | 158 | |
| 159 | −static struct attribute_group tc1100_attribute_group = { | |
| 159 | +static const struct attribute_group tc1100_attribute_group = { | |
| 160 | 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 | 7938 | continue; |
| 7939 | 7939 | } else if (sscanf(cmd, "level %u", &l) == 1 && |
| 7940 | 7940 | l >= 0 && l <= TP_EC_VOLUME_MAX) { |
| 7941 | − new_level = l; | |
| 7941 | + new_level = l; | |
| 7942 | 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 | 2832 | if (!dev->info_supported && !dev->system_event_supported) { |
| 2833 | 2833 | pr_warn("No hotkey query interface found\n"); |
| 2834 | + error = -EINVAL; | |
| 2834 | 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 | 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 | 136 | acpi_bus_generate_netlink_event(device->pnp.device_class, |
| 137 | 137 | dev_name(&device->dev), |
| @@ -299,6 +299,35 @@ static const struct ts_dmi_data estar_beauty_hd_data = { · 298 unmodified lines | ||
| 299 | 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 | 331 | static const struct property_entry gp_electronic_t701_props[] = { |
| 303 | 332 | PROPERTY_ENTRY_U32("touchscreen-size-x", 960), |
| 304 | 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 | 975 | .driver_data = (void *)&chuwi_hi10_pro_data, |
| 947 | 976 | .matches = { |
| 948 | 977 | DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), |
| @@ -1038,6 +1067,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 89 unmodified lines | ||
| 1038 | 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 | 1080 | /* GP-electronic T701 */ |
| 1043 | 1081 | .driver_data = (void *)&gp_electronic_t701_data, |
| @@ -1330,6 +1368,24 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 286 unmodified lines | ||
| 1330 | 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 | 1390 | /* Teclast X98 Plus II */ |
| 1335 | 1391 | .driver_data = (void *)&teclast_x98plus2_data, |
| @@ -1338,6 +1394,19 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 2 unmodified lines | ||
| 1338 | 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 | 1411 | /* Trekstor Primebook C11 */ |
| 1343 | 1412 | .driver_data = (void *)&trekstor_primebook_c11_data, |
| @@ -1413,6 +1482,22 @@ const struct dmi_system_id touchscreen_dmi_table[] = { · 69 unmodified lines | ||
| 1413 | 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 | 1502 | /* Yours Y8W81, same case and touchscreen as Chuwi Vi8 */ |
| 1418 | 1503 | .driver_data = (void *)&chuwi_vi8_data, |
| @@ -778,7 +778,7 @@ static struct attribute *base_attrs[] = { · 777 unmodified lines | ||
| 778 | 778 | NULL, |
| 779 | 779 | }; |
| 780 | 780 | |
| 781 | −static struct attribute_group base_attr_group = { | |
| 781 | +static const struct attribute_group base_attr_group = { | |
| 782 | 782 | .attrs = base_attrs |
| 783 | 783 | }; |
| 784 | 784 | |
| @@ -823,7 +823,7 @@ static struct attribute *hubless_base_attrs[] = { · 38 unmodified lines | ||
| 823 | 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 | 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 | 1097 | #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) |
| 1098 | 1098 | bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, |
| 1099 | 1099 | struct acpi_resource_gpio **agpio); |
| 1100 | +bool acpi_gpio_get_io_resource(struct acpi_resource *ares, | |
| 1101 | + struct acpi_resource_gpio **agpio); | |
| 1100 | 1102 | int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); |
| 1101 | 1103 | #else |
| 1102 | 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 | 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 | 1114 | static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, |
| 1108 | 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 | 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 | 79 | #endif |
| @@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, · 691 unmodified lines | ||
| 692 | 692 | const struct acpi_gpio_mapping *gpios); |
| 693 | 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 | 697 | #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ |
| 696 | 698 | |
| 697 | 699 | struct acpi_device; |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | * managing access and communication to and from the SSAM EC, as well as main |
| 7 | 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 | 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H |
| @@ -795,6 +795,20 @@ enum ssam_event_mask { · 782 unmodified lines | ||
| 795 | 795 | #define SSAM_EVENT_REGISTRY_REG \ |
| 796 | 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 | 813 | * struct ssam_event_notifier - Notifier block for SSAM events. |
| 800 | 814 | * @base: The base notifier block with callback function and priority. |
| @@ -803,6 +817,7 @@ enum ssam_event_mask { · 2 unmodified lines | ||
| 803 | 817 | * @event.id: ID specifying the event. |
| 804 | 818 | * @event.mask: Flags determining how events are matched to the notifier. |
| 805 | 819 | * @event.flags: Flags used for enabling the event. |
| 820 | + * @flags: Notifier flags (see &enum ssam_event_notifier_flags). | |
| 806 | 821 | */ |
| 807 | 822 | struct ssam_event_notifier { |
| 808 | 823 | struct ssam_notifier_block base; |
| @@ -813,6 +828,8 @@ struct ssam_event_notifier { · 4 unmodified lines | ||
| 813 | 828 | enum ssam_event_mask mask; |
| 814 | 829 | u8 flags; |
| 815 | 830 | } event; |
| 831 | + | |
| 832 | + unsigned long flags; | |
| 816 | 833 | }; |
| 817 | 834 | |
| 818 | 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 | 838 | int ssam_notifier_unregister(struct ssam_controller *ctrl, |
| 822 | 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 | 849 | #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ |
| @@ -7,7 +7,7 @@ · 6 unmodified lines | ||
| 7 | 7 | * Provides support for non-platform/non-ACPI SSAM clients via dedicated |
| 8 | 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 | 13 | #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | * Surface System Aggregator Module (SSAM). Provides the interface for basic |
| 7 | 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 | 12 | #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H |
| @@ -6,7 +6,7 @@ · 5 unmodified lines | ||
| 6 | 6 | * device. This device provides direct user-space access to the SSAM EC. |
| 7 | 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 | 12 | #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H |
| @@ -73,6 +73,75 @@ struct ssam_cdev_request { · 60 unmodified lines | ||
| 73 | 73 | } response; |
| 74 | 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 | 147 | #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ |
| @@ -15,7 +15,7 @@ struct process_cmd_struct { · 14 unmodified lines | ||
| 15 | 15 | int arg; |
| 16 | 16 | }; |
| 17 | 17 | |
| 18 | −static const char *version_str = "v1.9"; | |
| 18 | +static const char *version_str = "v1.10"; | |
| 19 | 19 | static const int supported_api_ver = 1; |
| 20 | 20 | static struct isst_if_platform_info isst_platform_info; |
| 21 | 21 | static char *progname; |
| @@ -106,6 +106,22 @@ int is_skx_based_platform(void) · 84 unmodified lines | ||
| 106 | 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 | 125 | static int update_cpu_model(void) |
| 110 | 126 | { |
| 111 | 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 | 202 | unsigned int resp; |
| 203 | 203 | int ret; |
| 204 | + | |
| 204 | 205 | ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, |
| 205 | 206 | 0, config_index, &resp); |
| 206 | 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 | 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 | 227 | debug_printf( |
| 213 | 228 | "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", |
| 214 | 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 | 446 | if (ctdp_level->mem_freq) { |
| 447 | 447 | snprintf(header, sizeof(header), "mem-frequency(MHz)"); |
| 448 | 448 | snprintf(value, sizeof(value), "%d", |
| 449 | − ctdp_level->mem_freq * DISP_FREQ_MULTIPLIER); | |
| 449 | + ctdp_level->mem_freq); | |
| 450 | 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 | 257 | extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap); |
| 258 | 258 | extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); |
| 259 | 259 | extern int is_skx_based_platform(void); |
| 260 | +extern int is_spr_platform(void); | |
| 261 | +extern int is_icx_platform(void); | |
| 260 | 262 | extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl); |
| 261 | 263 | #endif |