From f88d5f4236efb892696552b500441fa9571c9047 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Fri, 28 Aug 2026 11:13:19 +0200 Subject: [PATCH] handheld Signed-off-by: Peter Jung --- Documentation/ABI/testing/debugfs-cros-ec | 33 + Documentation/ABI/testing/sysfs-power | 13 + Documentation/driver-api/pm/devices.rst | 8 + .../wmi/devices/msi-wmi-platform.rst | 26 + MAINTAINERS | 12 + arch/x86/pci/fixup.c | 16 + drivers/acpi/battery.c | 81 + drivers/acpi/button.c | 12 +- drivers/acpi/device_pm.c | 6 + drivers/acpi/ec.c | 9 + drivers/acpi/x86/s2idle.c | 178 +- drivers/base/power/main.c | 53 + drivers/bluetooth/btrtl.c | 71 + drivers/bluetooth/btusb.c | 2 + drivers/bluetooth/hci_qca.c | 1 + drivers/bus/mhi/host/boot.c | 15 +- drivers/bus/mhi/host/init.c | 7 +- drivers/bus/mhi/host/internal.h | 1 + drivers/bus/mhi/host/pm.c | 9 + drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-steamdeck.c | 180 + drivers/gpu/drm/amd/amdgpu/amdgpu.h | 5 + drivers/gpu/drm/amd/amdgpu/amdgpu_device.c | 37 + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 2 +- drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c | 17 +- drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 2 - drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | 3 +- .../gpu/drm/amd/amdgpu/atombios_encoders.c | 10 +- drivers/gpu/drm/amd/amdgpu/cik_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/cz_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c | 39 +- drivers/gpu/drm/amd/amdgpu/iceland_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/ih_v6_0.c | 1 + drivers/gpu/drm/amd/amdgpu/ih_v6_1.c | 1 + drivers/gpu/drm/amd/amdgpu/navi10_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/si_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/tonga_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/vega10_ih.c | 1 + drivers/gpu/drm/amd/amdgpu/vega20_ih.c | 1 + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 36 +- .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 16 +- .../amd/display/dc/dcn30/dcn30_cm_common.c | 192 + .../display/dc/dwb/dcn30/dcn30_cm_common.h | 4 + .../amd/display/dc/hwss/dce110/dce110_hwseq.c | 33 +- .../amd/display/dc/hwss/dcn32/dcn32_hwseq.c | 16 +- .../drm/amd/display/dc/link/link_detection.c | 2 +- .../drm/amd/display/dc/link/link_validation.c | 11 + .../dc/resource/dcn301/dcn301_resource.c | 6 +- .../dc/resource/dcn32/dcn32_resource.c | 2 +- .../dc/resource/dcn321/dcn321_resource.c | 2 +- .../dc/resource/dcn401/dcn401_resource.c | 2 +- drivers/gpu/drm/clients/drm_client_setup.c | 2 +- drivers/gpu/drm/drm_panel_backlight_quirks.c | 15 + .../gpu/drm/drm_panel_orientation_quirks.c | 150 + drivers/gpu/drm/drm_sysfs.c | 31 + drivers/gpu/drm/ttm/ttm_device.c | 6 + drivers/hid/Kconfig | 24 + drivers/hid/Makefile | 4 + drivers/hid/hid-asus-ally.c | 2304 ++++++++ drivers/hid/hid-asus-ally.h | 400 ++ drivers/hid/hid-asus.c | 27 +- drivers/hid/hid-asus.h | 13 + drivers/hid/hid-ids.h | 16 + drivers/hid/hid-msi.c | 2063 +++++++ drivers/hid/hid-multitouch.c | 39 + drivers/hid/hid-steam.c | 1483 +++-- drivers/hid/zotac-zone-hid/Kconfig | 8 + drivers/hid/zotac-zone-hid/Makefile | 6 + .../zotac-zone-hid/zotac-zone-hid-config.c | 2231 +++++++ .../hid/zotac-zone-hid/zotac-zone-hid-core.c | 597 ++ .../hid/zotac-zone-hid/zotac-zone-hid-input.c | 522 ++ .../hid/zotac-zone-hid/zotac-zone-hid-rgb.c | 717 +++ drivers/hid/zotac-zone-hid/zotac-zone.h | 195 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpd-fan.c | 8 + drivers/hwmon/steamdeck-hwmon.c | 294 + drivers/i2c/busses/Kconfig | 2 +- drivers/input/input.c | 12 + drivers/input/joystick/xpad.c | 4 + drivers/input/misc/uinput.c | 6 +- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-steamdeck.c | 123 + drivers/leds/rgb/Kconfig | 9 + drivers/leds/rgb/Makefile | 1 + drivers/leds/rgb/leds-valve.c | 771 +++ .../media/cec/platform/cros-ec/cros-ec-cec.c | 33 +- drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/steamdeck.c | 147 + drivers/misc/cardreader/rtsx_pcr.c | 85 +- drivers/mmc/core/block.c | 11 +- drivers/mmc/core/host.c | 15 + drivers/mmc/core/mmc.c | 2 + drivers/mmc/core/quirks.h | 2 + drivers/mmc/core/sd.c | 2 + drivers/mmc/host/rtsx_pci_sdmmc.c | 144 +- drivers/mmc/host/sdhci-pci-gli.c | 8 + drivers/net/wireless/ath/ath11k/Makefile | 3 +- drivers/net/wireless/ath/ath11k/core.h | 3 + drivers/net/wireless/ath/ath11k/debugfs.c | 11 +- drivers/net/wireless/ath/ath11k/dp_rx.c | 2 +- drivers/net/wireless/ath/ath11k/hal.c | 7 +- drivers/net/wireless/ath/ath11k/hif.h | 8 + drivers/net/wireless/ath/ath11k/htc.c | 2 +- drivers/net/wireless/ath/ath11k/hw.c | 1 + drivers/net/wireless/ath/ath11k/mac.c | 38 + drivers/net/wireless/ath/ath11k/mhi.c | 22 +- drivers/net/wireless/ath/ath11k/mhi.h | 1 + drivers/net/wireless/ath/ath11k/pci.c | 18 +- drivers/net/wireless/ath/ath11k/unitest.c | 96 + drivers/net/wireless/ath/ath11k/unitest.h | 44 + drivers/net/wireless/ath/ath11k/wmi.c | 72 +- drivers/net/wireless/ath/ath11k/wmi.h | 32 + drivers/net/wireless/ath/ath12k/mhi.c | 14 +- drivers/net/wireless/realtek/rtw89/core.h | 1 + drivers/net/wireless/realtek/rtw89/fw.c | 6 +- drivers/net/wireless/realtek/rtw89/regd.c | 32 +- drivers/pci/pcie/aspm.c | 11 +- drivers/pci/pcie/portdrv.c | 8 +- drivers/pci/quirks.c | 218 +- drivers/pci/remove.c | 7 + drivers/pinctrl/pinctrl-amd.c | 10 +- drivers/platform/chrome/cros_ec_sysfs.c | 171 + drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 2 + drivers/platform/x86/asus-wmi.c | 6 +- drivers/platform/x86/msi-wmi-platform.c | 1283 +++- drivers/platform/x86/msi-wmi.c | 97 +- drivers/platform/x86/zotac-zone-platform.c | 1122 ++++ drivers/usb/core/hcd.c | 9 + drivers/usb/dwc3/dwc3-pci.c | 3 +- drivers/usb/usbip/vhci_hcd.c | 33 +- drivers/video/backlight/backlight.c | 2 + include/acpi/acpi_bus.h | 4 + include/drm/drm_sysfs.h | 10 + include/linux/mhi.h | 2 + include/linux/mmc/card.h | 1 + include/linux/mmc/host.h | 1 + include/linux/pci.h | 1 + .../linux/platform_data/cros_ec_commands.h | 45 +- include/linux/pm.h | 4 + include/linux/rtsx_pci.h | 4 + include/linux/suspend.h | 2 + include/net/bluetooth/hci.h | 1 + include/net/bluetooth/hci_core.h | 2 +- include/sound/hda_codec.h | 1 + kernel/power/hibernate.c | 21 +- kernel/power/main.c | 1 + kernel/power/suspend.c | 23 + kernel/power/swap.c | 20 +- net/bluetooth/hci_core.c | 10 +- net/bluetooth/hci_sync.c | 12 +- net/bluetooth/mgmt.c | 4 +- net/wireless/reg.c | 13 +- sound/hda/codecs/hdmi/hdmi.c | 4 +- sound/hda/codecs/realtek/alc269.c | 59 + sound/hda/common/codec.c | 5 +- sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/Makefile | 1 + sound/soc/codecs/aw87xxx/Kconfig | 5 + sound/soc/codecs/aw87xxx/Makefile | 4 + sound/soc/codecs/aw87xxx/aw87xxx.c | 1600 +++++ sound/soc/codecs/aw87xxx/aw87xxx.h | 130 + sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.c | 1558 +++++ sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.h | 191 + sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.c | 514 ++ sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.h | 73 + sound/soc/codecs/aw87xxx/aw87xxx_device.c | 976 +++ sound/soc/codecs/aw87xxx/aw87xxx_device.h | 149 + sound/soc/codecs/aw87xxx/aw87xxx_dsp.c | 355 ++ sound/soc/codecs/aw87xxx/aw87xxx_dsp.h | 65 + sound/soc/codecs/aw87xxx/aw87xxx_log.h | 33 + sound/soc/codecs/aw87xxx/aw87xxx_monitor.c | 1207 ++++ sound/soc/codecs/aw87xxx/aw87xxx_monitor.h | 96 + sound/soc/codecs/aw87xxx/aw87xxx_pid_18_reg.h | 2315 ++++++++ sound/soc/codecs/aw87xxx/aw87xxx_pid_39_reg.h | 67 + .../codecs/aw87xxx/aw87xxx_pid_59_3x9_reg.h | 93 + .../codecs/aw87xxx/aw87xxx_pid_59_5x9_reg.h | 94 + sound/soc/codecs/aw87xxx/aw87xxx_pid_5a_reg.h | 4124 +++++++++++++ sound/soc/codecs/aw87xxx/aw87xxx_pid_60_reg.h | 5246 +++++++++++++++++ sound/soc/codecs/aw87xxx/aw87xxx_pid_76_reg.h | 1205 ++++ sound/soc/codecs/aw87xxx/aw87xxx_pid_9b_reg.h | 81 + sound/soc/codecs/cs35l41.c | 127 +- sound/soc/codecs/max98388.c | 76 +- sound/soc/codecs/max98388.h | 1 + sound/soc/codecs/nau8821.c | 4 + sound/soc/sof/core.c | 2 +- sound/soc/sof/pm.c | 27 + 191 files changed, 37179 insertions(+), 653 deletions(-) create mode 100644 drivers/extcon/extcon-steamdeck.c create mode 100644 drivers/hid/hid-asus-ally.c create mode 100644 drivers/hid/hid-asus-ally.h create mode 100644 drivers/hid/hid-asus.h create mode 100644 drivers/hid/hid-msi.c create mode 100644 drivers/hid/zotac-zone-hid/Kconfig create mode 100644 drivers/hid/zotac-zone-hid/Makefile create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c create mode 100644 drivers/hid/zotac-zone-hid/zotac-zone.h create mode 100644 drivers/hwmon/steamdeck-hwmon.c create mode 100644 drivers/leds/leds-steamdeck.c create mode 100644 drivers/leds/rgb/leds-valve.c create mode 100644 drivers/mfd/steamdeck.c create mode 100644 drivers/net/wireless/ath/ath11k/unitest.c create mode 100644 drivers/net/wireless/ath/ath11k/unitest.h create mode 100644 drivers/platform/x86/zotac-zone-platform.c create mode 100644 sound/soc/codecs/aw87xxx/Kconfig create mode 100644 sound/soc/codecs/aw87xxx/Makefile create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_device.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_device.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_dsp.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_dsp.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_log.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_monitor.c create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_monitor.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_18_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_39_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_59_3x9_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_59_5x9_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_5a_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_60_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_76_reg.h create mode 100644 sound/soc/codecs/aw87xxx/aw87xxx_pid_9b_reg.h diff --git a/Documentation/ABI/testing/debugfs-cros-ec b/Documentation/ABI/testing/debugfs-cros-ec index 9a040c6f5e03..0588cc7382c1 100644 --- a/Documentation/ABI/testing/debugfs-cros-ec +++ b/Documentation/ABI/testing/debugfs-cros-ec @@ -76,3 +76,36 @@ Description: hardware line changed and the kernel sent an EC resume command. Output will be in the format: "%u\n". + +What: /sys/kernel/debug//cec_phys_addr +Date: December 2025 +KernelVersion: 6.16 +Description: + Some ECs allow for customized CEC wakeup configurations that + watch for specific physical addresses. The cec_phys_addr file + allows for reading the physical addresses set for each CEC port + as well as setting them individually. + + Supply "%d %d\n" where the first decimal is the zero-indexed + port number and the second decimal is the desired physical + address. For example "0 4096" will set port 0's physical address + to "1.0.0.0". + + Output will be in the format: "%d %d\n" with a line for each + port. + +What: /sys/kernel/debug//cec_wake_enable +Date: December 2025 +KernelVersion: 6.16 +Description: + Some ECs allow us to disable all wakeup operations that would + be triggered by CEC messages while leaving the CEC port active + for regular use. This file allows for reading back the wake + enabled flag for all ports as well as setting them individually. + + Supply "%d %d\n" where the first decimal is the zero-indexed + port number and the second decimal is 1 to enable wakeups or 0 + to disable them. + + Output will be in the format: "%d %d\n" with a line for each + port. diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index d38da077905a..0147fbaaa985 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -470,3 +470,16 @@ Description: Minimum value: 1 Default value: 3 + +What: /sys/power/lps0_screen_off +Date: May 2026 +Contact: Dmitrii Osipenko +Description: + This file is available if the ACPI/OSPM system supports + Display Off/On DSM notifications. It controls state of the + notification. + + Writing a "1" to this file invokes Display Off Notification. + Writing a "0" to this file invokes Display On Notification. + + Notifications are only triggered on state transitions. diff --git a/Documentation/driver-api/pm/devices.rst b/Documentation/driver-api/pm/devices.rst index 36d5c9c9fd11..55c633727108 100644 --- a/Documentation/driver-api/pm/devices.rst +++ b/Documentation/driver-api/pm/devices.rst @@ -578,6 +578,14 @@ should already have been stored during the ``freeze``, ``freeze_late`` or the entire system, so it is not necessary for the callback to put the device in a low-power state. +Skipping thaw phase +------------------- +In some rare situations, it may be desirable to skip the thaw phases +(``thaw_noirq``, ``thaw_early``, ``thaw``) of a device entirely. This can be +achieved by a device driver returning an error code from any of it's thaw +callbacks but also setting dev->power.is_frozen to true. This indicates to the +PM core that the device is still in the frozen state. The PM core will consider +this when resuming the device in later phases such as `restore` or `poweroff`. Leaving Hibernation ------------------- diff --git a/Documentation/wmi/devices/msi-wmi-platform.rst b/Documentation/wmi/devices/msi-wmi-platform.rst index 73197b31926a..704bfdac5203 100644 --- a/Documentation/wmi/devices/msi-wmi-platform.rst +++ b/Documentation/wmi/devices/msi-wmi-platform.rst @@ -169,6 +169,32 @@ The fan RPM readings can be calculated with the following formula: If the fan speed reading is zero, then the fan RPM is zero too. +The subfeature ``0x01`` is used to retrieve the fan speed table for the CPU fan. The output +data contains the fan speed table and two bytes with unknown data. The fan speed table +consists of six 8-bit entries, each containing a fan speed value in percent. + +The subfeature ``0x02`` is used tho retrieve the same data for the GPU fan. + +WMI method Set_Fan() +-------------------- + +The fan speed tables can be accessed using subfeature ``0x01`` (CPU fan) and subfeature ``0x02`` +(GPU fan). The input data has the same format as the output data of the ``Get_Fan`` WMI method. + +WMI method Get_AP() +------------------- + +The current fan mode can be accessed using subfeature ``0x01``. The output data contains a flag +byte and two bytes of unknown data. If the 7th bit inside the flag byte is cleared then all fans +are operating in automatic mode, otherwise the fans operate based on the fan speed tables +accessible thru the ``Get_Fan``/``Set_Fan`` WMI methods. + +WMI method Set_AP() +------------------- + +The current fan mode can be changed using subfeature ``0x01``. The input data has the same format +as the output data of the ``Get_AP`` WMI method. + WMI method Get_WMI() -------------------- diff --git a/MAINTAINERS b/MAINTAINERS index 8014b9f8253e..9af2371137a6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11564,6 +11564,12 @@ F: drivers/hid/hid-sensor-* F: drivers/iio/*/hid-* F: include/linux/hid-sensor-* +HID STEAM CONTROLLER +M: Vicki Pfau +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-steam.c + HID VRC-2 CAR CONTROLLER DRIVER M: Marcus Folkesson L: linux-input@vger.kernel.org @@ -18340,6 +18346,12 @@ S: Odd Fixes F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt F: drivers/net/ieee802154/mrf24j40.c +MSI HID DRIVER +M: Derek J. Clark +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-msi.c + MSI EC DRIVER M: Nikita Kravets L: platform-driver-x86@vger.kernel.org diff --git a/arch/x86/pci/fixup.c b/arch/x86/pci/fixup.c index b301c6c8df75..9da7ae0b2437 100644 --- a/arch/x86/pci/fixup.c +++ b/arch/x86/pci/fixup.c @@ -1080,4 +1080,20 @@ static void quirk_tuxeo_rp_d3(struct pci_dev *pdev) } } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1502, quirk_tuxeo_rp_d3); + +/* + * MSI Claw A8 firmware initializes the onboard Realtek RTS525A cardreader + * with an MSI-specific Subsystem Vendor and Device ID on boot, but neglects + * to re-initialize it on resume. Avoid hot-removal of the cardreader due to + * the spurious ID change. + */ +static void quirk_msi_claw_cardreader(struct pci_dev *pdev) +{ + if (pdev->subsystem_vendor == 0x1462 && + pdev->subsystem_device == 0x14af) { + pdev->subsystem_vendor = pdev->vendor; + pdev->subsystem_device = pdev->device; + } +} +DECLARE_PCI_FIXUP_SUSPEND_LATE(PCI_VENDOR_ID_REALTEK, 0x525a, quirk_msi_claw_cardreader); #endif /* CONFIG_SUSPEND */ diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f5e0eb299610..4e6d73fe8551 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,11 @@ enum { * on a full charge, but showing degradation in full charge cap. */ ACPI_BATTERY_QUIRK_DEGRADED_FULL_CHARGE, + /* Some ECs report a _BST present rate that never tracks load + * (seen on the MSI Claw 8 EX AI+). Derive current/power from + * capacity change over time instead. + */ + ACPI_BATTERY_QUIRK_DERIVE_RATE, }; struct acpi_battery { @@ -101,6 +107,9 @@ struct acpi_battery { int revision; int rate_now; int capacity_now; + int derived_rate; + int derive_cap_prev; + unsigned long derive_time_prev; int voltage_now; int design_capacity; int full_charge_capacity; @@ -149,6 +158,7 @@ static int acpi_battery_technology(struct acpi_battery *battery) } static int acpi_battery_get_state(struct acpi_battery *battery); +static void acpi_battery_derive_rate(struct acpi_battery *battery); static int acpi_battery_is_charged(struct acpi_battery *battery) { @@ -204,6 +214,10 @@ static int acpi_battery_get_property(struct power_supply *psy, if (acpi_battery_present(battery)) { /* run battery update only if it is present */ acpi_battery_get_state(battery); + if (test_bit(ACPI_BATTERY_QUIRK_DERIVE_RATE, &battery->flags)) { + acpi_battery_derive_rate(battery); + battery->rate_now = battery->derived_rate; + } } else if (psp != POWER_SUPPLY_PROP_PRESENT) return -ENODEV; switch (psp) { @@ -930,6 +944,67 @@ static void find_battery(const struct dmi_header *dm, void *private) } } +static const struct dmi_system_id acpi_battery_derive_rate_dmi[] = { + { + /* MSI Claw 8 EX AI+: EC _BST present rate is bogus */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T91"), + }, + }, + {} +}; + +/* + * Recomputes battery->derived_rate; the caller mirrors it into rate_now. + * Kept as a separate field rather than writing rate_now directly here, + * since extract_package() (called from acpi_battery_get_state(), before + * this runs) overwrites rate_now with the raw EC value on every real + * refresh, and derived_rate must survive that to be held steady between + * capacity ticks. + */ +static void acpi_battery_derive_rate(struct acpi_battery *battery) +{ + unsigned long now = jiffies; + unsigned long dt; + u64 dcap; + + /* + * When neither charging nor discharging (full/on AC), the true rate + * is ~0 but capacity_now stops ticking, so the hold-steady logic + * below would freeze the last discharge value. Report 0 and re-seed. + */ + if (!(battery->state & (ACPI_BATTERY_STATE_DISCHARGING | + ACPI_BATTERY_STATE_CHARGING))) { + battery->derived_rate = 0; + battery->derive_cap_prev = ACPI_BATTERY_VALUE_UNKNOWN; + return; + } + + if (!ACPI_BATTERY_CAPACITY_VALID(battery->capacity_now)) { + battery->derived_rate = ACPI_BATTERY_VALUE_UNKNOWN; + return; + } + + if (battery->derive_cap_prev == ACPI_BATTERY_VALUE_UNKNOWN) { + battery->derive_cap_prev = battery->capacity_now; + battery->derive_time_prev = now; + return; + } + + if (battery->capacity_now == battery->derive_cap_prev) + return; + + dcap = abs(battery->derive_cap_prev - battery->capacity_now); + dt = now - battery->derive_time_prev; + battery->derive_cap_prev = battery->capacity_now; + battery->derive_time_prev = now; + + if (dt) + battery->derived_rate = div64_u64(dcap * 3600 * HZ, dt); +} + /* * According to the ACPI spec, some kinds of primary batteries can * report percentage battery remaining capacity directly to OS. @@ -1261,6 +1336,12 @@ static int acpi_battery_probe(struct platform_device *pdev) if (acpi_has_method(battery->device->handle, "_BIX")) set_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags); + if (dmi_check_system(acpi_battery_derive_rate_dmi)) { + battery->derived_rate = ACPI_BATTERY_VALUE_UNKNOWN; + battery->derive_cap_prev = ACPI_BATTERY_VALUE_UNKNOWN; + set_bit(ACPI_BATTERY_QUIRK_DERIVE_RATE, &battery->flags); + } + result = devm_acpi_battery_update_retry(dev, battery); if (result) return result; diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 3836ee75dd66..9d174cf23443 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -21,6 +21,7 @@ #include #include #include +#include #define ACPI_BUTTON_CLASS "button" #define ACPI_BUTTON_FILE_STATE "state" @@ -451,6 +452,7 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data) struct acpi_device *device = button->adev; struct input_dev *input; int keycode; + bool hard = false; switch (event) { case ACPI_BUTTON_NOTIFY_STATUS: @@ -463,14 +465,16 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data) return; } - acpi_pm_wakeup_event(button->dev); + input = button->input; + keycode = test_bit(KEY_SLEEP, input->keybit) ? KEY_SLEEP : KEY_POWER; + + if (event == ACPI_BUTTON_NOTIFY_STATUS) + hard = pm_sleep_transition_in_progress(); + acpi_pm_wakeup_event_hard(button->dev, hard); if (button->suspended || event == ACPI_BUTTON_NOTIFY_WAKE) return; - input = button->input; - keycode = test_bit(KEY_SLEEP, input->keybit) ? KEY_SLEEP : KEY_POWER; - input_report_key(input, keycode, 1); input_sync(input); input_report_key(input, keycode, 0); diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index aa55ecfc2923..5aa10d55a4d2 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -526,6 +526,12 @@ void acpi_pm_wakeup_event(struct device *dev) } EXPORT_SYMBOL_GPL(acpi_pm_wakeup_event); +void acpi_pm_wakeup_event_hard(struct device *dev, bool hard) +{ + pm_wakeup_dev_event(dev, 0, acpi_s2idle_wakeup() || hard); +} +EXPORT_SYMBOL_GPL(acpi_pm_wakeup_event_hard); + static void acpi_pm_notify_handler(acpi_handle handle, u32 val, void *not_used) { struct acpi_device *adev; diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 64ad4cfa6208..59af5f004745 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -2345,6 +2345,15 @@ static const struct dmi_system_id acpi_ec_no_wakeup[] = { DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), } }, + /* + * MSI Claw A8. Similar issue to Legion Go S (and same screen) + */ + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T8K"), + } + }, { // TUXEDO InfinityBook Pro AMD Gen9 .matches = { diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index b6b1dd76a06b..ba0779a2d802 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -18,7 +18,10 @@ #include #include #include +#include +#include #include +#include #include "../sleep.h" @@ -66,6 +69,18 @@ static guid_t lps0_dsm_guid_microsoft; static int lps0_dsm_func_mask_microsoft; static int lps0_dsm_state; +enum screen_off_val { + SCREEN_OFF_NO_CHANGE = -1, + SCREEN_SET_ON, + SCREEN_SET_OFF, +}; + +static DEFINE_MUTEX(lps0_dsm_screen_off_lock); +static bool lps0_dsm_screen_state_off; +static enum screen_off_val lps0_screen_off_suspended; +static enum screen_off_val lps0_screen_off_sysfs_enb; +static struct notifier_block pm_notifier; + /* Device constraint entry structure */ struct lpi_device_info { char *name; @@ -535,17 +550,26 @@ static int acpi_s2idle_begin_lps0(void) return acpi_s2idle_begin(); } -static int acpi_s2idle_prepare_late_lps0(void) +static bool lps0_has_screen_off_dsm(void) { - struct acpi_s2idle_dev_ops *handler; + int id = acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_ON_AMD : ACPI_LPS0_SCREEN_OFF; - if (!lps0_device_handle || sleep_no_lps0) - return 0; + if (lps0_dsm_func_mask_microsoft > 0 && + (lps0_dsm_func_mask & BIT(ACPI_LPS0_SCREEN_OFF))) + return true; - if (check_lps0_constraints) - lpi_check_constraints(); + if (lps0_dsm_func_mask > 0 && (lps0_dsm_func_mask & BIT(id))) + return true; + + return false; +} + +static void lps0_dsm_screen_off(void) +{ + if (lps0_dsm_screen_state_off) + return; - /* Screen off */ if (lps0_dsm_func_mask > 0) acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? ACPI_LPS0_SCREEN_OFF_AMD : @@ -554,7 +578,59 @@ static int acpi_s2idle_prepare_late_lps0(void) if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_OFF, - lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); + lps0_dsm_func_mask_microsoft, + lps0_dsm_guid_microsoft); + + lps0_dsm_screen_state_off = true; +} + +static void lps0_dsm_screen_on(void) +{ + if (!lps0_dsm_screen_state_off) + return; + + if (lps0_dsm_func_mask_microsoft > 0) + acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask_microsoft, + lps0_dsm_guid_microsoft); + + if (lps0_dsm_func_mask > 0) + acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? + ACPI_LPS0_SCREEN_ON_AMD : + ACPI_LPS0_SCREEN_ON, + lps0_dsm_func_mask, lps0_dsm_guid); + + lps0_dsm_screen_state_off = false; +} + +static void lps0_dsm_screen_off_set(enum screen_off_val sysfs_off, + enum screen_off_val suspended) +{ + guard(mutex)(&lps0_dsm_screen_off_lock); + + if (sysfs_off != SCREEN_OFF_NO_CHANGE) + lps0_screen_off_sysfs_enb = sysfs_off; + if (suspended != SCREEN_OFF_NO_CHANGE) + lps0_screen_off_suspended = suspended; + + if (lps0_screen_off_suspended || lps0_screen_off_sysfs_enb) + lps0_dsm_screen_off(); + else + lps0_dsm_screen_on(); +} + +static int acpi_s2idle_prepare_late_lps0(void) +{ + struct acpi_s2idle_dev_ops *handler; + + if (!lps0_device_handle || sleep_no_lps0) + return 0; + + if (check_lps0_constraints) + lpi_check_constraints(); + + /* Screen off */ + lps0_dsm_screen_off_set(SCREEN_OFF_NO_CHANGE, SCREEN_SET_OFF); /* LPS0 entry */ if (lps0_dsm_func_mask > 0 && acpi_s2idle_vendor_amd()) @@ -624,14 +700,7 @@ static void acpi_s2idle_restore_early_lps0(void) } /* Screen on */ - if (lps0_dsm_func_mask_microsoft > 0) - acpi_sleep_run_lps0_dsm(ACPI_LPS0_SCREEN_ON, - lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); - if (lps0_dsm_func_mask > 0) - acpi_sleep_run_lps0_dsm(acpi_s2idle_vendor_amd() ? - ACPI_LPS0_SCREEN_ON_AMD : - ACPI_LPS0_SCREEN_ON, - lps0_dsm_func_mask, lps0_dsm_guid); + lps0_dsm_screen_off_set(SCREEN_OFF_NO_CHANGE, SCREEN_SET_ON); } static const struct platform_s2idle_ops acpi_s2idle_ops_lps0 = { @@ -679,4 +748,81 @@ void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) } EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); +static ssize_t lps0_screen_off_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + enum screen_off_val sysfs_val; + bool off; + int ret; + + ret = kstrtobool(buf, &off); + if (ret) + return ret; + + if (off) + sysfs_val = SCREEN_SET_OFF; + else + sysfs_val = SCREEN_SET_ON; + + lps0_dsm_screen_off_set(sysfs_val, SCREEN_OFF_NO_CHANGE); + + return count; +} + +static ssize_t lps0_screen_off_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%d\n", lps0_screen_off_sysfs_enb); +} + +static struct kobj_attribute lps0_screen_off_attr = + __ATTR(lps0_screen_off, 0644, + lps0_screen_off_show, lps0_screen_off_store); + +static struct attribute *lps0_screen_off_attrs[] = { + &lps0_screen_off_attr.attr, + NULL, +}; + +static struct attribute_group lps0_screen_off_attr_group = { + .attrs = lps0_screen_off_attrs, +}; + +static int lps0_pm_notifier_callback(struct notifier_block *nb, + unsigned long action, void *data) +{ + switch (action) { + case PM_POST_HIBERNATION: + lps0_dsm_screen_off_set(SCREEN_SET_ON, SCREEN_SET_ON); + break; + } + + return NOTIFY_DONE; +} + +static int lps0_dsm_screen_off_init(void) +{ + int ret; + + if (!lps0_has_screen_off_dsm()) + return 0; + + ret = sysfs_create_group(power_kobj, &lps0_screen_off_attr_group); + if (ret) + return ret; + + pm_notifier.notifier_call = lps0_pm_notifier_callback; + + ret = register_pm_notifier(&pm_notifier); + if (ret) { + sysfs_remove_group(power_kobj, &lps0_screen_off_attr_group); + return ret; + } + + return 0; +} +late_initcall(lps0_dsm_screen_off_init); + #endif /* CONFIG_SUSPEND */ diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index f71467f6ada4..91e90c67c361 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -1176,6 +1176,13 @@ static void device_resume(struct device *dev, pm_message_t state, bool async) End: error = dpm_run_callback(callback, dev, state, info); +#ifdef CONFIG_HIBERNATE_CALLBACKS + /* device manages frozen state */ + if (error && dev->power.is_frozen) { + dev->power.is_suspended = true; + error = 0; + } +#endif device_unlock(dev); dpm_watchdog_clear(&wd); @@ -1258,6 +1265,52 @@ void dpm_resume(pm_message_t state) trace_suspend_resume(TPS("dpm_resume"), state.event, false); } +void dpm_resume_suspended_devices(pm_message_t state) +{ + struct device *dev; + ktime_t starttime = ktime_get(); + + trace_suspend_resume(TPS("dpm_resume_suspended_devices"), state.event, true); + + pm_transition = state; + async_error = 0; + + mutex_lock(&dpm_list_mtx); + + /* + * Start processing "async" root devices upfront so they don't wait for + * the "sync" devices they don't depend on. + */ + list_for_each_entry(dev, &dpm_list, power.entry) { + if (!dev->power.is_suspended) + continue; + + dpm_clear_async_state(dev); + if (dpm_root_device(dev)) + dpm_async_with_cleanup(dev, async_resume); + + if (!dpm_async_fn(dev, async_resume)) { + get_device(dev); + + mutex_unlock(&dpm_list_mtx); + + device_resume(dev, state, false); + + put_device(dev); + + mutex_lock(&dpm_list_mtx); + } + } + + mutex_unlock(&dpm_list_mtx); + async_synchronize_full(); + dpm_show_time(starttime, state, 0, NULL); + if (READ_ONCE(async_error)) + dpm_save_failed_step(SUSPEND_RESUME); + + trace_suspend_resume(TPS("dpm_resume_suspended_devices"), state.event, false); +} + /** * device_complete - Complete a PM transition for given device. * @dev: Device to handle. diff --git a/drivers/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 7f54d2d2d13a..9829b53ffcc7 100644 --- a/drivers/bluetooth/btrtl.c +++ b/drivers/bluetooth/btrtl.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,9 @@ #define RTL_CHIP_SUBVER (&(struct rtl_vendor_cmd) {{0x10, 0x38, 0x04, 0x28, 0x80}}) #define RTL_CHIP_REV (&(struct rtl_vendor_cmd) {{0x10, 0x3A, 0x04, 0x28, 0x80}}) #define RTL_SEC_PROJ (&(struct rtl_vendor_cmd) {{0x10, 0xA4, 0xAD, 0x00, 0xb0}}) +#define RTL_IGNORE_MASK (&(struct rtl_vendor_cmd) {{0x10, 0x34, 0x60, 0x00, 0xb0}}) + +#define RTL_IGNORE_BT_DIS BIT(0) #define RTL_PATCH_SNIPPETS 0x01 #define RTL_PATCH_DUMMY_HEADER 0x02 @@ -449,6 +453,34 @@ static int btrtl_vendor_read_reg16(struct hci_dev *hdev, return 0; } +static int btrtl_vendor_write_reg16(struct hci_dev *hdev, + struct rtl_vendor_cmd *cmd, const u8 * const rp) +{ + struct sk_buff *skb; + u8 buf[sizeof(*cmd) + 2]; + int err = 0; + + memcpy(buf, cmd, sizeof(*cmd)); + memcpy(buf + sizeof(*cmd), rp, 2); + + skb = __hci_cmd_sync(hdev, 0xfc62, sizeof(buf), buf, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + rtl_dev_err(hdev, "RTL: Write reg16 failed (%d)", err); + return err; + } + + if (skb->len != 1 || skb->data[0]) { + bt_dev_err(hdev, "RTL: Write reg16 length mismatch"); + kfree_skb(skb); + return -EIO; + } + + kfree_skb(skb); + + return 0; +} + static void *rtl_iov_pull_data(struct rtl_iovec *iov, u32 len) { void *data = iov->data; @@ -1298,8 +1330,46 @@ int btrtl_download_firmware(struct hci_dev *hdev, } EXPORT_SYMBOL_GPL(btrtl_download_firmware); +static const struct dmi_system_id btrtl_can_ignore_bt_dis_table[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"), + }, + }, + {} +}; + +static void btrtl_handle_fw_can_ignore_bt_dis(struct hci_dev *hdev) +{ + u16 ignore_mask; + u8 buf[2]; + int ret; + + if (!dmi_check_system(btrtl_can_ignore_bt_dis_table)) + return; + + ret = btrtl_vendor_read_reg16(hdev, RTL_IGNORE_MASK, buf); + if (ret) { + rtl_dev_warn(hdev, "failed to read ignore mask, will not wake on bluetooth"); + return; + } + + ignore_mask = get_unaligned_le16(buf); + ignore_mask |= RTL_IGNORE_BT_DIS; + put_unaligned_le16(ignore_mask, buf); + + ret = btrtl_vendor_write_reg16(hdev, RTL_IGNORE_MASK, buf); + if (ret) + rtl_dev_warn(hdev, "Failed to enable wake-on-bluetooth. (possibly due to old fw)"); + else + rtl_dev_info(hdev, "wake-on-bluetooth enabled"); +} + void btrtl_set_quirks(struct hci_dev *hdev, struct btrtl_device_info *btrtl_dev) { + btrtl_handle_fw_can_ignore_bt_dis(hdev); + /* Enable controller to do both LE scan and BR/EDR inquiry * simultaneously. */ @@ -1525,6 +1595,7 @@ int btrtl_get_uart_settings(struct hci_dev *hdev, } EXPORT_SYMBOL_GPL(btrtl_get_uart_settings); + MODULE_AUTHOR("Daniel Drake "); MODULE_DESCRIPTION("Bluetooth support for Realtek devices ver " VERSION); MODULE_VERSION(VERSION); diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 76e9bf00f13e..dbf3195f2791 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -411,6 +411,8 @@ static const struct usb_device_id quirks_table[] = { BTUSB_WIDEBAND_SPEECH }, { USB_DEVICE(0x2c7c, 0x0132), .driver_info = BTUSB_QCA_WCN6855 | BTUSB_WIDEBAND_SPEECH }, + { USB_DEVICE(0x2c7c, 0x0137), .driver_info = BTUSB_QCA_WCN6855 | + BTUSB_WIDEBAND_SPEECH }, /* Broadcom BCM2035 */ { USB_DEVICE(0x0a5c, 0x2009), .driver_info = BTUSB_BCM92035 }, diff --git a/drivers/bluetooth/hci_qca.c b/drivers/bluetooth/hci_qca.c index 1222f97800f4..a2b0de45faa9 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2797,6 +2797,7 @@ static const struct acpi_device_id qca_bluetooth_acpi_match[] = { { "DLA16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, { "DLB16390", (kernel_ulong_t)&qca_soc_data_qca6390 }, { "DLB26390", (kernel_ulong_t)&qca_soc_data_qca6390 }, + { "QCOM2066", (kernel_ulong_t)&qca_soc_data_qca2066 }, { }, }; MODULE_DEVICE_TABLE(acpi, qca_bluetooth_acpi_match); diff --git a/drivers/bus/mhi/host/boot.c b/drivers/bus/mhi/host/boot.c index 19c84913cfb9..0bdc668340f1 100644 --- a/drivers/bus/mhi/host/boot.c +++ b/drivers/bus/mhi/host/boot.c @@ -577,10 +577,17 @@ void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl) fw_sz -= mhi_cntrl->sbl_size; } - ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, fw_sz); - if (ret) { - release_firmware(firmware); - goto error_fw_load; + if (mhi_cntrl->fbc_image && fw_sz != mhi_cntrl->prev_fw_sz) { + mhi_free_bhie_table(mhi_cntrl, mhi_cntrl->fbc_image); + mhi_cntrl->fbc_image = NULL; + } + if (!mhi_cntrl->fbc_image) { + ret = mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->fbc_image, fw_sz); + if (ret) { + release_firmware(firmware); + goto error_fw_load; + } + mhi_cntrl->prev_fw_sz = fw_sz; } /* Load the firmware into BHIE vec table */ diff --git a/drivers/bus/mhi/host/init.c b/drivers/bus/mhi/host/init.c index 12dcb1a2753c..b91d6fcc51cb 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -252,7 +252,7 @@ static int mhi_init_irq_setup(struct mhi_controller *mhi_cntrl) return ret; } -static void mhi_deinit_dev_ctxt(struct mhi_controller *mhi_cntrl) +void mhi_deinit_dev_ctxt(struct mhi_controller *mhi_cntrl) { int i; struct mhi_ctxt *mhi_ctxt = mhi_cntrl->mhi_ctxt; @@ -1159,8 +1159,9 @@ int mhi_prepare_for_power_up(struct mhi_controller *mhi_cntrl) /* * Allocate RDDM table for debugging purpose if specified */ - mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->rddm_image, - mhi_cntrl->rddm_size); + if (!mhi_cntrl->rddm_image) + mhi_alloc_bhie_table(mhi_cntrl, &mhi_cntrl->rddm_image, + mhi_cntrl->rddm_size); if (mhi_cntrl->rddm_image) { ret = mhi_rddm_prepare(mhi_cntrl, mhi_cntrl->rddm_image); diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 7b0ee5e3a12d..4475dea5db5e 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -384,6 +384,7 @@ void mhi_ring_chan_db(struct mhi_controller *mhi_cntrl, /* Initialization methods */ int mhi_init_mmio(struct mhi_controller *mhi_cntrl); +void mhi_deinit_dev_ctxt(struct mhi_controller *mhi_cntrl); int mhi_rddm_prepare(struct mhi_controller *mhi_cntrl, struct image_info *img_info); void mhi_fw_load_handler(struct mhi_controller *mhi_cntrl); diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index f799503c8f36..ec847e0b6734 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -1267,10 +1267,19 @@ void mhi_power_down(struct mhi_controller *mhi_cntrl, bool graceful) } EXPORT_SYMBOL_GPL(mhi_power_down); +static void __mhi_power_down_unprepare_keep_dev(struct mhi_controller *mhi_cntrl) +{ + mhi_cntrl->bhi = NULL; + mhi_cntrl->bhie = NULL; + + mhi_deinit_dev_ctxt(mhi_cntrl); +} + void mhi_power_down_keep_dev(struct mhi_controller *mhi_cntrl, bool graceful) { __mhi_power_down(mhi_cntrl, graceful, false); + __mhi_power_down_unprepare_keep_dev(mhi_cntrl); } EXPORT_SYMBOL_GPL(mhi_power_down_keep_dev); diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 68d9df7d2dae..ca1ad4afa38c 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -228,4 +228,11 @@ config EXTCON_RTK_TYPE_C The DHC (Digital Home Hub) RTD series SoC contains a type c module. This driver will detect the status of the type-c port. +config EXTCON_STEAMDECK + tristate "Steam Deck extcon support" + depends on MFD_STEAMDECK + help + Say Y here to enable support of USB Type C cable detection extcon + support on Steam Deck devices + endif diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6482f2bfd661..770dceb94c1d 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -28,3 +28,4 @@ obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o obj-$(CONFIG_EXTCON_RTK_TYPE_C) += extcon-rtk-type-c.o +obj-$(CONFIG_EXTCON_STEAMDECK) += extcon-steamdeck.o diff --git a/drivers/extcon/extcon-steamdeck.c b/drivers/extcon/extcon-steamdeck.c new file mode 100644 index 000000000000..74f190adc8ea --- /dev/null +++ b/drivers/extcon/extcon-steamdeck.c @@ -0,0 +1,180 @@ + +#include +#include +#include + +#define ACPI_STEAMDECK_NOTIFY_STATUS 0x80 + +/* 0 - port connected, 1 -port disconnected */ +#define ACPI_STEAMDECK_PORT_CONNECT BIT(0) +/* 0 - Upstream Facing Port, 1 - Downdstream Facing Port */ +#define ACPI_STEAMDECK_CUR_DATA_ROLE BIT(3) +/* + * Debouncing delay to allow negotiation process to settle. 2s value + * was arrived at via trial and error. + */ +#define STEAMDECK_ROLE_SWITCH_DELAY (msecs_to_jiffies(2000)) + +struct steamdeck_extcon { + struct acpi_device *adev; + struct delayed_work role_work; + struct extcon_dev *edev; + struct device *dev; +}; + +static int steamdeck_read_pdcs(struct steamdeck_extcon *sd, unsigned long long *pdcs) +{ + acpi_status status; + + status = acpi_evaluate_integer(sd->adev->handle, "PDCS", NULL, pdcs); + if (ACPI_FAILURE(status)) { + dev_err(sd->dev, "PDCS evaluation failed: %s\n", + acpi_format_exception(status)); + return -EIO; + } + + return 0; +} + +static void steamdeck_usb_role_work(struct work_struct *work) +{ + struct steamdeck_extcon *sd = + container_of(work, struct steamdeck_extcon, role_work.work); + unsigned long long pdcs; + bool usb_host; + + if (steamdeck_read_pdcs(sd, &pdcs)) + return; + + /* + * We only care about these two + */ + pdcs &= ACPI_STEAMDECK_PORT_CONNECT | ACPI_STEAMDECK_CUR_DATA_ROLE; + + /* + * For "connect" events our role is determined by a bit in + * PDCS, for "disconnect" we switch to being a gadget + * unconditionally. The thinking for the latter is we don't + * want to start acting as a USB host until we get + * confirmation from the firmware that we are a USB host + */ + usb_host = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ? + pdcs & ACPI_STEAMDECK_CUR_DATA_ROLE : false; + + dev_dbg(sd->dev, "USB role is %s\n", usb_host ? "host" : "device"); + WARN_ON(extcon_set_state_sync(sd->edev, EXTCON_USB_HOST, + usb_host)); + +} + +static void steamdeck_notify(acpi_handle handle, u32 event, void *context) +{ + struct device *dev = context; + struct steamdeck_extcon *sd = dev_get_drvdata(dev); + unsigned long long pdcs; + unsigned long delay; + + switch (event) { + case ACPI_STEAMDECK_NOTIFY_STATUS: + if (steamdeck_read_pdcs(sd, &pdcs)) + return; + /* + * We process "disconnect" events immediately and + * "connect" events with a delay to give the HW time + * to settle. For example attaching USB hub (at least + * for HW used for testing) will generate intermediary + * event with "host" bit not set, followed by the one + * that does have it set. + */ + delay = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ? + STEAMDECK_ROLE_SWITCH_DELAY : 0; + + queue_delayed_work(system_long_wq, &sd->role_work, delay); + break; + default: + dev_warn(dev, "Unsupported event [0x%x]\n", event); + } +} + +static void steamdeck_remove_notify_handler(void *data) +{ + struct steamdeck_extcon *sd = data; + + acpi_remove_notify_handler(sd->adev->handle, ACPI_DEVICE_NOTIFY, + steamdeck_notify); + cancel_delayed_work_sync(&sd->role_work); +} + +static const unsigned int steamdeck_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_ACA, + EXTCON_NONE, +}; + +static int steamdeck_extcon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct steamdeck_extcon *sd; + acpi_status status; + int ret; + + sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + + INIT_DELAYED_WORK(&sd->role_work, steamdeck_usb_role_work); + platform_set_drvdata(pdev, sd); + sd->adev = ACPI_COMPANION(dev->parent); + sd->dev = dev; + sd->edev = devm_extcon_dev_allocate(dev, steamdeck_extcon_cable); + if (IS_ERR(sd->edev)) + return PTR_ERR(sd->edev); + + ret = devm_extcon_dev_register(dev, sd->edev); + if (ret < 0) { + dev_err(dev, "Failed to register extcon device: %d\n", ret); + return ret; + } + + /* + * Set initial role value + */ + queue_delayed_work(system_long_wq, &sd->role_work, 0); + flush_delayed_work(&sd->role_work); + + status = acpi_install_notify_handler(sd->adev->handle, + ACPI_DEVICE_NOTIFY, + steamdeck_notify, + dev); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Error installing ACPI notify handler\n"); + return -EIO; + } + + ret = devm_add_action_or_reset(dev, steamdeck_remove_notify_handler, + sd); + return ret; +} + +static const struct platform_device_id steamdeck_extcon_id_table[] = { + { .name = "steamdeck-extcon" }, + {} +}; +MODULE_DEVICE_TABLE(platform, steamdeck_extcon_id_table); + +static struct platform_driver steamdeck_extcon_driver = { + .probe = steamdeck_extcon_probe, + .driver = { + .name = "steamdeck-extcon", + }, + .id_table = steamdeck_extcon_id_table, +}; +module_platform_driver(steamdeck_extcon_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Steam Deck extcon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 0dd8e2375c4d..1239442e15c3 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -51,6 +51,7 @@ #include #include #include +#include #include #include "dm_pp_interface.h" @@ -1067,6 +1068,7 @@ struct amdgpu_device { int asic_reset_res; struct work_struct xgmi_reset_work; + struct work_struct gpu_reset_event_work; struct list_head reset_list; long gfx_timeout; @@ -1102,6 +1104,7 @@ struct amdgpu_device { bool barrier_has_auto_waitcnt; struct amdgpu_reset_control *reset_cntl; + struct drm_reset_event reset_event_info; uint32_t ip_versions[MAX_HWIP][HWIP_MAX_INSTANCE]; bool ram_is_direct_mapped; @@ -1126,6 +1129,8 @@ struct amdgpu_device { /* Mask of active clusters */ uint32_t aid_mask; + bool csib_initialized; + /* Debug */ bool debug_vm; bool debug_largebar; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c index e84b75c03c6f..d3d3cbe85ddc 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c @@ -84,6 +84,7 @@ #include #include +#include #if IS_ENABLED(CONFIG_X86) #include @@ -3529,6 +3530,17 @@ bool amdgpu_device_has_dc_support(struct amdgpu_device *adev) return amdgpu_device_asic_has_dc_support(adev->pdev, adev->asic_type); } +static void amdgpu_device_reset_event_func(struct work_struct *__work) +{ + struct amdgpu_device *adev = container_of(__work, struct amdgpu_device, + gpu_reset_event_work); + /* + * A GPU reset has happened, inform the userspace and pass the + * reset related information. + */ + drm_sysfs_reset_event(&adev->ddev, &adev->reset_event_info); +} + static void amdgpu_device_xgmi_reset_func(struct work_struct *__work) { struct amdgpu_device *adev = @@ -3872,6 +3884,7 @@ int amdgpu_device_init(struct amdgpu_device *adev, } INIT_WORK(&adev->xgmi_reset_work, amdgpu_device_xgmi_reset_func); + INIT_WORK(&adev->gpu_reset_event_work, amdgpu_device_reset_event_func); amdgpu_coredump_init(adev); @@ -4577,6 +4590,11 @@ int amdgpu_device_suspend(struct drm_device *dev, bool notify_clients) if (amdgpu_sriov_vf(adev)) amdgpu_virt_release_full_gpu(adev, false); +#ifdef CONFIG_HIBERNATE_CALLBACKS + if (adev->in_s4) + dev->dev->power.is_frozen = 1; +#endif + return 0; unwind_evict: @@ -4764,6 +4782,11 @@ int amdgpu_device_resume(struct drm_device *dev, bool notify_clients) if (amdgpu_acpi_smart_shift_update(adev, AMDGPU_SS_DEV_D0)) dev_warn(adev->dev, "smart shift update failed\n"); +#ifdef CONFIG_HIBERNATE_CALLBACKS + if (adev->in_s4) + dev->dev->power.is_frozen = 0; +#endif + return 0; } @@ -5295,6 +5318,20 @@ int amdgpu_device_reinit_after_reset(struct amdgpu_reset_context *reset_context) if (!test_bit(AMDGPU_SKIP_COREDUMP, &reset_context->flags)) amdgpu_coredump(tmp_adev, false, vram_lost, reset_context->job); + if (reset_context->job && reset_context->job->vm) { + tmp_adev->reset_event_info.pid = + reset_context->job->vm->task_info->task.pid; + memset(tmp_adev->reset_event_info.pname, 0, TASK_COMM_LEN); + strcpy(tmp_adev->reset_event_info.pname, + reset_context->job->vm->task_info->process_name); + } else { + tmp_adev->reset_event_info.pid = 0; + memset(tmp_adev->reset_event_info.pname, 0, TASK_COMM_LEN); + } + + tmp_adev->reset_event_info.flags = vram_lost; + schedule_work(&tmp_adev->gpu_reset_event_work); + if (vram_lost) { dev_info( tmp_adev->dev, diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ec4225c36e63..8c27f585c5a0 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -2723,7 +2723,7 @@ static int amdgpu_pmops_thaw(struct device *dev) if (console_suspend_enabled && !pm_hibernate_is_recovering() && !pm_hibernation_mode_is_suspend()) - return 0; + return -EBUSY; return amdgpu_device_resume(drm_dev, true); } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c index a6419246e9c2..09d4a76fbda8 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ih.c @@ -210,6 +210,7 @@ int amdgpu_ih_process(struct amdgpu_device *adev, struct amdgpu_ih_ring *ih) { unsigned int count; u32 wptr; + int i; if (!ih->enabled || adev->shutdown) return IRQ_NONE; @@ -239,13 +240,27 @@ int amdgpu_ih_process(struct amdgpu_device *adev, struct amdgpu_ih_ring *ih) if (!ih->overflow) goto restart_ih; - if (ih->overflow) + if (ih->overflow) { if (amdgpu_sriov_runtime(adev)) WARN_ONCE(!amdgpu_reset_domain_schedule(adev->reset_domain, &adev->virt.flr_work), "Failed to queue work! at %s", __func__); + /* If the ring buffer overflowed, we might have lost some fence + * signal interrupts. Check if there was any activity so the signal + * doesn't get lost. + */ + for (i = 0; i < AMDGPU_MAX_RINGS; ++i) { + struct amdgpu_ring *ring = adev->rings[i]; + + if (!ring || !ring->fence_drv.initialized) + continue; + amdgpu_fence_process(ring); + } + ih->overflow = false; + } + return IRQ_HANDLED; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index 8069fc41cc7f..f04c1be0f372 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -437,8 +437,6 @@ struct amdgpu_mode_info { struct drm_property *regamma_tf_property; }; -#define AMDGPU_MAX_BL_LEVEL 0xFF - struct amdgpu_backlight_privdata { struct amdgpu_encoder *encoder; uint8_t negative; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c index b97fa35bac23..94c647733041 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c @@ -454,8 +454,6 @@ bool amdgpu_ring_soft_recovery(struct amdgpu_ring *ring, unsigned int vmid, ktime_t deadline; bool ret; - deadline = ktime_add_us(ktime_get(), 10000); - if (amdgpu_sriov_vf(ring->adev) || !ring->funcs->soft_recovery || !fence) return false; @@ -464,6 +462,7 @@ bool amdgpu_ring_soft_recovery(struct amdgpu_ring *ring, unsigned int vmid, dma_fence_set_error(fence, -ENODATA); dma_fence_unlock_irqrestore(fence, flags); + deadline = ktime_add_us(ktime_get(), 10000); while (!dma_fence_is_signaled(fence) && ktime_to_ns(ktime_sub(deadline, ktime_get())) > 0) ring->funcs->soft_recovery(ring, vmid); diff --git a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c index a1d05df0ac99..dc4f3a8d0d35 100644 --- a/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c +++ b/drivers/gpu/drm/amd/amdgpu/atombios_encoders.c @@ -39,6 +39,10 @@ #include #include "bif/bif_4_1_d.h" + +/* Maximum backlight level. */ +#define AMDGPU_ATOM_MAX_BL_LEVEL 0xFF + u8 amdgpu_atombios_encoder_get_backlight_level_from_reg(struct amdgpu_device *adev) { @@ -127,8 +131,8 @@ static u8 amdgpu_atombios_encoder_backlight_level(struct backlight_device *bd) /* Convert brightness to hardware level */ if (bd->props.brightness < 0) level = 0; - else if (bd->props.brightness > AMDGPU_MAX_BL_LEVEL) - level = AMDGPU_MAX_BL_LEVEL; + else if (bd->props.brightness > AMDGPU_ATOM_MAX_BL_LEVEL) + level = AMDGPU_ATOM_MAX_BL_LEVEL; else level = bd->props.brightness; @@ -198,7 +202,7 @@ void amdgpu_atombios_encoder_init_backlight(struct amdgpu_encoder *amdgpu_encode } memset(&props, 0, sizeof(props)); - props.max_brightness = AMDGPU_MAX_BL_LEVEL; + props.max_brightness = AMDGPU_ATOM_MAX_BL_LEVEL; props.type = BACKLIGHT_RAW; snprintf(bl_name, sizeof(bl_name), "amdgpu_bl%d", dev->primary->index); diff --git a/drivers/gpu/drm/amd/amdgpu/cik_ih.c b/drivers/gpu/drm/amd/amdgpu/cik_ih.c index 876a3256dba4..e0a4fc674e70 100644 --- a/drivers/gpu/drm/amd/amdgpu/cik_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/cik_ih.c @@ -216,6 +216,7 @@ static u32 cik_ih_get_wptr(struct amdgpu_device *adev, */ tmp &= ~IH_RB_CNTL__WPTR_OVERFLOW_CLEAR_MASK; WREG32(mmIH_RB_CNTL, tmp); + ih->overflow = true; } out: diff --git a/drivers/gpu/drm/amd/amdgpu/cz_ih.c b/drivers/gpu/drm/amd/amdgpu/cz_ih.c index bc7a2e06ab5f..795e52a10779 100644 --- a/drivers/gpu/drm/amd/amdgpu/cz_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/cz_ih.c @@ -227,6 +227,7 @@ static u32 cz_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32(mmIH_RB_CNTL, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c index a9961d504833..cbac8449e5bf 100644 --- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c +++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c @@ -3685,6 +3685,7 @@ static uint64_t gfx_v10_0_get_gpu_clock_counter(struct amdgpu_device *adev); static void gfx_v10_0_select_se_sh(struct amdgpu_device *adev, u32 se_num, u32 sh_num, u32 instance, int xcc_id); static u32 gfx_v10_0_get_wgp_active_bitmap_per_sh(struct amdgpu_device *adev); +static int gfx_v10_0_adev_wait_for_idle(struct amdgpu_device *adev); static int gfx_v10_0_rlc_backdoor_autoload_buffer_init(struct amdgpu_device *adev); static void gfx_v10_0_rlc_backdoor_autoload_buffer_fini(struct amdgpu_device *adev); @@ -6370,7 +6371,7 @@ static int gfx_v10_0_cp_gfx_load_microcode(struct amdgpu_device *adev) return 0; } -static int gfx_v10_0_cp_gfx_start(struct amdgpu_device *adev) +static int gfx_v10_csib_submit(struct amdgpu_device *adev) { struct amdgpu_ring *ring; const struct cs_section_def *sect = NULL; @@ -6378,13 +6379,6 @@ static int gfx_v10_0_cp_gfx_start(struct amdgpu_device *adev) int r, i; int ctx_reg_offset; - /* init the CP */ - WREG32_SOC15(GC, 0, mmCP_MAX_CONTEXT, - adev->gfx.config.max_hw_contexts - 1); - WREG32_SOC15(GC, 0, mmCP_DEVICE_ID, 1); - - gfx_v10_0_cp_gfx_enable(adev, true); - ring = &adev->gfx.gfx_ring[0]; r = amdgpu_ring_alloc(ring, gfx_v10_0_get_csb_size(adev) + 4); if (r) { @@ -6447,6 +6441,25 @@ static int gfx_v10_0_cp_gfx_start(struct amdgpu_device *adev) amdgpu_ring_commit(ring); } + + gfx_v10_0_adev_wait_for_idle(adev); + adev->csib_initialized = true; + + return 0; +}; + +static int gfx_v10_0_cp_gfx_start(struct amdgpu_device *adev) +{ + /* init the CP */ + WREG32_SOC15(GC, 0, mmCP_MAX_CONTEXT, + adev->gfx.config.max_hw_contexts - 1); + WREG32_SOC15(GC, 0, mmCP_DEVICE_ID, 1); + + gfx_v10_0_cp_gfx_enable(adev, true); + + if (!adev->csib_initialized) + gfx_v10_csib_submit(adev); + return 0; } @@ -7621,11 +7634,10 @@ static bool gfx_v10_0_is_idle(struct amdgpu_ip_block *ip_block) return true; } -static int gfx_v10_0_wait_for_idle(struct amdgpu_ip_block *ip_block) +static int gfx_v10_0_adev_wait_for_idle(struct amdgpu_device *adev) { unsigned int i; u32 tmp; - struct amdgpu_device *adev = ip_block->adev; for (i = 0; i < adev->usec_timeout; i++) { /* read MC_STATUS */ @@ -7639,6 +7651,13 @@ static int gfx_v10_0_wait_for_idle(struct amdgpu_ip_block *ip_block) return -ETIMEDOUT; } +static int gfx_v10_0_wait_for_idle(struct amdgpu_ip_block *ip_block) +{ + struct amdgpu_device *adev = ip_block->adev; + + return gfx_v10_0_adev_wait_for_idle(adev); +} + static int gfx_v10_0_soft_reset(struct amdgpu_ip_block *ip_block) { u32 grbm_soft_reset = 0; diff --git a/drivers/gpu/drm/amd/amdgpu/iceland_ih.c b/drivers/gpu/drm/amd/amdgpu/iceland_ih.c index 01cadf898c00..23079fa29de4 100644 --- a/drivers/gpu/drm/amd/amdgpu/iceland_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/iceland_ih.c @@ -226,6 +226,7 @@ static u32 iceland_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32(mmIH_RB_CNTL, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/amdgpu/ih_v6_0.c b/drivers/gpu/drm/amd/amdgpu/ih_v6_0.c index 333e9c30c091..7fbf51fa2d41 100644 --- a/drivers/gpu/drm/amd/amdgpu/ih_v6_0.c +++ b/drivers/gpu/drm/amd/amdgpu/ih_v6_0.c @@ -471,6 +471,7 @@ static u32 ih_v6_0_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32_NO_KIQ(ih_regs->ih_rb_cntl, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); } diff --git a/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c b/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c index 699c274d357e..5fd3d4937d53 100644 --- a/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c +++ b/drivers/gpu/drm/amd/amdgpu/ih_v6_1.c @@ -439,6 +439,7 @@ static u32 ih_v6_1_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32_NO_KIQ(ih_regs->ih_rb_cntl, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/amdgpu/navi10_ih.c b/drivers/gpu/drm/amd/amdgpu/navi10_ih.c index 4cd325149b63..167359da71b6 100644 --- a/drivers/gpu/drm/amd/amdgpu/navi10_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/navi10_ih.c @@ -447,6 +447,7 @@ static u32 navi10_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32_NO_KIQ(ih_regs->ih_rb_cntl, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); } diff --git a/drivers/gpu/drm/amd/amdgpu/si_ih.c b/drivers/gpu/drm/amd/amdgpu/si_ih.c index 66f650f87243..583766bba409 100644 --- a/drivers/gpu/drm/amd/amdgpu/si_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/si_ih.c @@ -132,6 +132,7 @@ static u32 si_ih_get_wptr(struct amdgpu_device *adev, */ tmp &= ~IH_RB_CNTL__WPTR_OVERFLOW_CLEAR_MASK; WREG32(IH_RB_CNTL, tmp); + ih->overflow = true; } out: diff --git a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c index ee8038df17e3..b386d74944c6 100644 --- a/drivers/gpu/drm/amd/amdgpu/tonga_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/tonga_ih.c @@ -230,6 +230,7 @@ static u32 tonga_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32(mmIH_RB_CNTL, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/amdgpu/vega10_ih.c b/drivers/gpu/drm/amd/amdgpu/vega10_ih.c index eb16916c6473..77728a4e4a4c 100644 --- a/drivers/gpu/drm/amd/amdgpu/vega10_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/vega10_ih.c @@ -377,6 +377,7 @@ static u32 vega10_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32_NO_KIQ(ih_regs->ih_rb_cntl, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/amdgpu/vega20_ih.c b/drivers/gpu/drm/amd/amdgpu/vega20_ih.c index 85846fd08ce4..fa9cca935d3c 100644 --- a/drivers/gpu/drm/amd/amdgpu/vega20_ih.c +++ b/drivers/gpu/drm/amd/amdgpu/vega20_ih.c @@ -461,6 +461,7 @@ static u32 vega20_ih_get_wptr(struct amdgpu_device *adev, */ tmp = REG_SET_FIELD(tmp, IH_RB_CNTL, WPTR_OVERFLOW_CLEAR, 0); WREG32_NO_KIQ(ih_regs->ih_rb_cntl, tmp); + ih->overflow = true; out: return (wptr & ih->ptr_mask); diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index 00de201cc524..827de22fc74a 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -161,6 +161,9 @@ MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); #define FIRMWARE_DCN_42B_DMUB "amdgpu/dcn_4_2_1_dmcub.bin" MODULE_FIRMWARE(FIRMWARE_DCN_42B_DMUB); +/* Maximum backlight level. */ +#define AMDGPU_MAX_BL_LEVEL 0xFFFF + /** * DOC: overview * @@ -4197,6 +4200,8 @@ void amdgpu_dm_update_connector_after_detect( struct drm_connector *connector = &aconnector->base; struct dc_sink *sink __free(sink_release) = NULL; struct drm_device *dev = connector->dev; + struct amdgpu_device *adev = drm_to_adev(dev); + int inst; /* MST handled by drm_mst framework */ if (aconnector->mst_mgr.mst_state == true) @@ -4330,6 +4335,13 @@ void amdgpu_dm_update_connector_after_detect( /* Set CP to DESIRED if it was ENABLED, so we can re-enable it again on hotplug */ if (connector->state->content_protection == DRM_MODE_CONTENT_PROTECTION_ENABLED) connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED; + + mutex_lock(&adev->dm.audio_lock); + inst = aconnector->audio_inst; + aconnector->audio_inst = -1; + mutex_unlock(&adev->dm.audio_lock); + if (inst != -1) + amdgpu_dm_audio_eld_notify(adev, inst); } update_subconnector_property(aconnector); @@ -5397,6 +5409,7 @@ static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, struct amdgpu_dm_backlight_caps *caps; struct dc_link *link; u32 brightness = 0; + u32 prev_brightness; bool rc = false, reallow_idle = false; struct drm_connector *connector; struct dc_stream_state *stream; @@ -5419,7 +5432,10 @@ static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, amdgpu_dm_update_backlight_caps(dm, bl_idx); caps = &dm->backlight_caps[bl_idx]; + prev_brightness = dm->brightness[bl_idx]; dm->brightness[bl_idx] = user_brightness; + dm->actual_brightness[bl_idx] = user_brightness; + /* update scratch register */ if (bl_idx == 0) amdgpu_atombios_scratch_regs_set_backlight_level(dm->adev, dm->brightness[bl_idx]); @@ -5482,8 +5498,8 @@ static void amdgpu_dm_backlight_set_level(struct amdgpu_display_manager *dm, mutex_unlock(&dm->dc_lock); - if (rc) - dm->actual_brightness[bl_idx] = user_brightness; + if (!rc) + dm->actual_brightness[bl_idx] = prev_brightness; } static int amdgpu_dm_backlight_update_status(struct backlight_device *bd) @@ -5599,6 +5615,7 @@ amdgpu_dm_register_backlight_device(struct amdgpu_dm_connector *aconnector) backlight_device_register(bl_name, aconnector->base.kdev, dm, &amdgpu_dm_backlight_ops, &props); dm->brightness[aconnector->bl_idx] = props.brightness; + dm->actual_brightness[aconnector->bl_idx] = props.brightness; if (IS_ERR(dm->backlight_dev[aconnector->bl_idx])) { drm_err(drm, "DM: Backlight registration failed!\n"); @@ -6992,11 +7009,7 @@ static void fill_stream_properties_from_drm_display_mode( timing_out->v_border_top = 0; timing_out->v_border_bottom = 0; /* TODO: un-hardcode */ - if (drm_mode_is_420_only(info, mode_in) - && (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A || - stream->signal == SIGNAL_TYPE_HDMI_FRL) - && aconnector - && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) + if (drm_mode_is_420_only(info, mode_in)) timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420; else if (drm_mode_is_420_also(info, mode_in) && aconnector @@ -9306,7 +9319,7 @@ static int amdgpu_dm_connector_get_modes(struct drm_connector *connector) } } else { amdgpu_dm_connector_ddc_get_modes(connector, drm_edid); - if (encoder) + if (encoder && connector->connector_type != DRM_MODE_CONNECTOR_eDP) amdgpu_dm_connector_add_common_modes(encoder, connector); amdgpu_dm_connector_add_freesync_modes(connector, drm_edid); } @@ -10362,6 +10375,13 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_commit *state, struct dc_stream_update stream_update; } *bundle; + /* + * Hack: Make unconditional. + * TODO: rework with flag to gate this or keep in in active period + * based on userland selection. + */ + vrr_active = true; + bundle = kzalloc_obj(*bundle); if (!bundle) { diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c index 4f99f6a01d42..bfa13259f02f 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c @@ -1461,7 +1461,7 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, const struct drm_color_lut *degamma_lut; enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; uint32_t degamma_size; - bool has_degamma_lut; + bool has_degamma_lut, is_subsampled_format; int ret; degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, @@ -1491,12 +1491,20 @@ __set_dm_plane_degamma(struct drm_plane_state *plane_state, if (ret) return ret; } else { - dc_plane_state->in_transfer_func.type = - TF_TYPE_PREDEFINED; + /* Check if format requires post-scale color processing (subsampled formats) */ + is_subsampled_format = (dc_plane_state->format >= SURFACE_PIXEL_FORMAT_VIDEO_BEGIN && + dc_plane_state->format < SURFACE_PIXEL_FORMAT_SUBSAMPLE_END); + + dc_plane_state->in_transfer_func.type = TF_TYPE_PREDEFINED; if (!mod_color_calculate_degamma_params(color_caps, - &dc_plane_state->in_transfer_func, NULL, false)) + &dc_plane_state->in_transfer_func, + NULL, + is_subsampled_format)) { + drm_err(plane_state->state->dev, + "Failed to calculate degamma params.\n"); return -ENOMEM; + } } return 0; } diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c index bfd5515c2f4f..da07c6e05281 100644 --- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c +++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_cm_common.c @@ -303,6 +303,198 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, return true; } +bool cm3_helper_translate_curve_to_degamma_hw_format( + const struct dc_transfer_func *output_tf, + struct pwl_params *lut_params) +{ + struct curve_points3 *corner_points; + struct pwl_result_data *rgb_resulted; + struct pwl_result_data *rgb; + struct pwl_result_data *rgb_plus_1; + + int32_t region_start, region_end; + int32_t i; + uint32_t j, k, seg_distr[MAX_REGIONS_NUMBER], increment, start_index, hw_points; + + if (output_tf == NULL || lut_params == NULL || output_tf->type == TF_TYPE_BYPASS) + return false; + + corner_points = lut_params->corner_points; + rgb_resulted = lut_params->rgb_resulted; + hw_points = 0; + + memset(lut_params, 0, sizeof(struct pwl_params)); + memset(seg_distr, 0, sizeof(seg_distr)); + + if (output_tf->tf == TRANSFER_FUNCTION_PQ || TRANSFER_FUNCTION_SRGB) { + /* 9 segments + * segments are from 2^-8 to 0 + */ + const uint8_t SEG_COUNT = 9; + seg_distr[0] = 0; // Since we only have one point in last region + for (k = 1; k < SEG_COUNT; k++) + seg_distr[k] = k - 1; // Depends upon the regions' points 2^(n-1); seg_distr = n + + region_start = -SEG_COUNT; + region_end = 0; + } else { + /* 12 segments + * segments are from 2^-11 to 2^0 + * There are less than 256 points, for optimization + */ + const uint8_t SEG_COUNT = 12; + + for (i = 0; i < SEG_COUNT; i++) + seg_distr[i] = 4; + + region_start = -SEG_COUNT; + region_end = 0; + } + + for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) + seg_distr[i] = -1; + + for (k = 0; k < MAX_REGIONS_NUMBER; k++) { + if (seg_distr[k] != -1) + hw_points += (1 << seg_distr[k]); + } + + j = 0; + for (k = 0; k < (region_end - region_start); k++) { + increment = (NUMBER_SW_SEGMENTS << 4) / (1 << seg_distr[k]); + start_index = (region_start + k + MAX_LOW_POINT) * + NUMBER_SW_SEGMENTS; + for (i = (start_index << 4); i < (start_index << 4) + (NUMBER_SW_SEGMENTS << 4); + i += increment) { + struct fixed31_32 in_plus_one, in; + struct fixed31_32 value, red_value, green_value, blue_value; + uint32_t t = i & 0xf; + + if (j == hw_points - 1) + break; + if ((i >> 4) >= TRANSFER_FUNC_POINTS) + return false; + + in_plus_one = output_tf->tf_pts.red[(i >> 4) + 1]; + in = output_tf->tf_pts.red[i >> 4]; + value = dc_fixpt_sub(in_plus_one, in); + value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); + value = dc_fixpt_add(in, value); + red_value = value; + + in_plus_one = output_tf->tf_pts.green[(i >> 4) + 1]; + in = output_tf->tf_pts.green[i >> 4]; + value = dc_fixpt_sub(in_plus_one, in); + value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); + value = dc_fixpt_add(in, value); + green_value = value; + + in_plus_one = output_tf->tf_pts.blue[(i >> 4) + 1]; + in = output_tf->tf_pts.blue[i >> 4]; + value = dc_fixpt_sub(in_plus_one, in); + value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); + value = dc_fixpt_add(in, value); + blue_value = value; + + rgb_resulted[j].red = red_value; + rgb_resulted[j].green = green_value; + rgb_resulted[j].blue = blue_value; + j++; + } + } + + /* last point */ + start_index = (region_end + MAX_LOW_POINT) * NUMBER_SW_SEGMENTS; + rgb_resulted[hw_points - 1].red = output_tf->tf_pts.red[start_index]; + rgb_resulted[hw_points - 1].green = output_tf->tf_pts.green[start_index]; + rgb_resulted[hw_points - 1].blue = output_tf->tf_pts.blue[start_index]; + + corner_points[0].red.x = dc_fixpt_pow(dc_fixpt_from_int(2), + dc_fixpt_from_int(region_start)); + corner_points[0].green.x = corner_points[0].red.x; + corner_points[0].blue.x = corner_points[0].red.x; + + corner_points[1].red.x = dc_fixpt_pow(dc_fixpt_from_int(2), + dc_fixpt_from_int(region_end)); + corner_points[1].green.x = corner_points[1].red.x; + corner_points[1].blue.x = corner_points[1].red.x; + + corner_points[0].red.y = rgb_resulted[0].red; + corner_points[0].green.y = rgb_resulted[0].green; + corner_points[0].blue.y = rgb_resulted[0].blue; + + /* see comment above, m_arrPoints[1].y should be the Y value for the + * region end (m_numOfHwPoints), not last HW point(m_numOfHwPoints - 1) + */ + corner_points[1].red.y = rgb_resulted[hw_points - 1].red; + corner_points[1].green.y = rgb_resulted[hw_points - 1].green; + corner_points[1].blue.y = rgb_resulted[hw_points - 1].blue; + corner_points[1].red.slope = dc_fixpt_zero; + corner_points[1].green.slope = dc_fixpt_zero; + corner_points[1].blue.slope = dc_fixpt_zero; + + if (output_tf->tf == TRANSFER_FUNCTION_PQ) { + /* for PQ, we want to have a straight line from last HW X point, + * and the slope to be such that we hit 1.0 at 10000 nits. + */ + const struct fixed31_32 end_value = + dc_fixpt_from_int(125); + + corner_points[1].red.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].red.y), + dc_fixpt_sub(end_value, corner_points[1].red.x)); + corner_points[1].green.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].green.y), + dc_fixpt_sub(end_value, corner_points[1].green.x)); + corner_points[1].blue.slope = dc_fixpt_div( + dc_fixpt_sub(dc_fixpt_one, corner_points[1].blue.y), + dc_fixpt_sub(end_value, corner_points[1].blue.x)); + } + + lut_params->hw_points_num = hw_points; + + k = 0; + for (i = 1; i < MAX_REGIONS_NUMBER; i++) { + if (seg_distr[k] != -1) { + lut_params->arr_curve_points[k].segments_num = + seg_distr[k]; + lut_params->arr_curve_points[i].offset = + lut_params->arr_curve_points[k].offset + (1 << seg_distr[k]); + } + k++; + } + + if (seg_distr[k] != -1) + lut_params->arr_curve_points[k].segments_num = seg_distr[k]; + + rgb = rgb_resulted; + rgb_plus_1 = rgb_resulted + 1; + + i = 1; + while (i != hw_points + 1) { + if (dc_fixpt_lt(rgb_plus_1->red, rgb->red)) + rgb_plus_1->red = rgb->red; + if (dc_fixpt_lt(rgb_plus_1->green, rgb->green)) + rgb_plus_1->green = rgb->green; + if (dc_fixpt_lt(rgb_plus_1->blue, rgb->blue)) + rgb_plus_1->blue = rgb->blue; + + rgb->delta_red = dc_fixpt_sub(rgb_plus_1->red, rgb->red); + rgb->delta_green = dc_fixpt_sub(rgb_plus_1->green, rgb->green); + rgb->delta_blue = dc_fixpt_sub(rgb_plus_1->blue, rgb->blue); + + ++rgb_plus_1; + ++rgb; + ++i; + } + + cm3_helper_convert_to_custom_float(rgb_resulted, + lut_params->corner_points, + hw_points, false); + + return true; +} + bool cm3_helper_convert_to_custom_float( struct pwl_result_data *rgb_resulted, struct curve_points3 *corner_points, diff --git a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h index 95f9318a54ef..c23dc1bb29bf 100644 --- a/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h +++ b/drivers/gpu/drm/amd/display/dc/dwb/dcn30/dcn30_cm_common.h @@ -63,6 +63,10 @@ bool cm3_helper_translate_curve_to_hw_format(struct dc_context *ctx, const struct dc_transfer_func *output_tf, struct pwl_params *lut_params, bool fixpoint); +bool cm3_helper_translate_curve_to_degamma_hw_format( + const struct dc_transfer_func *output_tf, + struct pwl_params *lut_params); + bool cm3_helper_convert_to_custom_float( struct pwl_result_data *rgb_resulted, struct curve_points3 *corner_points, diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c index f5b3a2eff94a..c31d9bd3fd87 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dce110/dce110_hwseq.c @@ -97,6 +97,8 @@ #define FN(reg_name, field_name) \ hws->shifts->field_name, hws->masks->field_name +static const uint8_t DP_SINK_BRANCH_DEV_NAME_KT50X0[] = "KT50X0!"; + struct dce110_hw_seq_reg_offsets { uint32_t crtc; }; @@ -2128,9 +2130,29 @@ void dce110_enable_accelerated_mode(struct dc *dc, struct dc_state *context) break; } } - // We are trying to enable eDP, don't power down VDD - if (can_apply_edp_fast_boot) + + /* + * TO-DO: So far the code logic below only addresses single eDP case. + * For dual eDP case, there are a few things that need to be + * implemented first: + * + * 1. Change the fastboot logic above, so eDP link[0 or 1]'s + * stream[0 or 1] will all be checked. + * + * 2. Change keep_edp_vdd_on to an array, and maintain keep_edp_vdd_on + * for each eDP. + * + * Once above 2 things are completed, we can then change the logic below + * correspondingly, so dual eDP case will be fully covered. + */ + + // We are trying to enable eDP, don't power down VDD if eDP stream is existing + if ((edp_stream_num == 1 && edp_streams[0] != NULL) || can_apply_edp_fast_boot) { keep_edp_vdd_on = true; + DC_LOG_EVENT_LINK_TRAINING("Keep eDP Vdd on\n"); + } else { + DC_LOG_EVENT_LINK_TRAINING("No eDP stream enabled, turn eDP Vdd off\n"); + } } // Check seamless boot support @@ -3514,6 +3536,13 @@ void dce110_enable_dp_link_output( link->dc->res_pool->dp_clock_source; const struct link_hwss *link_hwss = get_link_hwss(link, link_res); unsigned int i; + if (link->ctx->dce_version == DCN_VERSION_3_01 && + link->dpcd_caps.sink_dev_id == DP_BRANCH_DEVICE_ID_0060AD && + memcmp(&link->dpcd_caps.branch_dev_name, + DP_SINK_BRANCH_DEV_NAME_KT50X0, + sizeof(link->dpcd_caps.branch_dev_name)) == 0) { + msleep(2000); + } /* * Add the logic to extract BOTH power up and power down sequences diff --git a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c index a3242e7521a4..aec54687725a 100644 --- a/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c +++ b/drivers/gpu/drm/amd/display/dc/hwss/dcn32/dcn32_hwseq.c @@ -493,9 +493,8 @@ bool dcn32_set_mcm_luts( if (plane_state->blend_tf.type == TF_TYPE_HWPWL) lut_params = &plane_state->blend_tf.pwl; else if (plane_state->blend_tf.type == TF_TYPE_DISTRIBUTED_POINTS) { - result = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->blend_tf, - &dpp_base->regamma_params, false); + result = cm3_helper_translate_curve_to_degamma_hw_format(&plane_state->blend_tf, + &dpp_base->regamma_params); if (!result) return result; @@ -509,9 +508,9 @@ bool dcn32_set_mcm_luts( lut_params = &plane_state->in_shaper_func.pwl; else if (plane_state->in_shaper_func.type == TF_TYPE_DISTRIBUTED_POINTS) { // TODO: dpp_base replace - rval = cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_shaper_func, - &dpp_base->shaper_params, true); + rval = cm_helper_translate_curve_to_hw_format(plane_state->ctx, + &plane_state->in_shaper_func, + &dpp_base->shaper_params, true); lut_params = rval ? &dpp_base->shaper_params : NULL; } @@ -551,9 +550,8 @@ bool dcn32_set_input_transfer_func(struct dc *dc, if (plane_state->in_transfer_func.type == TF_TYPE_HWPWL) params = &plane_state->in_transfer_func.pwl; else if (plane_state->in_transfer_func.type == TF_TYPE_DISTRIBUTED_POINTS && - cm3_helper_translate_curve_to_hw_format(plane_state->ctx, - &plane_state->in_transfer_func, - &dpp_base->degamma_params, false)) + cm3_helper_translate_curve_to_degamma_hw_format(&plane_state->in_transfer_func, + &dpp_base->degamma_params)) params = &dpp_base->degamma_params; dpp_base->funcs->dpp_program_gamcor_lut(dpp_base, params); diff --git a/drivers/gpu/drm/amd/display/dc/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 29dbb5e410d8..75dcc39126bc 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -652,7 +652,7 @@ static bool wait_for_entering_dp_alt_mode(struct dc_link *link) * 500 microseconds * 400 tries us 200 ms **/ unsigned int sleep_time_in_microseconds = 500; - unsigned int tries_allowed = 400; + unsigned int tries_allowed = 1000; bool is_in_alt_mode; unsigned long long enter_timestamp; unsigned long long finish_timestamp; diff --git a/drivers/gpu/drm/amd/display/dc/link/link_validation.c b/drivers/gpu/drm/amd/display/dc/link/link_validation.c index d7bd352880b3..1b7ab74b6d88 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_validation.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_validation.c @@ -36,6 +36,8 @@ #define DC_LOGGER_INIT(logger) +static const uint8_t DP_SINK_BRANCH_DEV_NAME_KT50X0[] = "KT50X0!"; + static uint32_t get_tmds_output_pixel_clock_100hz(const struct dc_crtc_timing *timing) { @@ -482,6 +484,15 @@ static bool dp_validate_mode_timing( timing->v_addressable == (uint32_t) 480) return true; + if (link->ctx->dce_version == DCN_VERSION_3_01 && + link->dpcd_caps.sink_dev_id == DP_BRANCH_DEVICE_ID_0060AD && + memcmp(&link->dpcd_caps.branch_dev_name, + DP_SINK_BRANCH_DEV_NAME_KT50X0, + sizeof(link->dpcd_caps.branch_dev_name)) == 0) { + if (timing->pix_clk_100hz / 10 >= (uint32_t) 1200000) + return false; /* KT50X0 does not support Pxl clock >= 1200MHz */ + } + link_setting = dp_get_verified_link_cap(link); /* TODO: DYNAMIC_VALIDATION needs to be implemented */ diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c index 4ecf448d15b6..a7c2f25a4fa3 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn301/dcn301_resource.c @@ -1474,9 +1474,9 @@ static bool dcn301_resource_construct( dc->caps.max_cursor_size = 256; dc->caps.min_horizontal_blanking_period = 80; dc->caps.dmdata_alloc_size = 2048; - dc->caps.max_slave_planes = 2; - dc->caps.max_slave_yuv_planes = 2; - dc->caps.max_slave_rgb_planes = 2; + dc->caps.max_slave_planes = 3; + dc->caps.max_slave_yuv_planes = 3; + dc->caps.max_slave_rgb_planes = 3; dc->caps.is_apu = true; dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c index a11110e304fc..491d3af0b350 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn32/dcn32_resource.c @@ -772,7 +772,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .use_max_lb = true, - .force_disable_subvp = false, + .force_disable_subvp = true, .exit_idle_opt_for_cursor_updates = true, .using_dml2 = false, .using_dml21 = false, // TODO : Temporary for N-1 validation. Remove after N-1 is done. diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c index d1dbcc8ddb71..0d5016cfdf42 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn321/dcn321_resource.c @@ -762,7 +762,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .use_max_lb = true, - .force_disable_subvp = false, + .force_disable_subvp = true, .exit_idle_opt_for_cursor_updates = true, .enable_single_display_2to1_odm_policy = true, diff --git a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c index e3ee792fb2e8..e715dd28c0e0 100644 --- a/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c +++ b/drivers/gpu/drm/amd/display/dc/resource/dcn401/dcn401_resource.c @@ -760,7 +760,7 @@ static const struct dc_debug_options debug_defaults_drv = { } }, .use_max_lb = true, - .force_disable_subvp = false, + .force_disable_subvp = true, .disable_force_pstate_allow_on_hw_release = false, .exit_idle_opt_for_cursor_updates = true, .using_dml2 = true, diff --git a/drivers/gpu/drm/clients/drm_client_setup.c b/drivers/gpu/drm/clients/drm_client_setup.c index 515aceac22b1..37d4549fe31d 100644 --- a/drivers/gpu/drm/clients/drm_client_setup.c +++ b/drivers/gpu/drm/clients/drm_client_setup.c @@ -40,7 +40,7 @@ void drm_client_setup(struct drm_device *dev, const struct drm_format_info *form } #ifdef CONFIG_DRM_FBDEV_EMULATION - if (!strcmp(drm_client_default, "fbdev")) { + if (strcmp(dev->driver->name, "virtio_gpu") && !strcmp(drm_client_default, "fbdev")) { int ret; ret = drm_fbdev_client_setup(dev, format); diff --git a/drivers/gpu/drm/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index e417c7533053..d574b521ada4 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -104,6 +104,21 @@ static const struct drm_get_panel_backlight_quirk drm_panel_min_backlight_quirks .dmi_match_other.value = "ONEXPLAYER F1 EVA-02", .quirk = { .brightness_mask = 3, }, }, + /* Steam Deck models */ + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "Valve", + .dmi_match_other.field = DMI_PRODUCT_NAME, + .dmi_match_other.value = "Jupiter", + .quirk = { .min_brightness = 1, }, + }, + { + .dmi_match.field = DMI_SYS_VENDOR, + .dmi_match.value = "Valve", + .dmi_match_other.field = DMI_PRODUCT_NAME, + .dmi_match_other.value = "Galileo", + .quirk = { .min_brightness = 1, }, + }, }; static bool drm_panel_min_backlight_quirk_matches( diff --git a/drivers/gpu/drm/drm_panel_orientation_quirks.c b/drivers/gpu/drm/drm_panel_orientation_quirks.c index 3a218fb592ce..3bb48c63600c 100644 --- a/drivers/gpu/drm/drm_panel_orientation_quirks.c +++ b/drivers/gpu/drm/drm_panel_orientation_quirks.c @@ -179,6 +179,18 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Win600"), }, .driver_data = (void *)&lcd720x1280_rightside_up, + }, { /* AOKZOE A1 Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AOKZOE A1 Pro"), + }, + .driver_data = (void *)&lcd1200x1920_leftside_up, + }, { /* AOKZOE A2 Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AOKZOE"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AOKZOE A2 Pro"), + }, + .driver_data = (void *)&lcd1200x1920_leftside_up, }, { /* Asus T100HA */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), @@ -203,12 +215,36 @@ static const struct dmi_system_id orientation_data[] = { DMI_MATCH(DMI_PRODUCT_NAME, "AYANEO 2"), }, .driver_data = (void *)&lcd1200x1920_rightside_up, + }, { /* AYA NEO AYANEO 2S */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYANEO 2S"), + }, + .driver_data = (void *)&lcd1200x1920_rightside_up, + }, { /* AYANEO 3 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYANEO 3"), + }, + .driver_data = (void *)&lcd1080x1920_rightside_up, + }, { /* AYA NEO FLIP DS */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "FLIP DS"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, }, { /* AYA NEO 2021 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYADEVICE"), DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "AYA NEO 2021"), }, .driver_data = (void *)&lcd800x1280_rightside_up, + }, { /* AYA NEO 2021 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "NEO 2021"), + }, + .driver_data = (void *)&lcd800x1280_rightside_up, }, { /* AYA NEO AIR */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), @@ -309,6 +345,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-01"), }, .driver_data = (void *)&lcd800x1280_rightside_up, + }, { /* GPD Win Max (2021) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1619-03"), + }, + .driver_data = (void *)&lcd800x1280_rightside_up, }, { /* * GPD Pocket, note that the DMI data is less generic then * it seems, devices with a board-vendor of "AMI Corporation" @@ -364,6 +406,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1617-01") }, .driver_data = (void *)&lcd1080x1920_rightside_up, + }, { /* GPD Pocket 4 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G1628-04"), + }, + .driver_data = (void *)&lcd1600x2560_rightside_up, }, { /* I.T.Works TW891 */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."), @@ -461,6 +509,12 @@ static const struct dmi_system_id orientation_data[] = { DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"), }, .driver_data = (void *)&lcd1600x2560_rightside_up, + }, { /* Mysten SuiPlay0X1 */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Mysten Labs, Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "SuiPlay0X1"), + }, + .driver_data = (void *)&lcd1200x1920_rightside_up, }, { /* Nanote UMPC-01 */ .matches = { DMI_MATCH(DMI_SYS_VENDOR, "RWC CO.,LTD"), @@ -486,6 +540,90 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONE XPLAYER"), }, .driver_data = (void *)&lcd1200x1920_leftside_up, + }, { /* OneXPlayer X1 AMD */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 A"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 i"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 mini (AMD) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1 mini"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Air"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 mini pro (AMD Strix Point) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Mini Pro"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 AMD second variant */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1z"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1 AMD Strix Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Pro"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer X1Pro EVA variant with Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER X1Pro EVA-02"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) LE Red variant */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1 EVA-02"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) Hawk Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1 OLED"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly F1 Pro (OLED) Strix Point */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER F1Pro"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* OneXPlayer OneXFly G1 AMD */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 A"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* OneXPlayer OneXFly G1 Intel */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ONE-NETBOOK"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ONEXPLAYER G1 i"), + }, + .driver_data = (void *)&lcd1600x2560_leftside_up, + }, { /* Zotac Gaming Zone (OLED) */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ZOTAC"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ZOTAC GAMING ZONE"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, }, { /* OrangePi Neo */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OrangePi"), @@ -498,6 +636,18 @@ static const struct dmi_system_id orientation_data[] = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galaxy Book 10.6"), }, .driver_data = (void *)&lcd1280x1920_rightside_up, + }, { /* Tectoy Zeenix Lite */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Tectoy"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Zeenix Lite"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, + }, { /* Tectoy Zeenix Pro */ + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Tectoy"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Zeenix Pro"), + }, + .driver_data = (void *)&lcd1080x1920_leftside_up, }, { /* Valve Steam Deck (Jupiter) */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index ef4e923a8728..57f08accdf80 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c @@ -477,6 +477,37 @@ void drm_sysfs_connector_hotplug_event(struct drm_connector *connector) } EXPORT_SYMBOL(drm_sysfs_connector_hotplug_event); +/** + * drm_sysfs_reset_event - generate a DRM uevent to indicate GPU reset + * @dev: DRM device + * @reset_info: The contextual information about the reset (like PID, flags) + * + * Send a uevent for the DRM device specified by @dev. This informs + * user that a GPU reset has occurred, so that an interested client + * can take any recovery or profiling measure. + */ +void drm_sysfs_reset_event(struct drm_device *dev, struct drm_reset_event *reset_info) +{ + unsigned char pid_str[13]; + unsigned char flags_str[15]; + unsigned char pname_str[TASK_COMM_LEN + 6]; + unsigned char reset_str[] = "RESET=1"; + char *envp[] = { reset_str, pid_str, pname_str, flags_str, NULL }; + + if (!reset_info) { + DRM_WARN("No reset info, not sending the event\n"); + return; + } + + DRM_DEBUG("generating reset event\n"); + + snprintf(pid_str, ARRAY_SIZE(pid_str), "PID=%u", reset_info->pid); + snprintf(pname_str, ARRAY_SIZE(pname_str), "NAME=%s", reset_info->pname); + snprintf(flags_str, ARRAY_SIZE(flags_str), "FLAGS=%u", reset_info->flags); + kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp); +} +EXPORT_SYMBOL(drm_sysfs_reset_event); + /** * drm_sysfs_connector_property_event - generate a DRM uevent for connector * property change diff --git a/drivers/gpu/drm/ttm/ttm_device.c b/drivers/gpu/drm/ttm/ttm_device.c index d3bfb9a696a7..7aecd8968169 100644 --- a/drivers/gpu/drm/ttm/ttm_device.c +++ b/drivers/gpu/drm/ttm/ttm_device.c @@ -40,6 +40,9 @@ #include "ttm_module.h" #include "ttm_bo_internal.h" +static unsigned long ttm_pages_min; +MODULE_PARM_DESC(pages_min, "Minimum calculated limit for the allocated pages"); +module_param_named(pages_min, ttm_pages_min, ulong, 0644); /* * ttm_global_mutex - protecting the global state */ @@ -91,6 +94,9 @@ static int ttm_global_init(void) num_pages = ((u64)si.totalram * si.mem_unit) >> PAGE_SHIFT; num_pages /= 2; + /* Ensure it meats minimum */ + num_pages = max(num_pages, ttm_pages_min); + /* But for DMA32 we limit ourself to only use 2GiB maximum. */ num_dma32 = (u64)(si.totalram - si.totalhigh) * si.mem_unit >> PAGE_SHIFT; diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index d8171252f9ff..e9c30e56ed92 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -201,6 +201,15 @@ config HID_ASUS - GL553V series - GL753V series +config HID_ASUS_ALLY + tristate "Asus Ally gamepad configuration support" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + select POWER_SUPPLY + help + Support for configuring the Asus ROG Ally gamepad using attributes. + config HID_AUREAL tristate "Aureal" help @@ -486,6 +495,19 @@ config HID_GT683R Currently the following devices are know to be supported: - MSI GT683R +config HID_MSI + tristate "MSI Claw Gamepad Support" + depends on USB_HID + select NEW_LEDS + select LEDS_CLASS + select LEDS_CLASS_MULTICOLOR + help + Support for the MSI Claw RGB and controller configuration + + Say Y here to include configuration interface support for the MSI Claw Line + of Handheld Console Controllers. Say M here to compile this driver as a + module. The module will be called hid-msi. + config HID_KEYTOUCH tristate "Keytouch HID devices" help @@ -1492,6 +1514,8 @@ source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/intel-thc-hid/Kconfig" +source "drivers/hid/zotac-zone-hid/Kconfig" + endif # HID # USB support may be used with HID disabled diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 01c6cfa52a49..4e294f38a030 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_HID_APPLETB_BL) += hid-appletb-bl.o obj-$(CONFIG_HID_APPLETB_KBD) += hid-appletb-kbd.o obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o obj-$(CONFIG_HID_ASUS) += hid-asus.o +obj-$(CONFIG_HID_ASUS_ALLY) += hid-asus-ally.o obj-$(CONFIG_HID_AUREAL) += hid-aureal.o obj-$(CONFIG_HID_BELKIN) += hid-belkin.o obj-$(CONFIG_HID_BETOP_FF) += hid-betopff.o @@ -92,6 +93,7 @@ obj-$(CONFIG_HID_MAYFLASH) += hid-mf.o obj-$(CONFIG_HID_MEGAWORLD_FF) += hid-megaworld.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o obj-$(CONFIG_HID_MONTEREY) += hid-monterey.o +obj-$(CONFIG_HID_MSI) += hid-msi.o obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o obj-$(CONFIG_HID_NTI) += hid-nti.o @@ -185,6 +187,8 @@ obj-$(CONFIG_SPI_HID_APPLE_CORE) += spi-hid/ obj-$(CONFIG_HID_DOCKCHANNEL) += dockchannel-hid/ +obj-$(CONFIG_ZOTAC_ZONE_HID) += zotac-zone-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/ diff --git a/drivers/hid/hid-asus-ally.c b/drivers/hid/hid-asus-ally.c new file mode 100644 index 000000000000..1cbc86328aec --- /dev/null +++ b/drivers/hid/hid-asus-ally.c @@ -0,0 +1,2304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include "linux/compiler_attributes.h" +#include "linux/device.h" +#include +#include +#include "linux/pm.h" +#include "linux/printk.h" +#include "linux/slab.h" +#include +#include +#include +#include +#include + +#include "hid-ids.h" +#include "hid-asus.h" +#include "hid-asus-ally.h" + +#define DEBUG + +#define READY_MAX_TRIES 3 +#define FEATURE_REPORT_ID 0x0d +#define FEATURE_ROG_ALLY_REPORT_ID 0x5a +#define FEATURE_ROG_ALLY_CODE_PAGE 0xD1 +#define FEATURE_ROG_ALLY_REPORT_SIZE 64 +#define ALLY_X_INPUT_REPORT_USB 0x0B +#define ALLY_X_INPUT_REPORT_USB_SIZE 16 + +#define ROG_ALLY_REPORT_SIZE 64 +#define ROG_ALLY_X_MIN_MCU 313 +#define ROG_ALLY_MIN_MCU 319 + +#define FEATURE_KBD_LED_REPORT_ID1 0x5d +#define FEATURE_KBD_LED_REPORT_ID2 0x5e + +#define BTN_DATA_LEN 11; +#define BTN_CODE_BYTES_LEN 8 + +static const u8 EC_INIT_STRING[] = { 0x5A, 'A', 'S', 'U', 'S', ' ', 'T', 'e','c', 'h', '.', 'I', 'n', 'c', '.', '\0' }; +static const u8 EC_MODE_LED_APPLY[] = { 0x5A, 0xB4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static const u8 EC_MODE_LED_SET[] = { 0x5A, 0xB5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +static const u8 FORCE_FEEDBACK_OFF[] = { 0x0D, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xEB }; + +static const struct hid_device_id rog_ally_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X) }, + {} +}; + +struct btn_code_map { + u64 code; + const char *name; +}; + +static const struct btn_code_map ally_btn_codes[] = { + { 0, "NONE" }, + /* Gamepad button codes */ + { BTN_PAD_A, "PAD_A" }, + { BTN_PAD_B, "PAD_B" }, + { BTN_PAD_X, "PAD_X" }, + { BTN_PAD_Y, "PAD_Y" }, + { BTN_PAD_LB, "PAD_LB" }, + { BTN_PAD_RB, "PAD_RB" }, + { BTN_PAD_LS, "PAD_LS" }, + { BTN_PAD_RS, "PAD_RS" }, + { BTN_PAD_DPAD_UP, "PAD_DPAD_UP" }, + { BTN_PAD_DPAD_DOWN, "PAD_DPAD_DOWN" }, + { BTN_PAD_DPAD_LEFT, "PAD_DPAD_LEFT" }, + { BTN_PAD_DPAD_RIGHT, "PAD_DPAD_RIGHT" }, + { BTN_PAD_VIEW, "PAD_VIEW" }, + { BTN_PAD_MENU, "PAD_MENU" }, + { BTN_PAD_XBOX, "PAD_XBOX" }, + + /* Triggers mapped to keyboard codes */ + { BTN_KB_M2, "KB_M2" }, + { BTN_KB_M1, "KB_M1" }, + { BTN_KB_ESC, "KB_ESC" }, + { BTN_KB_F1, "KB_F1" }, + { BTN_KB_F2, "KB_F2" }, + { BTN_KB_F3, "KB_F3" }, + { BTN_KB_F4, "KB_F4" }, + { BTN_KB_F5, "KB_F5" }, + { BTN_KB_F6, "KB_F6" }, + { BTN_KB_F7, "KB_F7" }, + { BTN_KB_F8, "KB_F8" }, + { BTN_KB_F9, "KB_F9" }, + { BTN_KB_F10, "KB_F10" }, + { BTN_KB_F11, "KB_F11" }, + { BTN_KB_F12, "KB_F12" }, + { BTN_KB_F14, "KB_F14" }, + { BTN_KB_F15, "KB_F15" }, + { BTN_KB_BACKTICK, "KB_BACKTICK" }, + { BTN_KB_1, "KB_1" }, + { BTN_KB_2, "KB_2" }, + { BTN_KB_3, "KB_3" }, + { BTN_KB_4, "KB_4" }, + { BTN_KB_5, "KB_5" }, + { BTN_KB_6, "KB_6" }, + { BTN_KB_7, "KB_7" }, + { BTN_KB_8, "KB_8" }, + { BTN_KB_9, "KB_9" }, + { BTN_KB_0, "KB_0" }, + { BTN_KB_HYPHEN, "KB_HYPHEN" }, + { BTN_KB_EQUALS, "KB_EQUALS" }, + { BTN_KB_BACKSPACE, "KB_BACKSPACE" }, + { BTN_KB_TAB, "KB_TAB" }, + { BTN_KB_Q, "KB_Q" }, + { BTN_KB_W, "KB_W" }, + { BTN_KB_E, "KB_E" }, + { BTN_KB_R, "KB_R" }, + { BTN_KB_T, "KB_T" }, + { BTN_KB_Y, "KB_Y" }, + { BTN_KB_U, "KB_U" }, + { BTN_KB_O, "KB_O" }, + { BTN_KB_P, "KB_P" }, + { BTN_KB_LBRACKET, "KB_LBRACKET" }, + { BTN_KB_RBRACKET, "KB_RBRACKET" }, + { BTN_KB_BACKSLASH, "KB_BACKSLASH" }, + { BTN_KB_CAPS, "KB_CAPS" }, + { BTN_KB_A, "KB_A" }, + { BTN_KB_S, "KB_S" }, + { BTN_KB_D, "KB_D" }, + { BTN_KB_F, "KB_F" }, + { BTN_KB_G, "KB_G" }, + { BTN_KB_H, "KB_H" }, + { BTN_KB_J, "KB_J" }, + { BTN_KB_K, "KB_K" }, + { BTN_KB_L, "KB_L" }, + { BTN_KB_SEMI, "KB_SEMI" }, + { BTN_KB_QUOTE, "KB_QUOTE" }, + { BTN_KB_RET, "KB_RET" }, + { BTN_KB_LSHIFT, "KB_LSHIFT" }, + { BTN_KB_Z, "KB_Z" }, + { BTN_KB_X, "KB_X" }, + { BTN_KB_C, "KB_C" }, + { BTN_KB_V, "KB_V" }, + { BTN_KB_B, "KB_B" }, + { BTN_KB_N, "KB_N" }, + { BTN_KB_M, "KB_M" }, + { BTN_KB_COMMA, "KB_COMMA" }, + { BTN_KB_PERIOD, "KB_PERIOD" }, + { BTN_KB_RSHIFT, "KB_RSHIFT" }, + { BTN_KB_LCTL, "KB_LCTL" }, + { BTN_KB_META, "KB_META" }, + { BTN_KB_LALT, "KB_LALT" }, + { BTN_KB_SPACE, "KB_SPACE" }, + { BTN_KB_RALT, "KB_RALT" }, + { BTN_KB_MENU, "KB_MENU" }, + { BTN_KB_RCTL, "KB_RCTL" }, + { BTN_KB_PRNTSCN, "KB_PRNTSCN" }, + { BTN_KB_SCRLCK, "KB_SCRLCK" }, + { BTN_KB_PAUSE, "KB_PAUSE" }, + { BTN_KB_INS, "KB_INS" }, + { BTN_KB_HOME, "KB_HOME" }, + { BTN_KB_PGUP, "KB_PGUP" }, + { BTN_KB_DEL, "KB_DEL" }, + { BTN_KB_END, "KB_END" }, + { BTN_KB_PGDWN, "KB_PGDWN" }, + { BTN_KB_UP_ARROW, "KB_UP_ARROW" }, + { BTN_KB_DOWN_ARROW, "KB_DOWN_ARROW" }, + { BTN_KB_LEFT_ARROW, "KB_LEFT_ARROW" }, + { BTN_KB_RIGHT_ARROW, "KB_RIGHT_ARROW" }, + + /* Numpad mappings */ + { BTN_NUMPAD_LOCK, "NUMPAD_LOCK" }, + { BTN_NUMPAD_FWDSLASH, "NUMPAD_FWDSLASH" }, + { BTN_NUMPAD_ASTERISK, "NUMPAD_ASTERISK" }, + { BTN_NUMPAD_HYPHEN, "NUMPAD_HYPHEN" }, + { BTN_NUMPAD_0, "NUMPAD_0" }, + { BTN_NUMPAD_1, "NUMPAD_1" }, + { BTN_NUMPAD_2, "NUMPAD_2" }, + { BTN_NUMPAD_3, "NUMPAD_3" }, + { BTN_NUMPAD_4, "NUMPAD_4" }, + { BTN_NUMPAD_5, "NUMPAD_5" }, + { BTN_NUMPAD_6, "NUMPAD_6" }, + { BTN_NUMPAD_7, "NUMPAD_7" }, + { BTN_NUMPAD_8, "NUMPAD_8" }, + { BTN_NUMPAD_9, "NUMPAD_9" }, + { BTN_NUMPAD_PLUS, "NUMPAD_PLUS" }, + { BTN_NUMPAD_ENTER, "NUMPAD_ENTER" }, + { BTN_NUMPAD_PERIOD, "NUMPAD_PERIOD" }, + + /* Mouse mappings */ + { BTN_MOUSE_LCLICK, "MOUSE_LCLICK" }, + { BTN_MOUSE_RCLICK, "MOUSE_RCLICK" }, + { BTN_MOUSE_MCLICK, "MOUSE_MCLICK" }, + { BTN_MOUSE_WHEEL_UP, "MOUSE_WHEEL_UP" }, + { BTN_MOUSE_WHEEL_DOWN, "MOUSE_WHEEL_DOWN" }, + + /* Media mappings */ + { BTN_MEDIA_SCREENSHOT, "MEDIA_SCREENSHOT" }, + { BTN_MEDIA_SHOW_KEYBOARD, "MEDIA_SHOW_KEYBOARD" }, + { BTN_MEDIA_SHOW_DESKTOP, "MEDIA_SHOW_DESKTOP" }, + { BTN_MEDIA_START_RECORDING, "MEDIA_START_RECORDING" }, + { BTN_MEDIA_MIC_OFF, "MEDIA_MIC_OFF" }, + { BTN_MEDIA_VOL_DOWN, "MEDIA_VOL_DOWN" }, + { BTN_MEDIA_VOL_UP, "MEDIA_VOL_UP" }, +}; +static const size_t keymap_len = ARRAY_SIZE(ally_btn_codes); + +/* byte_array must be >= 8 in length */ +static void btn_code_to_byte_array(u64 keycode, u8 *byte_array) +{ + /* Convert the u64 to bytes[8] */ + for (int i = 0; i < 8; ++i) { + byte_array[i] = (keycode >> (56 - 8 * i)) & 0xFF; + } +} + +static u64 name_to_btn(const char *name) +{ + int len = strcspn(name, "\n"); + for (size_t i = 0; i < keymap_len; ++i) { + if (strncmp(ally_btn_codes[i].name, name, len) == 0) { + return ally_btn_codes[i].code; + } + } + return -EINVAL; +} + +static const char* btn_to_name(u64 key) +{ + for (size_t i = 0; i < keymap_len; ++i) { + if (ally_btn_codes[i].code == key) { + return ally_btn_codes[i].name; + } + } + return NULL; +} + +struct btn_data { + u64 button; + u64 macro; + bool turbo; +}; + +struct btn_mapping { + struct btn_data btn_a; + struct btn_data btn_b; + struct btn_data btn_x; + struct btn_data btn_y; + struct btn_data btn_lb; + struct btn_data btn_rb; + struct btn_data btn_ls; + struct btn_data btn_rs; + struct btn_data btn_lt; + struct btn_data btn_rt; + struct btn_data dpad_up; + struct btn_data dpad_down; + struct btn_data dpad_left; + struct btn_data dpad_right; + struct btn_data btn_view; + struct btn_data btn_menu; + struct btn_data btn_m1; + struct btn_data btn_m2; +}; + +struct deadzone { + u8 inner; + u8 outer; +}; + +struct response_curve { + uint8_t move_pct_1; + uint8_t response_pct_1; + uint8_t move_pct_2; + uint8_t response_pct_2; + uint8_t move_pct_3; + uint8_t response_pct_3; + uint8_t move_pct_4; + uint8_t response_pct_4; +} __packed; + +struct js_axis_calibrations { + uint16_t left_y_stable; + uint16_t left_y_min; + uint16_t left_y_max; + uint16_t left_x_stable; + uint16_t left_x_min; + uint16_t left_x_max; + uint16_t right_y_stable; + uint16_t right_y_min; + uint16_t right_y_max; + uint16_t right_x_stable; + uint16_t right_x_min; + uint16_t right_x_max; +} __packed; + +struct tr_axis_calibrations { + uint16_t left_stable; + uint16_t left_max; + uint16_t right_stable; + uint16_t right_max; +} __packed; + +/* ROG Ally has many settings related to the gamepad, all using the same n-key endpoint */ +struct ally_gamepad_cfg { + struct hid_device *hdev; + struct input_dev *input; + + enum xpad_mode mode; + /* + * index: [mode] + */ + struct btn_mapping key_mapping[xpad_mode_mouse]; + /* + * index: left, right + * max: 64 + */ + u8 vibration_intensity[2]; + + /* deadzones */ + struct deadzone ls_dz; // left stick + struct deadzone rs_dz; // right stick + struct deadzone lt_dz; // left trigger + struct deadzone rt_dz; // right trigger + /* anti-deadzones */ + u8 ls_adz; // left stick + u8 rs_adz; // right stick + /* joystick response curves */ + struct response_curve ls_rc; + struct response_curve rs_rc; + + struct js_axis_calibrations js_cal; + struct tr_axis_calibrations tr_cal; +}; + +/* The hatswitch outputs integers, we use them to index this X|Y pair */ +static const int hat_values[][2] = { + { 0, 0 }, { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, + { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, +}; + +/* rumble packet structure */ +struct ff_data { + u8 enable; + u8 magnitude_left; + u8 magnitude_right; + u8 magnitude_strong; + u8 magnitude_weak; + u8 pulse_sustain_10ms; + u8 pulse_release_10ms; + u8 loop_count; +} __packed; + +struct ff_report { + u8 report_id; + struct ff_data ff; +} __packed; + +struct ally_x_input_report { + uint16_t x, y; + uint16_t rx, ry; + uint16_t z, rz; + uint8_t buttons[4]; +} __packed; + +struct ally_x_device { + struct input_dev *input; + struct hid_device *hdev; + spinlock_t lock; + + struct ff_report *ff_packet; + struct work_struct output_worker; + bool output_worker_initialized; + /* Prevent multiple queued event due to the enforced delay in worker */ + bool update_qam_btn; + /* Set if the QAM and AC buttons emit Xbox and Xbox+A */ + bool qam_btns_steam_mode; + bool update_ff; +}; + +struct ally_rgb_dev { + struct hid_device *hdev; + struct led_classdev_mc led_rgb_dev; + struct work_struct work; + bool output_worker_initialized; + spinlock_t lock; + + bool removed; + bool attr_group_added; + bool update_rgb; + uint8_t red[4]; + uint8_t green[4]; + uint8_t blue[4]; +}; + +struct ally_rgb_data { + uint8_t brightness; + uint8_t red[4]; + uint8_t green[4]; + uint8_t blue[4]; + bool initialized; +}; + +static struct ally_drvdata { + struct hid_device *hdev; + struct ally_x_device *ally_x; + struct ally_gamepad_cfg *gamepad_cfg; + struct ally_rgb_dev *led_rgb_dev; + struct ally_rgb_data led_rgb_data; + uint mcu_version; +} drvdata; + +static void reverse_bytes_in_pairs(u8 *buf, size_t size) { + uint16_t *word_ptr; + size_t i; + + for (i = 0; i < size; i += 2) { + if (i + 1 < size) { + word_ptr = (uint16_t *)&buf[i]; + *word_ptr = cpu_to_be16(*word_ptr); + } + } +} + +/** + * asus_dev_set_report - send set report request to device. + * + * @hdev: hid device + * @buf: in/out data to transfer + * @len: length of buf + * + * Return: count of data transferred, negative if error + * + * Same behavior as hid_hw_raw_request. Note that the input buffer is duplicated. + */ +static int asus_dev_set_report(struct hid_device *hdev, const u8 *buf, size_t len) +{ + unsigned char *dmabuf; + int ret; + + dmabuf = kmemdup(buf, len, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, buf[0], dmabuf, len, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + kfree(dmabuf); + + return ret; +} + +/** + * asus_dev_get_report - send get report request to device. + * + * @hdev: hid device + * @out: buffer to write output data in to + * @len: length the output buffer provided + * + * Return: count of data transferred, negative if error + * + * Same behavior as hid_hw_raw_request. + */ +static int asus_dev_get_report(struct hid_device *hdev, u8 *out, size_t len) +{ + return hid_hw_raw_request(hdev, FEATURE_REPORT_ID, out, len, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); +} + +static u8 get_endpoint_address(struct hid_device *hdev) +{ + struct usb_interface *intf; + struct usb_host_endpoint *ep; + + intf = to_usb_interface(hdev->dev.parent); + + if (intf) { + ep = intf->cur_altsetting->endpoint; + if (ep) { + return ep->desc.bEndpointAddress; + } + } + + return -ENODEV; +} + +/**************************************************************************************************/ +/* ROG Ally gamepad configuration */ +/**************************************************************************************************/ + +/* This should be called before any attempts to set device functions */ +static int ally_gamepad_check_ready(struct hid_device *hdev) +{ + int ret, count; + u8 *hidbuf; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + ret = 0; + for (count = 0; count < READY_MAX_TRIES; count++) { + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_check_ready; + hidbuf[3] = 01; + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + hid_dbg(hdev, "ROG Ally check failed set report: %d\n", ret); + + hidbuf[0] = hidbuf[1] = hidbuf[2] = hidbuf[3] = 0; + ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + hid_dbg(hdev, "ROG Ally check failed get report: %d\n", ret); + + ret = hidbuf[2] == xpad_cmd_check_ready; + if (ret) + break; + usleep_range( + 1000, + 2000); /* don't spam the entire loop in less than USB response time */ + } + + if (count == READY_MAX_TRIES) + hid_warn(hdev, "ROG Ally never responded with a ready\n"); + + kfree(hidbuf); + return ret; +} + +/* VIBRATION INTENSITY ****************************************************************************/ +static ssize_t gamepad_vibration_intensity_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "left right\n"); +} + +ALLY_DEVICE_ATTR_RO(gamepad_vibration_intensity_index, vibration_intensity_index); + +static ssize_t _gamepad_apply_intensity(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_vibe_intensity; + hidbuf[3] = xpad_cmd_len_vibe_intensity; + hidbuf[4] = ally_cfg->vibration_intensity[0]; + hidbuf[5] = ally_cfg->vibration_intensity[1]; + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t gamepad_vibration_intensity_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit( + buf, "%d %d\n", + ally_cfg->vibration_intensity[0], + ally_cfg->vibration_intensity[1]); +} + +static ssize_t gamepad_vibration_intensity_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u32 left, right; + int ret; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + if (sscanf(buf, "%d %d", &left, &right) != 2) + return -EINVAL; + + if (left > 64 || right > 64) + return -EINVAL; + + ally_cfg->vibration_intensity[0] = left; + ally_cfg->vibration_intensity[1] = right; + + ret = _gamepad_apply_intensity(hdev, ally_cfg); + if (ret < 0) + return ret; + + return count; +} + +ALLY_DEVICE_ATTR_RW(gamepad_vibration_intensity, vibration_intensity); + +/* ANALOGUE DEADZONES *****************************************************************************/ +static ssize_t _gamepad_apply_deadzones(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_js_dz; + hidbuf[3] = xpad_cmd_len_deadzone; + hidbuf[4] = ally_cfg->ls_dz.inner; + hidbuf[5] = ally_cfg->ls_dz.outer; + hidbuf[6] = ally_cfg->rs_dz.inner; + hidbuf[7] = ally_cfg->rs_dz.outer; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto end; + + hidbuf[2] = xpad_cmd_set_tr_dz; + hidbuf[4] = ally_cfg->lt_dz.inner; + hidbuf[5] = ally_cfg->lt_dz.outer; + hidbuf[6] = ally_cfg->rt_dz.inner; + hidbuf[7] = ally_cfg->rt_dz.outer; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto end; + +end: + kfree(hidbuf); + return ret; +} + +static void _gamepad_set_deadzones_default(struct ally_gamepad_cfg *ally_cfg) +{ + ally_cfg->ls_dz.inner = 0x00; + ally_cfg->ls_dz.outer = 0x64; + ally_cfg->rs_dz.inner = 0x00; + ally_cfg->rs_dz.outer = 0x64; + ally_cfg->lt_dz.inner = 0x00; + ally_cfg->lt_dz.outer = 0x64; + ally_cfg->rt_dz.inner = 0x00; + ally_cfg->rt_dz.outer = 0x64; +} + +static ssize_t axis_xyz_deadzone_index_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "inner outer\n"); +} + +ALLY_DEVICE_ATTR_RO(axis_xyz_deadzone_index, deadzone_index); + +ALLY_DEADZONES(axis_xy_left, ls_dz); +ALLY_DEADZONES(axis_xy_right, rs_dz); +ALLY_DEADZONES(axis_z_left, lt_dz); +ALLY_DEADZONES(axis_z_right, rt_dz); + +/* ANTI-DEADZONES *********************************************************************************/ +static ssize_t _gamepad_apply_js_ADZ(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_adz; + hidbuf[3] = xpad_cmd_len_adz; + hidbuf[4] = ally_cfg->ls_adz; + hidbuf[5] = ally_cfg->rs_adz; + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static void _gamepad_set_anti_deadzones_default(struct ally_gamepad_cfg *ally_cfg) +{ + ally_cfg->ls_adz = 0x00; + ally_cfg->rs_adz = 0x00; +} + +static ssize_t _gamepad_js_ADZ_store(struct device *dev, const char *buf, u8 *adz) +{ + int ret, val; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val < 0 || val > 32) + return -EINVAL; + + *adz = val; + + return ret; +} + +static ssize_t axis_xy_left_anti_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + return sysfs_emit(buf, "%d\n", ally_cfg->ls_adz); +} + +static ssize_t axis_xy_left_anti_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret; + + ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->ls_adz); + if (ret) + return ret; + + return count; +} +ALLY_DEVICE_ATTR_RW(axis_xy_left_anti_deadzone, anti_deadzone); + +static ssize_t axis_xy_right_anti_deadzone_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + return sysfs_emit(buf, "%d\n", ally_cfg->rs_adz); +} + +static ssize_t axis_xy_right_anti_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret; + + ret = _gamepad_js_ADZ_store(dev, buf, &ally_cfg->rs_adz); + if (ret) + return ret; + + return count; +} +ALLY_DEVICE_ATTR_RW(axis_xy_right_anti_deadzone, anti_deadzone); + +/* JS RESPONSE CURVES *****************************************************************************/ +static void _gamepad_set_js_response_curves_default(struct ally_gamepad_cfg *ally_cfg) +{ + struct response_curve *js1_rc = &ally_cfg->ls_rc; + struct response_curve *js2_rc = &ally_cfg->rs_rc; + js1_rc->move_pct_1 = js2_rc->move_pct_1 = 0x16; // 25% + js1_rc->move_pct_2 = js2_rc->move_pct_2 = 0x32; // 50% + js1_rc->move_pct_3 = js2_rc->move_pct_3 = 0x48; // 75% + js1_rc->move_pct_4 = js2_rc->move_pct_4 = 0x64; // 100% + js1_rc->response_pct_1 = js2_rc->response_pct_1 = 0x16; + js1_rc->response_pct_2 = js2_rc->response_pct_2 = 0x32; + js1_rc->response_pct_3 = js2_rc->response_pct_3 = 0x48; + js1_rc->response_pct_4 = js2_rc->response_pct_4 = 0x64; +} + +static ssize_t _gamepad_apply_response_curves(struct hid_device *hdev, + struct ally_gamepad_cfg *ally_cfg) +{ + u8 *hidbuf; + int ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + memcpy(&hidbuf[2], &ally_cfg->ls_rc, sizeof(ally_cfg->ls_rc)); + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + hidbuf[4] = 0x02; + memcpy(&hidbuf[5], &ally_cfg->rs_rc, sizeof(ally_cfg->rs_rc)); + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +ALLY_JS_RC_POINT(axis_xy_left, move, 1); +ALLY_JS_RC_POINT(axis_xy_left, move, 2); +ALLY_JS_RC_POINT(axis_xy_left, move, 3); +ALLY_JS_RC_POINT(axis_xy_left, move, 4); +ALLY_JS_RC_POINT(axis_xy_left, response, 1); +ALLY_JS_RC_POINT(axis_xy_left, response, 2); +ALLY_JS_RC_POINT(axis_xy_left, response, 3); +ALLY_JS_RC_POINT(axis_xy_left, response, 4); + +ALLY_JS_RC_POINT(axis_xy_right, move, 1); +ALLY_JS_RC_POINT(axis_xy_right, move, 2); +ALLY_JS_RC_POINT(axis_xy_right, move, 3); +ALLY_JS_RC_POINT(axis_xy_right, move, 4); +ALLY_JS_RC_POINT(axis_xy_right, response, 1); +ALLY_JS_RC_POINT(axis_xy_right, response, 2); +ALLY_JS_RC_POINT(axis_xy_right, response, 3); +ALLY_JS_RC_POINT(axis_xy_right, response, 4); + +/* CALIBRATIONS ***********************************************************************************/ +static int gamepad_get_calibration(struct hid_device *hdev) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + u8 *hidbuf; + int ret, i; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + for (i = 0; i < 2; i++) { + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = 0xD0; + hidbuf[2] = 0x03; + hidbuf[3] = i + 1; // 0x01 JS, 0x02 TR + hidbuf[4] = 0x20; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) { + hid_warn(hdev, "ROG Ally check failed set report: %d\n", ret); + goto cleanup; + } + + memset(hidbuf, 0, FEATURE_ROG_ALLY_REPORT_SIZE); + ret = asus_dev_get_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0 || hidbuf[5] != 1) { + hid_warn(hdev, "ROG Ally check failed get report: %d\n", ret); + goto cleanup; + } + + if (i == 0) { + /* Joystick calibration */ + reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct js_axis_calibrations)); + ally_cfg->js_cal = *(struct js_axis_calibrations *)&hidbuf[6]; + print_hex_dump(KERN_INFO, "HID Buffer JS: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true); + struct js_axis_calibrations *cal = &drvdata.gamepad_cfg->js_cal; + pr_err("LS_CAL: X: %d, Min: %d, Max: %d", cal->left_x_stable, cal->left_x_min, cal->left_x_max); + pr_err("LS_CAL: Y: %d, Min: %d, Max: %d", cal->left_y_stable, cal->left_y_min, cal->left_y_max); + pr_err("RS_CAL: X: %d, Min: %d, Max: %d", cal->right_x_stable, cal->right_x_min, cal->right_x_max); + pr_err("RS_CAL: Y: %d, Min: %d, Max: %d", cal->right_y_stable, cal->right_y_min, cal->right_y_max); + } else { + /* Trigger calibration */ + reverse_bytes_in_pairs(&hidbuf[6], sizeof(struct tr_axis_calibrations)); + ally_cfg->tr_cal = *(struct tr_axis_calibrations *)&hidbuf[6]; + print_hex_dump(KERN_INFO, "HID Buffer TR: ", DUMP_PREFIX_OFFSET, 16, 1, hidbuf, 32, true); + } + } + +cleanup: + kfree(hidbuf); + return ret; +} + +static struct attribute *axis_xy_left_attrs[] = { + &dev_attr_axis_xy_left_anti_deadzone.attr, + &dev_attr_axis_xy_left_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_axis_xy_left_move_1.attr, + &dev_attr_axis_xy_left_move_2.attr, + &dev_attr_axis_xy_left_move_3.attr, + &dev_attr_axis_xy_left_move_4.attr, + &dev_attr_axis_xy_left_response_1.attr, + &dev_attr_axis_xy_left_response_2.attr, + &dev_attr_axis_xy_left_response_3.attr, + &dev_attr_axis_xy_left_response_4.attr, + NULL +}; +static const struct attribute_group axis_xy_left_attr_group = { + .name = "axis_xy_left", + .attrs = axis_xy_left_attrs, +}; + +static struct attribute *axis_xy_right_attrs[] = { + &dev_attr_axis_xy_right_anti_deadzone.attr, + &dev_attr_axis_xy_right_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + &dev_attr_axis_xy_right_move_1.attr, + &dev_attr_axis_xy_right_move_2.attr, + &dev_attr_axis_xy_right_move_3.attr, + &dev_attr_axis_xy_right_move_4.attr, + &dev_attr_axis_xy_right_response_1.attr, + &dev_attr_axis_xy_right_response_2.attr, + &dev_attr_axis_xy_right_response_3.attr, + &dev_attr_axis_xy_right_response_4.attr, + NULL +}; +static const struct attribute_group axis_xy_right_attr_group = { + .name = "axis_xy_right", + .attrs = axis_xy_right_attrs, +}; + +static struct attribute *axis_z_left_attrs[] = { + &dev_attr_axis_z_left_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + NULL, +}; +static const struct attribute_group axis_z_left_attr_group = { + .name = "axis_z_left", + .attrs = axis_z_left_attrs, +}; + +static struct attribute *axis_z_right_attrs[] = { + &dev_attr_axis_z_right_deadzone.attr, + &dev_attr_axis_xyz_deadzone_index.attr, + NULL, +}; +static const struct attribute_group axis_z_right_attr_group = { + .name = "axis_z_right", + .attrs = axis_z_right_attrs, +}; + +/* A HID packet conatins mappings for two buttons: btn1, btn1_macro, btn2, btn2_macro */ +static void _btn_pair_to_hid_pkt(struct ally_gamepad_cfg *ally_cfg, + enum btn_pair_index pair, + struct btn_data *btn1, struct btn_data *btn2, + u8 *out, int out_len) +{ + int start = 5; + + out[0] = FEATURE_ROG_ALLY_REPORT_ID; + out[1] = FEATURE_ROG_ALLY_CODE_PAGE; + out[2] = xpad_cmd_set_mapping; + out[3] = pair; + out[4] = xpad_cmd_len_mapping; + + btn_code_to_byte_array(btn1->button, &out[start]); + start += BTN_DATA_LEN; + btn_code_to_byte_array(btn1->macro, &out[start]); + start += BTN_DATA_LEN; + btn_code_to_byte_array(btn2->button, &out[start]); + start += BTN_DATA_LEN; + btn_code_to_byte_array(btn2->macro, &out[start]); + //print_hex_dump(KERN_DEBUG, "byte_array: ", DUMP_PREFIX_OFFSET, 64, 1, out, 64, false); +} + +/* Apply the mapping pair to the device */ +static int _gamepad_apply_btn_pair(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, + enum btn_pair_index btn_pair) +{ + u8 mode = ally_cfg->mode - 1; + struct btn_data *btn1, *btn2; + u8 *hidbuf; + int ret; + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + return ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + switch (btn_pair) { + case btn_pair_dpad_u_d: + btn1 = &ally_cfg->key_mapping[mode].dpad_up; + btn2 = &ally_cfg->key_mapping[mode].dpad_down; + break; + case btn_pair_dpad_l_r: + btn1 = &ally_cfg->key_mapping[mode].dpad_left; + btn2 = &ally_cfg->key_mapping[mode].dpad_right; + break; + case btn_pair_ls_rs: + btn1 = &ally_cfg->key_mapping[mode].btn_ls; + btn2 = &ally_cfg->key_mapping[mode].btn_rs; + break; + case btn_pair_lb_rb: + btn1 = &ally_cfg->key_mapping[mode].btn_lb; + btn2 = &ally_cfg->key_mapping[mode].btn_rb; + break; + case btn_pair_lt_rt: + btn1 = &ally_cfg->key_mapping[mode].btn_lt; + btn2 = &ally_cfg->key_mapping[mode].btn_rt; + break; + case btn_pair_a_b: + btn1 = &ally_cfg->key_mapping[mode].btn_a; + btn2 = &ally_cfg->key_mapping[mode].btn_b; + break; + case btn_pair_x_y: + btn1 = &ally_cfg->key_mapping[mode].btn_x; + btn2 = &ally_cfg->key_mapping[mode].btn_y; + break; + case btn_pair_view_menu: + btn1 = &ally_cfg->key_mapping[mode].btn_view; + btn2 = &ally_cfg->key_mapping[mode].btn_menu; + break; + case btn_pair_m1_m2: + btn1 = &ally_cfg->key_mapping[mode].btn_m1; + btn2 = &ally_cfg->key_mapping[mode].btn_m2; + break; + default: + break; + } + + _btn_pair_to_hid_pkt(ally_cfg, btn_pair, btn1, btn2, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + + kfree(hidbuf); + + return ret; +} + +static int _gamepad_apply_turbo(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg) +{ + struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1]; + u8 *hidbuf; + int ret; + + /* set turbo */ + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_turbo; + hidbuf[3] = xpad_cmd_len_turbo; + + hidbuf[4] = map->dpad_up.turbo; + hidbuf[6] = map->dpad_down.turbo; + hidbuf[8] = map->dpad_left.turbo; + hidbuf[10] = map->dpad_right.turbo; + + hidbuf[12] = map->btn_ls.turbo; + hidbuf[14] = map->btn_rs.turbo; + hidbuf[16] = map->btn_lb.turbo; + hidbuf[18] = map->btn_rb.turbo; + + hidbuf[20] = map->btn_a.turbo; + hidbuf[22] = map->btn_b.turbo; + hidbuf[24] = map->btn_x.turbo; + hidbuf[26] = map->btn_y.turbo; + + hidbuf[28] = map->btn_lt.turbo; + hidbuf[30] = map->btn_rt.turbo; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + + kfree(hidbuf); + + return ret; +} + +static ssize_t _gamepad_apply_all(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg) +{ + int ret; + + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_u_d); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_dpad_l_r); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_ls_rs); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lb_rb); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_a_b); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_x_y); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_view_menu); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2); + if (ret < 0) + return ret; + ret = _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_lt_rt); + if (ret < 0) + return ret; + ret = _gamepad_apply_turbo(hdev, ally_cfg); + if (ret < 0) + return ret; + ret = _gamepad_apply_deadzones(hdev, ally_cfg); + if (ret < 0) + return ret; + ret = _gamepad_apply_js_ADZ(hdev, ally_cfg); + if (ret < 0) + return ret; + ret =_gamepad_apply_response_curves(hdev, ally_cfg); + if (ret < 0) + return ret; + + return 0; +} + +static ssize_t gamepad_apply_all_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + struct hid_device *hdev = to_hid_device(dev); + int ret; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = _gamepad_apply_all(hdev, ally_cfg); + if (ret < 0) + return ret; + + return count; +} +ALLY_DEVICE_ATTR_WO(gamepad_apply_all, apply_all); + +/* button map attributes, regular and macro*/ +ALLY_BTN_MAPPING(m1, btn_m1); +ALLY_BTN_MAPPING(m2, btn_m2); +ALLY_BTN_MAPPING(view, btn_view); +ALLY_BTN_MAPPING(menu, btn_menu); +ALLY_TURBO_BTN_MAPPING(a, btn_a); +ALLY_TURBO_BTN_MAPPING(b, btn_b); +ALLY_TURBO_BTN_MAPPING(x, btn_x); +ALLY_TURBO_BTN_MAPPING(y, btn_y); +ALLY_TURBO_BTN_MAPPING(lb, btn_lb); +ALLY_TURBO_BTN_MAPPING(rb, btn_rb); +ALLY_TURBO_BTN_MAPPING(ls, btn_ls); +ALLY_TURBO_BTN_MAPPING(rs, btn_rs); +ALLY_TURBO_BTN_MAPPING(lt, btn_lt); +ALLY_TURBO_BTN_MAPPING(rt, btn_rt); +ALLY_TURBO_BTN_MAPPING(dpad_u, dpad_up); +ALLY_TURBO_BTN_MAPPING(dpad_d, dpad_down); +ALLY_TURBO_BTN_MAPPING(dpad_l, dpad_left); +ALLY_TURBO_BTN_MAPPING(dpad_r, dpad_right); + +static void _gamepad_set_xpad_default(struct ally_gamepad_cfg *ally_cfg) +{ + struct btn_mapping *map = &ally_cfg->key_mapping[ally_cfg->mode - 1]; + map->btn_m1.button = BTN_KB_M1; + map->btn_m2.button = BTN_KB_M2; + map->btn_a.button = BTN_PAD_A; + map->btn_b.button = BTN_PAD_B; + map->btn_x.button = BTN_PAD_X; + map->btn_y.button = BTN_PAD_Y; + map->btn_lb.button = BTN_PAD_LB; + map->btn_rb.button = BTN_PAD_RB; + map->btn_lt.button = BTN_PAD_LT; + map->btn_rt.button = BTN_PAD_RT; + map->btn_ls.button = BTN_PAD_LS; + map->btn_rs.button = BTN_PAD_RS; + map->dpad_up.button = BTN_PAD_DPAD_UP; + map->dpad_down.button = BTN_PAD_DPAD_DOWN; + map->dpad_left.button = BTN_PAD_DPAD_LEFT; + map->dpad_right.button = BTN_PAD_DPAD_RIGHT; + map->btn_view.button = BTN_PAD_VIEW; + map->btn_menu.button = BTN_PAD_MENU; +} + +static ssize_t btn_mapping_reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + switch (ally_cfg->mode) { + case xpad_mode_game: + _gamepad_set_xpad_default(ally_cfg); + break; + default: + _gamepad_set_xpad_default(ally_cfg); + break; + } + + return count; +} +ALLY_DEVICE_ATTR_WO(btn_mapping_reset, reset_btn_mapping); + +/* GAMEPAD MODE */ +static ssize_t _gamepad_set_mode(struct hid_device *hdev, struct ally_gamepad_cfg *ally_cfg, + int val) +{ + u8 *hidbuf; + int ret; + + hidbuf = kzalloc(FEATURE_ROG_ALLY_REPORT_SIZE, GFP_KERNEL); + if (!hidbuf) + return -ENOMEM; + + hidbuf[0] = FEATURE_ROG_ALLY_REPORT_ID; + hidbuf[1] = FEATURE_ROG_ALLY_CODE_PAGE; + hidbuf[2] = xpad_cmd_set_mode; + hidbuf[3] = xpad_cmd_len_mode; + hidbuf[4] = val; + + ret = ally_gamepad_check_ready(hdev); + if (ret < 0) + goto report_fail; + + ret = asus_dev_set_report(hdev, hidbuf, FEATURE_ROG_ALLY_REPORT_SIZE); + if (ret < 0) + goto report_fail; + + ret = _gamepad_apply_all(hdev, ally_cfg); + if (ret < 0) + goto report_fail; + +report_fail: + kfree(hidbuf); + return ret; +} + +static ssize_t gamepad_mode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + return sysfs_emit(buf, "%d\n", ally_cfg->mode); +} + +static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int ret, val; + + if (!drvdata.gamepad_cfg) + return -ENODEV; + + ret = kstrtoint(buf, 0, &val); + if (ret) + return ret; + + if (val < xpad_mode_game || val > xpad_mode_mouse) + return -EINVAL; + + ally_cfg->mode = val; + + ret = _gamepad_set_mode(hdev, ally_cfg, val); + if (ret < 0) + return ret; + + return count; +} + +DEVICE_ATTR_RW(gamepad_mode); + +static ssize_t mcu_version_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", drvdata.mcu_version); +} + +DEVICE_ATTR_RO(mcu_version); + +/* ROOT LEVEL ATTRS *******************************************************************************/ +static struct attribute *gamepad_device_attrs[] = { + &dev_attr_btn_mapping_reset.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_apply_all.attr, + &dev_attr_gamepad_vibration_intensity.attr, + &dev_attr_gamepad_vibration_intensity_index.attr, + &dev_attr_mcu_version.attr, + NULL +}; + +static const struct attribute_group ally_controller_attr_group = { + .attrs = gamepad_device_attrs, +}; + +static const struct attribute_group *gamepad_device_attr_groups[] = { + &ally_controller_attr_group, + &axis_xy_left_attr_group, + &axis_xy_right_attr_group, + &axis_z_left_attr_group, + &axis_z_right_attr_group, + &btn_mapping_m1_attr_group, + &btn_mapping_m2_attr_group, + &btn_mapping_a_attr_group, + &btn_mapping_b_attr_group, + &btn_mapping_x_attr_group, + &btn_mapping_y_attr_group, + &btn_mapping_lb_attr_group, + &btn_mapping_rb_attr_group, + &btn_mapping_ls_attr_group, + &btn_mapping_rs_attr_group, + &btn_mapping_lt_attr_group, + &btn_mapping_rt_attr_group, + &btn_mapping_dpad_u_attr_group, + &btn_mapping_dpad_d_attr_group, + &btn_mapping_dpad_l_attr_group, + &btn_mapping_dpad_r_attr_group, + &btn_mapping_view_attr_group, + &btn_mapping_menu_attr_group, + NULL, +}; + +static struct ally_gamepad_cfg *ally_gamepad_cfg_create(struct hid_device *hdev) +{ + struct ally_gamepad_cfg *ally_cfg; + struct input_dev *input_dev; + int err; + + ally_cfg = devm_kzalloc(&hdev->dev, sizeof(*ally_cfg), GFP_KERNEL); + if (!ally_cfg) + return ERR_PTR(-ENOMEM); + ally_cfg->hdev = hdev; + // Allocate memory for each mode's `btn_mapping` + ally_cfg->mode = xpad_mode_game; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) { + err = -ENOMEM; + goto free_ally_cfg; + } + + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->uniq = hdev->uniq; + input_dev->name = "ASUS ROG Ally Config"; + input_set_capability(input_dev, EV_KEY, KEY_PROG1); + input_set_capability(input_dev, EV_KEY, KEY_F16); + input_set_capability(input_dev, EV_KEY, KEY_F17); + input_set_capability(input_dev, EV_KEY, KEY_F18); + input_set_drvdata(input_dev, hdev); + + err = input_register_device(input_dev); + if (err) + goto free_input_dev; + ally_cfg->input = input_dev; + + /* ignore all errors for this as they are related to USB HID I/O */ + _gamepad_set_xpad_default(ally_cfg); + ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m1.button = BTN_KB_M1; + ally_cfg->key_mapping[ally_cfg->mode - 1].btn_m2.button = BTN_KB_M2; + _gamepad_apply_btn_pair(hdev, ally_cfg, btn_pair_m1_m2); + gamepad_get_calibration(hdev); + + ally_cfg->vibration_intensity[0] = 0x64; + ally_cfg->vibration_intensity[1] = 0x64; + _gamepad_set_deadzones_default(ally_cfg); + _gamepad_set_anti_deadzones_default(ally_cfg); + _gamepad_set_js_response_curves_default(ally_cfg); + + drvdata.gamepad_cfg = ally_cfg; // Must asign before attr group setup + if (sysfs_create_groups(&hdev->dev.kobj, gamepad_device_attr_groups)) { + err = -ENODEV; + goto unregister_input_dev; + } + + return ally_cfg; + +unregister_input_dev: + input_unregister_device(input_dev); + ally_cfg->input = NULL; // Prevent double free when kfree(ally_cfg) happens + +free_input_dev: + devm_kfree(&hdev->dev, input_dev); + +free_ally_cfg: + devm_kfree(&hdev->dev, ally_cfg); + return ERR_PTR(err); +} + +static void ally_cfg_remove(struct hid_device *hdev) +{ + // __gamepad_set_mode(hdev, drvdata.gamepad_cfg, xpad_mode_mouse); + sysfs_remove_groups(&hdev->dev.kobj, gamepad_device_attr_groups); +} + +/**************************************************************************************************/ +/* ROG Ally gamepad i/o and force-feedback */ +/**************************************************************************************************/ +static int ally_x_raw_event(struct ally_x_device *ally_x, struct hid_report *report, u8 *data, + int size) +{ + struct ally_x_input_report *in_report; + unsigned long flags; + u8 byte; + + if (data[0] == 0x0B) { + in_report = (struct ally_x_input_report *)&data[1]; + + input_report_abs(ally_x->input, ABS_X, in_report->x); + input_report_abs(ally_x->input, ABS_Y, in_report->y); + input_report_abs(ally_x->input, ABS_RX, in_report->rx); + input_report_abs(ally_x->input, ABS_RY, in_report->ry); + input_report_abs(ally_x->input, ABS_Z, in_report->z); + input_report_abs(ally_x->input, ABS_RZ, in_report->rz); + + byte = in_report->buttons[0]; + input_report_key(ally_x->input, BTN_A, byte & BIT(0)); + input_report_key(ally_x->input, BTN_B, byte & BIT(1)); + input_report_key(ally_x->input, BTN_X, byte & BIT(2)); + input_report_key(ally_x->input, BTN_Y, byte & BIT(3)); + input_report_key(ally_x->input, BTN_TL, byte & BIT(4)); + input_report_key(ally_x->input, BTN_TR, byte & BIT(5)); + input_report_key(ally_x->input, BTN_SELECT, byte & BIT(6)); + input_report_key(ally_x->input, BTN_START, byte & BIT(7)); + + byte = in_report->buttons[1]; + input_report_key(ally_x->input, BTN_THUMBL, byte & BIT(0)); + input_report_key(ally_x->input, BTN_THUMBR, byte & BIT(1)); + input_report_key(ally_x->input, BTN_MODE, byte & BIT(2)); + + byte = in_report->buttons[2]; + input_report_abs(ally_x->input, ABS_HAT0X, hat_values[byte][0]); + input_report_abs(ally_x->input, ABS_HAT0Y, hat_values[byte][1]); + } + /* + * The MCU used on Ally provides many devices: gamepad, keyboord, mouse, other. + * The AC and QAM buttons route through another interface making it difficult to + * use the events unless we grab those and use them here. Only works for Ally X. + */ + else if (data[0] == 0x5A) { + if (ally_x->qam_btns_steam_mode) { + spin_lock_irqsave(&ally_x->lock, flags); + if ((data[1] == 0x38 || data[1] == 0x93) && !ally_x->update_qam_btn) { + ally_x->update_qam_btn = true; + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + } + spin_unlock_irqrestore(&ally_x->lock, flags); + /* Left/XBox button. Long press does ctrl+alt+del which we can't catch */ + input_report_key(ally_x->input, BTN_MODE, data[1] == 0xA6); + } else { + input_report_key(ally_x->input, KEY_F16, data[1] == 0xA6); + input_report_key(ally_x->input, KEY_PROG1, data[1] == 0x38 || data[1] == 0x93); + } + /* QAM long press */ + input_report_key(ally_x->input, KEY_F17, data[1] == 0xA7); + /* QAM long press released */ + input_report_key(ally_x->input, KEY_F18, data[1] == 0xA8); + } + + input_sync(ally_x->input); + + return 0; +} + +static struct input_dev *ally_x_alloc_input_dev(struct hid_device *hdev, + const char *name_suffix) +{ + struct input_dev *input_dev; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) + return ERR_PTR(-ENOMEM); + + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->uniq = hdev->uniq; + input_dev->name = "ASUS ROG Ally X Gamepad"; + + input_set_drvdata(input_dev, hdev); + + return input_dev; +} + +static int ally_x_play_effect(struct input_dev *idev, void *data, struct ff_effect *effect) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + unsigned long flags; + + if (effect->type != FF_RUMBLE) + return 0; + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->ff_packet->ff.magnitude_strong = effect->u.rumble.strong_magnitude / 512; + ally_x->ff_packet->ff.magnitude_weak = effect->u.rumble.weak_magnitude / 512; + ally_x->update_ff = true; + spin_unlock_irqrestore(&ally_x->lock, flags); + + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + + return 0; +} + +static void ally_x_work(struct work_struct *work) +{ + struct ally_x_device *ally_x = container_of(work, struct ally_x_device, output_worker); + struct ff_report *ff_report = NULL; + bool update_qam = false; + bool update_ff = false; + unsigned long flags; + + spin_lock_irqsave(&ally_x->lock, flags); + update_ff = ally_x->update_ff; + if (ally_x->update_ff) { + ff_report = kmemdup(ally_x->ff_packet, sizeof(*ally_x->ff_packet), GFP_KERNEL); + ally_x->update_ff = false; + } + update_qam = ally_x->update_qam_btn; + spin_unlock_irqrestore(&ally_x->lock, flags); + + if (update_ff && ff_report) + asus_dev_set_report(ally_x->hdev, (u8 *)ff_report, sizeof(*ff_report)); + kfree(ff_report); + + if (update_qam) { + /* + * The sleeps here are required to allow steam to register the button combo. + */ + usleep_range(1000, 2000); + input_report_key(ally_x->input, BTN_MODE, 1); + input_sync(ally_x->input); + + msleep(80); + input_report_key(ally_x->input, BTN_A, 1); + input_sync(ally_x->input); + + msleep(80); + input_report_key(ally_x->input, BTN_A, 0); + input_sync(ally_x->input); + + msleep(80); + input_report_key(ally_x->input, BTN_MODE, 0); + input_sync(ally_x->input); + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->update_qam_btn = false; + spin_unlock_irqrestore(&ally_x->lock, flags); + } +} + +static struct input_dev *ally_x_setup_input(struct hid_device *hdev) +{ + int ret, abs_min = 0, js_abs_max = 65535, tr_abs_max = 1023; + struct input_dev *input; + + input = ally_x_alloc_input_dev(hdev, NULL); + if (IS_ERR(input)) + return ERR_CAST(input); + + input_set_abs_params(input, ABS_X, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_Y, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_RX, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_RY, abs_min, js_abs_max, 0, 0); + input_set_abs_params(input, ABS_Z, abs_min, tr_abs_max, 0, 0); + input_set_abs_params(input, ABS_RZ, abs_min, tr_abs_max, 0, 0); + input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0); + input_set_capability(input, EV_KEY, BTN_A); + input_set_capability(input, EV_KEY, BTN_B); + input_set_capability(input, EV_KEY, BTN_X); + input_set_capability(input, EV_KEY, BTN_Y); + input_set_capability(input, EV_KEY, BTN_TL); + input_set_capability(input, EV_KEY, BTN_TR); + input_set_capability(input, EV_KEY, BTN_SELECT); + input_set_capability(input, EV_KEY, BTN_START); + input_set_capability(input, EV_KEY, BTN_MODE); + input_set_capability(input, EV_KEY, BTN_THUMBL); + input_set_capability(input, EV_KEY, BTN_THUMBR); + + input_set_capability(input, EV_KEY, KEY_PROG1); + input_set_capability(input, EV_KEY, KEY_F16); + input_set_capability(input, EV_KEY, KEY_F17); + input_set_capability(input, EV_KEY, KEY_F18); + + input_set_capability(input, EV_FF, FF_RUMBLE); + input_ff_create_memless(input, NULL, ally_x_play_effect); + + ret = input_register_device(input); + if (ret) + return ERR_PTR(ret); + + return input; +} + +static ssize_t ally_x_qam_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + + return sysfs_emit(buf, "%d\n", ally_x->qam_btns_steam_mode); +} + +static ssize_t ally_x_qam_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + ally_x->qam_btns_steam_mode = val; + + return count; +} +ALLY_DEVICE_ATTR_RW(ally_x_qam_mode, qam_mode); + +static struct ally_x_device *ally_x_create(struct hid_device *hdev) +{ + uint8_t max_output_report_size; + struct ally_x_device *ally_x; + struct ff_report *report; + int ret; + + ally_x = devm_kzalloc(&hdev->dev, sizeof(*ally_x), GFP_KERNEL); + if (!ally_x) + return ERR_PTR(-ENOMEM); + + ally_x->hdev = hdev; + INIT_WORK(&ally_x->output_worker, ally_x_work); + spin_lock_init(&ally_x->lock); + ally_x->output_worker_initialized = true; + ally_x->qam_btns_steam_mode = + true; /* Always default to steam mode, it can be changed by userspace attr */ + + max_output_report_size = sizeof(struct ally_x_input_report); + report = devm_kzalloc(&hdev->dev, sizeof(*report), GFP_KERNEL); + if (!report) { + ret = -ENOMEM; + goto free_ally_x; + } + + /* None of these bytes will change for the FF command for now */ + report->report_id = 0x0D; + report->ff.enable = 0x0F; /* Enable all by default */ + report->ff.pulse_sustain_10ms = 0xFF; /* Duration */ + report->ff.pulse_release_10ms = 0x00; /* Start Delay */ + report->ff.loop_count = 0xEB; /* Loop Count */ + ally_x->ff_packet = report; + + ally_x->input = ally_x_setup_input(hdev); + if (IS_ERR(ally_x->input)) { + ret = PTR_ERR(ally_x->input); + goto free_ff_packet; + } + + if (sysfs_create_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr)) { + ret = -ENODEV; + goto unregister_input; + } + + ally_x->update_ff = true; + if (ally_x->output_worker_initialized) + schedule_work(&ally_x->output_worker); + + hid_info(hdev, "Registered Ally X controller using %s\n", + dev_name(&ally_x->input->dev)); + return ally_x; + +unregister_input: + input_unregister_device(ally_x->input); +free_ff_packet: + kfree(ally_x->ff_packet); +free_ally_x: + kfree(ally_x); + return ERR_PTR(ret); +} + +static void ally_x_remove(struct hid_device *hdev) +{ + struct ally_x_device *ally_x = drvdata.ally_x; + unsigned long flags; + + spin_lock_irqsave(&ally_x->lock, flags); + ally_x->output_worker_initialized = false; + spin_unlock_irqrestore(&ally_x->lock, flags); + cancel_work_sync(&ally_x->output_worker); + sysfs_remove_file(&hdev->dev.kobj, &dev_attr_ally_x_qam_mode.attr); +} + +/**************************************************************************************************/ +/* ROG Ally LED control */ +/**************************************************************************************************/ +static void ally_rgb_schedule_work(struct ally_rgb_dev *led) +{ + unsigned long flags; + + spin_lock_irqsave(&led->lock, flags); + if (!led->removed) + schedule_work(&led->work); + spin_unlock_irqrestore(&led->lock, flags); +} + +/* + * The RGB still has the basic 0-3 level brightness. Since the multicolour + * brightness is being used in place, set this to max + */ +static int ally_rgb_set_bright_base_max(struct hid_device *hdev) +{ + u8 buf[] = { FEATURE_KBD_LED_REPORT_ID1, 0xba, 0xc5, 0xc4, 0x02 }; + + return asus_dev_set_report(hdev, buf, sizeof(buf)); +} + +/* + * Persistent mask of power states in which the MCU animates the joystick + * rings (factory default: all). With the sleep bit set the MCU breathes the + * rings through s2idle whenever it has power, which on AC it always does, + * regardless of mcu_powersave. + */ +#define ALLY_LED_POWER_BOOT BIT(0) +#define ALLY_LED_POWER_AWAKE BIT(1) +#define ALLY_LED_POWER_SLEEP BIT(2) +#define ALLY_LED_POWER_SHUTDOWN BIT(3) + +/* Default to no animation during sleep, adjustable via sleep_animation attr */ +static u8 ally_led_power_mask = ALLY_LED_POWER_BOOT | ALLY_LED_POWER_AWAKE | + ALLY_LED_POWER_SHUTDOWN; + +static int ally_set_led_power_states(struct hid_device *hdev, u8 mask) +{ + u8 buf[16] = { [0] = FEATURE_ROG_ALLY_REPORT_ID, + [1] = FEATURE_ROG_ALLY_CODE_PAGE, + [2] = xpad_cmd_set_led_power, + [3] = xpad_cmd_len_led_power, + [4] = mask }; + + return asus_dev_set_report(hdev, buf, sizeof(buf)); +} + +static ssize_t sleep_animation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "%d\n", !!(ally_led_power_mask & ALLY_LED_POWER_SLEEP)); +} + +static ssize_t sleep_animation_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct ally_rgb_dev *led_rgb = container_of(mc_cdev, struct ally_rgb_dev, led_rgb_dev); + bool enable; + int ret; + + ret = kstrtobool(buf, &enable); + if (ret < 0) + return ret; + + if (enable) + ally_led_power_mask |= ALLY_LED_POWER_SLEEP; + else + ally_led_power_mask &= ~ALLY_LED_POWER_SLEEP; + + ret = ally_set_led_power_states(led_rgb->hdev, ally_led_power_mask); + if (ret < 0) + return ret; + + return count; +} +DEVICE_ATTR_RW(sleep_animation); + +static ssize_t sleep_animation_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0 1\n"); +} +DEVICE_ATTR_RO(sleep_animation_index); + +static struct attribute *ally_rgb_attrs[] = { + &dev_attr_sleep_animation.attr, + &dev_attr_sleep_animation_index.attr, + NULL +}; + +static const struct attribute_group ally_rgb_attr_group = { + .attrs = ally_rgb_attrs, +}; + +static void ally_rgb_do_work(struct work_struct *work) +{ + struct ally_rgb_dev *led = container_of(work, struct ally_rgb_dev, work); + int ret; + unsigned long flags; + + u8 buf[16] = { [0] = FEATURE_ROG_ALLY_REPORT_ID, + [1] = FEATURE_ROG_ALLY_CODE_PAGE, + [2] = xpad_cmd_set_leds, + [3] = xpad_cmd_len_leds }; + + spin_lock_irqsave(&led->lock, flags); + if (!led->update_rgb) { + spin_unlock_irqrestore(&led->lock, flags); + return; + } + + for (int i = 0; i < 4; i++) { + buf[5 + i * 3] = drvdata.led_rgb_dev->green[i]; + buf[6 + i * 3] = drvdata.led_rgb_dev->blue[i]; + buf[4 + i * 3] = drvdata.led_rgb_dev->red[i]; + } + led->update_rgb = false; + + spin_unlock_irqrestore(&led->lock, flags); + + ret = asus_dev_set_report(led->hdev, buf, sizeof(buf)); + if (ret < 0) + hid_err(led->hdev, "Ally failed to set gamepad backlight: %d\n", ret); +} + +static void ally_rgb_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct ally_rgb_dev *led = container_of(mc_cdev, struct ally_rgb_dev, led_rgb_dev); + int intensity, bright; + unsigned long flags; + + led_mc_calc_color_components(mc_cdev, brightness); + spin_lock_irqsave(&led->lock, flags); + led->update_rgb = true; + bright = mc_cdev->led_cdev.brightness; + for (int i = 0; i < 4; i++) { + intensity = mc_cdev->subled_info[i].intensity; + drvdata.led_rgb_dev->red[i] = (((intensity >> 16) & 0xFF) * bright) / 255; + drvdata.led_rgb_dev->green[i] = (((intensity >> 8) & 0xFF) * bright) / 255; + drvdata.led_rgb_dev->blue[i] = ((intensity & 0xFF) * bright) / 255; + } + spin_unlock_irqrestore(&led->lock, flags); + drvdata.led_rgb_data.initialized = true; + + ally_rgb_schedule_work(led); +} + +static int ally_rgb_set_static_from_multi(struct hid_device *hdev) +{ + u8 buf[17] = {FEATURE_KBD_LED_REPORT_ID1, 0xb3}; + int ret; + + /* + * Set single zone single colour based on the first LED of EC software mode. + * buf[2] = zone, buf[3] = mode + */ + buf[4] = drvdata.led_rgb_data.red[0]; + buf[5] = drvdata.led_rgb_data.green[0]; + buf[6] = drvdata.led_rgb_data.blue[0]; + + ret = asus_dev_set_report(hdev, buf, sizeof(buf)); + if (ret < 0) + return ret; + + ret = asus_dev_set_report(hdev, EC_MODE_LED_APPLY, sizeof(EC_MODE_LED_APPLY)); + if (ret < 0) + return ret; + + return asus_dev_set_report(hdev, EC_MODE_LED_SET, sizeof(EC_MODE_LED_SET)); +} + +/* + * Store the RGB values for restoring on resume, and set the static mode to the first LED colour +*/ +static void ally_rgb_store_settings(void) +{ + int arr_size = sizeof(drvdata.led_rgb_data.red); + + struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; + + drvdata.led_rgb_data.brightness = led_rgb->led_rgb_dev.led_cdev.brightness; + + memcpy(drvdata.led_rgb_data.red, led_rgb->red, arr_size); + memcpy(drvdata.led_rgb_data.green, led_rgb->green, arr_size); + memcpy(drvdata.led_rgb_data.blue, led_rgb->blue, arr_size); + + ally_rgb_set_static_from_multi(led_rgb->hdev); +} + +static void ally_rgb_restore_settings(struct ally_rgb_dev *led_rgb, struct led_classdev *led_cdev, + struct mc_subled *mc_led_info) +{ + int arr_size = sizeof(drvdata.led_rgb_data.red); + + memcpy(led_rgb->red, drvdata.led_rgb_data.red, arr_size); + memcpy(led_rgb->green, drvdata.led_rgb_data.green, arr_size); + memcpy(led_rgb->blue, drvdata.led_rgb_data.blue, arr_size); + for (int i = 0; i < 4; i++) { + mc_led_info[i].intensity = (drvdata.led_rgb_data.red[i] << 16) | + (drvdata.led_rgb_data.green[i] << 8) | + drvdata.led_rgb_data.blue[i]; + } + led_cdev->brightness = drvdata.led_rgb_data.brightness; +} + +/* Set LEDs. Call after any setup. */ +static void ally_rgb_resume(void) +{ + struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; + struct led_classdev *led_cdev; + struct mc_subled *mc_led_info; + + if (!led_rgb) + return; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + mc_led_info = led_rgb->led_rgb_dev.subled_info; + + if (drvdata.led_rgb_data.initialized) { + ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info); + led_rgb->update_rgb = true; + ally_rgb_schedule_work(led_rgb); + ally_rgb_set_bright_base_max(led_rgb->hdev); + } +} + +static int ally_rgb_register(struct hid_device *hdev, struct ally_rgb_dev *led_rgb) +{ + struct mc_subled *mc_led_info; + struct led_classdev *led_cdev; + + mc_led_info = + devm_kmalloc_array(&hdev->dev, 12, sizeof(*mc_led_info), GFP_KERNEL | __GFP_ZERO); + if (!mc_led_info) + return -ENOMEM; + + mc_led_info[0].color_index = LED_COLOR_ID_RGB; + mc_led_info[1].color_index = LED_COLOR_ID_RGB; + mc_led_info[2].color_index = LED_COLOR_ID_RGB; + mc_led_info[3].color_index = LED_COLOR_ID_RGB; + + led_rgb->led_rgb_dev.subled_info = mc_led_info; + led_rgb->led_rgb_dev.num_colors = 4; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + led_cdev->brightness = 128; + led_cdev->name = "ally:rgb:joystick_rings"; + led_cdev->max_brightness = 255; + led_cdev->brightness_set = ally_rgb_set; + + if (drvdata.led_rgb_data.initialized) { + ally_rgb_restore_settings(led_rgb, led_cdev, mc_led_info); + } + + return devm_led_classdev_multicolor_register(&hdev->dev, &led_rgb->led_rgb_dev); +} + +static struct ally_rgb_dev *ally_rgb_create(struct hid_device *hdev) +{ + struct ally_rgb_dev *led_rgb; + int ret; + + led_rgb = devm_kzalloc(&hdev->dev, sizeof(struct ally_rgb_dev), GFP_KERNEL); + if (!led_rgb) + return ERR_PTR(-ENOMEM); + + ret = ally_rgb_register(hdev, led_rgb); + if (ret < 0) { + cancel_work_sync(&led_rgb->work); + devm_kfree(&hdev->dev, led_rgb); + return ERR_PTR(ret); + } + + led_rgb->hdev = hdev; + led_rgb->removed = false; + + INIT_WORK(&led_rgb->work, ally_rgb_do_work); + led_rgb->output_worker_initialized = true; + spin_lock_init(&led_rgb->lock); + + ally_rgb_set_bright_base_max(hdev); + + /* The multicolor LED core owns led_cdev->groups, add ours separately */ + ret = device_add_group(led_rgb->led_rgb_dev.led_cdev.dev, &ally_rgb_attr_group); + if (ret) { + hid_err(hdev, "Failed to create sleep_animation attributes: %d\n", ret); + goto skip_led_power; + } + led_rgb->attr_group_added = true; + + ret = ally_set_led_power_states(hdev, ally_led_power_mask); + if (ret < 0) + hid_err(hdev, "Failed to set ring LED power states: %d\n", ret); + +skip_led_power: + /* Not marked as initialized unless ally_rgb_set() is called */ + if (drvdata.led_rgb_data.initialized) { + msleep(1500); + led_rgb->update_rgb = true; + ally_rgb_schedule_work(led_rgb); + } + + return led_rgb; +} + +static void ally_rgb_remove(struct hid_device *hdev) +{ + struct ally_rgb_dev *led_rgb = drvdata.led_rgb_dev; + unsigned long flags; + int ep; + + ep = get_endpoint_address(hdev); + if (ep != ROG_ALLY_CFG_INTF_IN) + return; + + if (!drvdata.led_rgb_dev || led_rgb->removed) + return; + + spin_lock_irqsave(&led_rgb->lock, flags); + led_rgb->removed = true; + led_rgb->output_worker_initialized = false; + spin_unlock_irqrestore(&led_rgb->lock, flags); + cancel_work_sync(&led_rgb->work); + if (led_rgb->attr_group_added) + device_remove_group(led_rgb->led_rgb_dev.led_cdev.dev, &ally_rgb_attr_group); + devm_led_classdev_multicolor_unregister(&hdev->dev, &led_rgb->led_rgb_dev); + + hid_info(hdev, "Removed Ally RGB interface"); +} + +/**************************************************************************************************/ +/* ROG Ally driver init */ +/**************************************************************************************************/ + +static int ally_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, + int size) +{ + struct ally_gamepad_cfg *cfg = drvdata.gamepad_cfg; + struct ally_x_device *ally_x = drvdata.ally_x; + + if (ally_x) { + if ((hdev->bus == BUS_USB && report->id == ALLY_X_INPUT_REPORT_USB && + size == ALLY_X_INPUT_REPORT_USB_SIZE) || + (data[0] == 0x5A)) { + ally_x_raw_event(ally_x, report, data, size); + } else { + return -1; + } + } + + if (cfg && !ally_x) { + input_report_key(cfg->input, KEY_PROG1, data[1] == 0x38); + input_report_key(cfg->input, KEY_F16, data[1] == 0xA6); + input_report_key(cfg->input, KEY_F17, data[1] == 0xA7); + input_report_key(cfg->input, KEY_F18, data[1] == 0xA8); + input_sync(cfg->input); + } + + return 0; +} + +static int ally_hid_init(struct hid_device *hdev) +{ + int ret; + + ret = asus_dev_set_report(hdev, EC_INIT_STRING, sizeof(EC_INIT_STRING)); + if (ret < 0) { + hid_err(hdev, "Ally failed to send init command: %d\n", ret); + return ret; + } + + ret = asus_dev_set_report(hdev, FORCE_FEEDBACK_OFF, sizeof(FORCE_FEEDBACK_OFF)); + if (ret < 0) + hid_err(hdev, "Ally failed to send init command: %d\n", ret); + + return ret; +} + +static void ally_disable_nkey_wakeup(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev; + + if (!intf) + return; + + udev = interface_to_usbdev(intf); + if (!udev) + return; + + /* HACK: Mark Ally N-Key as incapable of wakeup */ + device_set_wakeup_enable(&udev->dev, false); + hid_info(hdev, "Disabled wakeup capability on %s\n", dev_name(&udev->dev)); +} + +static int ally_hid_probe(struct hid_device *hdev, const struct hid_device_id *_id) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = interface_to_usbdev(intf); + u16 idProduct = le16_to_cpu(udev->descriptor.idProduct); + int ret, ep; + + ep = get_endpoint_address(hdev); + if (ep < 0) + return ep; + + if (ep != ROG_ALLY_CFG_INTF_IN && + ep != ROG_ALLY_X_INTF_IN) + return -ENODEV; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + return ret; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + goto err_stop; + } + + /* Initialize MCU even before alloc */ + ret = ally_hid_init(hdev); + if (ret < 0) + return ret; + + drvdata.hdev = hdev; + hid_set_drvdata(hdev, &drvdata); + + /* This should almost always exist */ + if (ep == ROG_ALLY_CFG_INTF_IN) { + validate_mcu_fw_version(hdev, idProduct); + ally_disable_nkey_wakeup(hdev); + + drvdata.led_rgb_dev = ally_rgb_create(hdev); + if (IS_ERR(drvdata.led_rgb_dev)) + hid_err(hdev, "Failed to create Ally gamepad LEDs.\n"); + else + hid_info(hdev, "Created Ally RGB LED controls.\n"); + + drvdata.gamepad_cfg = ally_gamepad_cfg_create(hdev); + if (IS_ERR(drvdata.gamepad_cfg)) + hid_err(hdev, "Failed to create Ally gamepad attributes.\n"); + else + hid_info(hdev, "Created Ally gamepad attributes.\n"); + + if (IS_ERR(drvdata.led_rgb_dev) && IS_ERR(drvdata.gamepad_cfg)) + goto err_close; + } + + /* May or may not exist */ + if (ep == ROG_ALLY_X_INTF_IN) { + drvdata.ally_x = ally_x_create(hdev); + if (IS_ERR(drvdata.ally_x)) { + hid_err(hdev, "Failed to create Ally X gamepad.\n"); + drvdata.ally_x = NULL; + goto err_close; + } + hid_info(hdev, "Created Ally X controller.\n"); + + // Not required since we send this inputs ep through the gamepad input dev + if (drvdata.gamepad_cfg && drvdata.gamepad_cfg->input) { + input_unregister_device(drvdata.gamepad_cfg->input); + hid_info(hdev, "Ally X removed unrequired input dev.\n"); + } + } + + return 0; + +err_close: + hid_hw_close(hdev); +err_stop: + hid_hw_stop(hdev); + return ret; +} + +static void ally_hid_remove(struct hid_device *hdev) +{ + if (drvdata.led_rgb_dev) + ally_rgb_remove(hdev); + + if (drvdata.ally_x) + ally_x_remove(hdev); + + if (drvdata.gamepad_cfg) + ally_cfg_remove(hdev); + + hid_hw_close(hdev); + hid_hw_stop(hdev); +} + +static int ally_hid_resume(struct hid_device *hdev) +{ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; + int err; + + if (!ally_cfg) + return 0; + + err = _gamepad_apply_all(hdev, ally_cfg); + if (err) + return err; + + return 0; +} + +static int ally_hid_reset_resume(struct hid_device *hdev) +{ + int ep = get_endpoint_address(hdev); + if (ep != ROG_ALLY_CFG_INTF_IN) + return 0; + + ally_hid_init(hdev); + ally_rgb_resume(); + + return ally_hid_resume(hdev); +} + +static int ally_pm_thaw(struct device *dev) +{ + struct hid_device *hdev = to_hid_device(dev); + + return ally_hid_reset_resume(hdev); +} + +static int ally_pm_suspend(struct device *dev) +{ + if (drvdata.led_rgb_dev) { + ally_rgb_store_settings(); + } + + return 0; +} + +static const struct dev_pm_ops ally_pm_ops = { + .thaw = ally_pm_thaw, + .suspend = ally_pm_suspend, + .poweroff = ally_pm_suspend, +}; + +MODULE_DEVICE_TABLE(hid, rog_ally_devices); + +static struct hid_driver rog_ally_cfg = { .name = "asus_rog_ally", + .id_table = rog_ally_devices, + .probe = ally_hid_probe, + .remove = ally_hid_remove, + .raw_event = ally_raw_event, + /* HID is the better place for resume functions, not pm_ops */ + .resume = ally_hid_resume, + /* ALLy 1 requires this to reset device state correctly */ + .reset_resume = ally_hid_reset_resume, + .driver = { + .pm = &ally_pm_ops, + } +}; + +static int __init rog_ally_init(void) +{ + return hid_register_driver(&rog_ally_cfg); +} + +static void __exit rog_ally_exit(void) +{ + hid_unregister_driver(&rog_ally_cfg); +} + +module_init(rog_ally_init); +module_exit(rog_ally_exit); + +MODULE_IMPORT_NS("ASUS_WMI"); +MODULE_IMPORT_NS("HID_ASUS"); +MODULE_AUTHOR("Luke D. Jones"); +MODULE_DESCRIPTION("HID Driver for ASUS ROG Ally gamepad configuration."); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-asus-ally.h b/drivers/hid/hid-asus-ally.h new file mode 100644 index 000000000000..c73e11619661 --- /dev/null +++ b/drivers/hid/hid-asus-ally.h @@ -0,0 +1,400 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * + * HID driver for Asus ROG laptops and Ally + * + * Copyright (c) 2023 Luke Jones + */ + +#include +#include + +/* + * the xpad_mode is used inside the mode setting packet and is used + * for indexing (xpad_mode - 1) + */ +enum xpad_mode { + xpad_mode_game = 0x01, + xpad_mode_wasd = 0x02, + xpad_mode_mouse = 0x03, +}; + +/* the xpad_cmd determines which feature is set or queried */ +enum xpad_cmd { + xpad_cmd_set_mode = 0x01, + xpad_cmd_set_mapping = 0x02, + xpad_cmd_set_js_dz = 0x04, /* deadzones */ + xpad_cmd_set_tr_dz = 0x05, /* deadzones */ + xpad_cmd_set_vibe_intensity = 0x06, + xpad_cmd_set_leds = 0x08, + xpad_cmd_set_led_power = 0x09, + xpad_cmd_check_ready = 0x0A, + xpad_cmd_set_turbo = 0x0F, + xpad_cmd_set_response_curve = 0x13, + xpad_cmd_set_adz = 0x18, +}; + +/* the xpad_cmd determines which feature is set or queried */ +enum xpad_cmd_len { + xpad_cmd_len_mode = 0x01, + xpad_cmd_len_mapping = 0x2c, + xpad_cmd_len_deadzone = 0x04, + xpad_cmd_len_vibe_intensity = 0x02, + xpad_cmd_len_leds = 0x0C, + xpad_cmd_len_led_power = 0x01, + xpad_cmd_len_turbo = 0x20, + xpad_cmd_len_response_curve = 0x09, + xpad_cmd_len_adz = 0x02, +}; + +/* Values correspond to the actual HID byte value required */ +enum btn_pair_index { + btn_pair_dpad_u_d = 0x01, + btn_pair_dpad_l_r = 0x02, + btn_pair_ls_rs = 0x03, + btn_pair_lb_rb = 0x04, + btn_pair_a_b = 0x05, + btn_pair_x_y = 0x06, + btn_pair_view_menu = 0x07, + btn_pair_m1_m2 = 0x08, + btn_pair_lt_rt = 0x09, +}; + +#define BTN_PAD_A 0x0101000000000000 +#define BTN_PAD_B 0x0102000000000000 +#define BTN_PAD_X 0x0103000000000000 +#define BTN_PAD_Y 0x0104000000000000 +#define BTN_PAD_LB 0x0105000000000000 +#define BTN_PAD_RB 0x0106000000000000 +#define BTN_PAD_LS 0x0107000000000000 +#define BTN_PAD_RS 0x0108000000000000 +#define BTN_PAD_DPAD_UP 0x0109000000000000 +#define BTN_PAD_DPAD_DOWN 0x010A000000000000 +#define BTN_PAD_DPAD_LEFT 0x010B000000000000 +#define BTN_PAD_DPAD_RIGHT 0x010C000000000000 +#define BTN_PAD_LT 0x010D000000000000 +#define BTN_PAD_RT 0x010E000000000000 +#define BTN_PAD_VIEW 0x0111000000000000 +#define BTN_PAD_MENU 0x0112000000000000 +#define BTN_PAD_XBOX 0x0113000000000000 + +#define BTN_KB_M2 0x02008E0000000000 +#define BTN_KB_M1 0x02008F0000000000 +#define BTN_KB_ESC 0x0200760000000000 +#define BTN_KB_F1 0x0200500000000000 +#define BTN_KB_F2 0x0200600000000000 +#define BTN_KB_F3 0x0200400000000000 +#define BTN_KB_F4 0x02000C0000000000 +#define BTN_KB_F5 0x0200030000000000 +#define BTN_KB_F6 0x02000B0000000000 +#define BTN_KB_F7 0x0200800000000000 +#define BTN_KB_F8 0x02000A0000000000 +#define BTN_KB_F9 0x0200010000000000 +#define BTN_KB_F10 0x0200090000000000 +#define BTN_KB_F11 0x0200780000000000 +#define BTN_KB_F12 0x0200070000000000 +#define BTN_KB_F14 0x0200180000000000 +#define BTN_KB_F15 0x0200100000000000 +#define BTN_KB_BACKTICK 0x02000E0000000000 +#define BTN_KB_1 0x0200160000000000 +#define BTN_KB_2 0x02001E0000000000 +#define BTN_KB_3 0x0200260000000000 +#define BTN_KB_4 0x0200250000000000 +#define BTN_KB_5 0x02002E0000000000 +#define BTN_KB_6 0x0200360000000000 +#define BTN_KB_7 0x02003D0000000000 +#define BTN_KB_8 0x02003E0000000000 +#define BTN_KB_9 0x0200460000000000 +#define BTN_KB_0 0x0200450000000000 +#define BTN_KB_HYPHEN 0x02004E0000000000 +#define BTN_KB_EQUALS 0x0200550000000000 +#define BTN_KB_BACKSPACE 0x0200660000000000 +#define BTN_KB_TAB 0x02000D0000000000 +#define BTN_KB_Q 0x0200150000000000 +#define BTN_KB_W 0x02001D0000000000 +#define BTN_KB_E 0x0200240000000000 +#define BTN_KB_R 0x02002D0000000000 +#define BTN_KB_T 0x02002C0000000000 +#define BTN_KB_Y 0x0200350000000000 +#define BTN_KB_U 0x02003C0000000000 +#define BTN_KB_O 0x0200440000000000 +#define BTN_KB_P 0x02004D0000000000 +#define BTN_KB_LBRACKET 0x0200540000000000 +#define BTN_KB_RBRACKET 0x02005B0000000000 +#define BTN_KB_BACKSLASH 0x02005D0000000000 +#define BTN_KB_CAPS 0x0200580000000000 +#define BTN_KB_A 0x02001C0000000000 +#define BTN_KB_S 0x02001B0000000000 +#define BTN_KB_D 0x0200230000000000 +#define BTN_KB_F 0x02002B0000000000 +#define BTN_KB_G 0x0200340000000000 +#define BTN_KB_H 0x0200330000000000 +#define BTN_KB_J 0x02003B0000000000 +#define BTN_KB_K 0x0200420000000000 +#define BTN_KB_L 0x02004B0000000000 +#define BTN_KB_SEMI 0x02004C0000000000 +#define BTN_KB_QUOTE 0x0200520000000000 +#define BTN_KB_RET 0x02005A0000000000 +#define BTN_KB_LSHIFT 0x0200880000000000 +#define BTN_KB_Z 0x02001A0000000000 +#define BTN_KB_X 0x0200220000000000 +#define BTN_KB_C 0x0200210000000000 +#define BTN_KB_V 0x02002A0000000000 +#define BTN_KB_B 0x0200320000000000 +#define BTN_KB_N 0x0200310000000000 +#define BTN_KB_M 0x02003A0000000000 +#define BTN_KB_COMMA 0x0200410000000000 +#define BTN_KB_PERIOD 0x0200490000000000 +#define BTN_KB_RSHIFT 0x0200890000000000 +#define BTN_KB_LCTL 0x02008C0000000000 +#define BTN_KB_META 0x0200820000000000 +#define BTN_KB_LALT 0x02008A0000000000 +#define BTN_KB_SPACE 0x0200290000000000 +#define BTN_KB_RALT 0x02008B0000000000 +#define BTN_KB_MENU 0x0200840000000000 +#define BTN_KB_RCTL 0x02008D0000000000 +#define BTN_KB_PRNTSCN 0x0200C30000000000 +#define BTN_KB_SCRLCK 0x02007E0000000000 +#define BTN_KB_PAUSE 0x0200910000000000 +#define BTN_KB_INS 0x0200C20000000000 +#define BTN_KB_HOME 0x0200940000000000 +#define BTN_KB_PGUP 0x0200960000000000 +#define BTN_KB_DEL 0x0200C00000000000 +#define BTN_KB_END 0x0200950000000000 +#define BTN_KB_PGDWN 0x0200970000000000 +#define BTN_KB_UP_ARROW 0x0200980000000000 +#define BTN_KB_DOWN_ARROW 0x0200990000000000 +#define BTN_KB_LEFT_ARROW 0x0200910000000000 +#define BTN_KB_RIGHT_ARROW 0x02009B0000000000 + +#define BTN_NUMPAD_LOCK 0x0200770000000000 +#define BTN_NUMPAD_FWDSLASH 0x0200900000000000 +#define BTN_NUMPAD_ASTERISK 0x02007C0000000000 +#define BTN_NUMPAD_HYPHEN 0x02007B0000000000 +#define BTN_NUMPAD_0 0x0200700000000000 +#define BTN_NUMPAD_1 0x0200690000000000 +#define BTN_NUMPAD_2 0x0200720000000000 +#define BTN_NUMPAD_3 0x02007A0000000000 +#define BTN_NUMPAD_4 0x02006B0000000000 +#define BTN_NUMPAD_5 0x0200730000000000 +#define BTN_NUMPAD_6 0x0200740000000000 +#define BTN_NUMPAD_7 0x02006C0000000000 +#define BTN_NUMPAD_8 0x0200750000000000 +#define BTN_NUMPAD_9 0x02007D0000000000 +#define BTN_NUMPAD_PLUS 0x0200790000000000 +#define BTN_NUMPAD_ENTER 0x0200810000000000 +#define BTN_NUMPAD_PERIOD 0x0200710000000000 + +#define BTN_MOUSE_LCLICK 0x0300000001000000 +#define BTN_MOUSE_RCLICK 0x0300000002000000 +#define BTN_MOUSE_MCLICK 0x0300000003000000 +#define BTN_MOUSE_WHEEL_UP 0x0300000004000000 +#define BTN_MOUSE_WHEEL_DOWN 0x0300000005000000 + +#define BTN_MEDIA_SCREENSHOT 0x0500001600000000 +#define BTN_MEDIA_SHOW_KEYBOARD 0x0500001900000000 +#define BTN_MEDIA_SHOW_DESKTOP 0x0500001C00000000 +#define BTN_MEDIA_START_RECORDING 0x0500001E00000000 +#define BTN_MEDIA_MIC_OFF 0x0500000100000000 +#define BTN_MEDIA_VOL_DOWN 0x0500000200000000 +#define BTN_MEDIA_VOL_UP 0x0500000300000000 + +#define ALLY_DEVICE_ATTR_WO(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0200, NULL, _name##_store) + +/* required so we can have nested attributes with same name but different functions */ +#define ALLY_DEVICE_ATTR_RW(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0644, _name##_show, _name##_store) + +#define ALLY_DEVICE_ATTR_RO(_name, _sysfs_name) \ + struct device_attribute dev_attr_##_name = \ + __ATTR(_sysfs_name, 0444, _name##_show, NULL) + +/* button specific macros */ +#define ALLY_BTN_SHOW(_fname, _btn_name, _secondary) \ + static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct btn_data *btn; \ + const char* name; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ + name = btn_to_name(_secondary ? btn->macro : btn->button); \ + return sysfs_emit(buf, "%s\n", name); \ + } + +#define ALLY_BTN_STORE(_fname, _btn_name, _secondary) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct btn_data *btn; \ + u64 code; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ + code = name_to_btn(buf); \ + if (_secondary) \ + btn->macro = code; \ + else \ + btn->button = code; \ + return count; \ + } + +#define ALLY_TURBO_SHOW(_fname, _btn_name) \ + static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct btn_data *btn; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ + return sysfs_emit(buf, "%d\n", btn->turbo); \ + } + +#define ALLY_TURBO_STORE(_fname, _btn_name) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct btn_data *btn; \ + bool turbo; \ + int ret; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + btn = &ally_cfg->key_mapping[ally_cfg->mode - 1]._btn_name; \ + ret = kstrtobool(buf, &turbo); \ + if (ret) \ + return ret; \ + btn->turbo = turbo; \ + return count; \ + } + +#define ALLY_DEADZONE_SHOW(_fname, _axis_name) \ + static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct deadzone *dz; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + dz = &ally_cfg->_axis_name; \ + return sysfs_emit(buf, "%d %d\n", dz->inner, dz->outer); \ + } + +#define ALLY_DEADZONE_STORE(_fname, _axis_name) \ + static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + struct hid_device *hdev = to_hid_device(dev); \ + u32 inner, outer; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + if (sscanf(buf, "%d %d", &inner, &outer) != 2) \ + return -EINVAL; \ + if (inner > 64 || outer > 64 || inner > outer) \ + return -EINVAL; \ + ally_cfg->_axis_name.inner = inner; \ + ally_cfg->_axis_name.outer = outer; \ + _gamepad_apply_deadzones(hdev, ally_cfg); \ + return count; \ + } + +#define ALLY_DEADZONES(_fname, _mname) \ + ALLY_DEADZONE_SHOW(_fname##_deadzone, _mname); \ + ALLY_DEADZONE_STORE(_fname##_deadzone, _mname); \ + ALLY_DEVICE_ATTR_RW(_fname##_deadzone, deadzone) + +/* response curve macros */ +#define ALLY_RESP_CURVE_SHOW(_fname, _mname) \ +static ssize_t _fname##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + return sysfs_emit(buf, "%d\n", ally_cfg->ls_rc._mname); \ + } + +#define ALLY_RESP_CURVE_STORE(_fname, _mname) \ +static ssize_t _fname##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct ally_gamepad_cfg *ally_cfg = drvdata.gamepad_cfg; \ + int ret, val; \ + if (!drvdata.gamepad_cfg) \ + return -ENODEV; \ + ret = kstrtoint(buf, 0, &val); \ + if (ret) \ + return ret; \ + if (val < 0 || val > 100) \ + return -EINVAL; \ + ally_cfg->ls_rc._mname = val; \ + return count; \ + } + +/* _point_n must start at 1 */ +#define ALLY_JS_RC_POINT(_fname, _mname, _num) \ + ALLY_RESP_CURVE_SHOW(_fname##_##_mname##_##_num, _mname##_pct_##_num); \ + ALLY_RESP_CURVE_STORE(_fname##_##_mname##_##_num, _mname##_pct_##_num); \ + ALLY_DEVICE_ATTR_RW(_fname##_##_mname##_##_num, curve_##_mname##_pct_##_num) + +#define ALLY_BTN_ATTRS_GROUP(_name, _fname) \ + static struct attribute *_fname##_attrs[] = { \ + &dev_attr_##_fname.attr, \ + &dev_attr_##_fname##_macro.attr, \ + }; \ + static const struct attribute_group _fname##_attr_group = { \ + .name = __stringify(_name), \ + .attrs = _fname##_attrs, \ + } + +#define _ALLY_BTN_REMAP(_fname, _btn_name) \ + ALLY_BTN_SHOW(btn_mapping_##_fname##_remap, _btn_name, false); \ + ALLY_BTN_STORE(btn_mapping_##_fname##_remap, _btn_name, false); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_remap, remap); + +#define _ALLY_BTN_MACRO(_fname, _btn_name) \ + ALLY_BTN_SHOW(btn_mapping_##_fname##_macro, _btn_name, true); \ + ALLY_BTN_STORE(btn_mapping_##_fname##_macro, _btn_name, true); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_macro, macro_remap); + +#define ALLY_BTN_MAPPING(_fname, _btn_name) \ + _ALLY_BTN_REMAP(_fname, _btn_name) \ + _ALLY_BTN_MACRO(_fname, _btn_name) \ + static struct attribute *_fname##_attrs[] = { \ + &dev_attr_btn_mapping_##_fname##_remap.attr, \ + &dev_attr_btn_mapping_##_fname##_macro.attr, \ + NULL, \ + }; \ + static const struct attribute_group btn_mapping_##_fname##_attr_group = { \ + .name = __stringify(btn_##_fname), \ + .attrs = _fname##_attrs, \ + } + +#define ALLY_TURBO_BTN_MAPPING(_fname, _btn_name) \ + _ALLY_BTN_REMAP(_fname, _btn_name) \ + _ALLY_BTN_MACRO(_fname, _btn_name) \ + ALLY_TURBO_SHOW(btn_mapping_##_fname##_turbo, _btn_name); \ + ALLY_TURBO_STORE(btn_mapping_##_fname##_turbo, _btn_name); \ + ALLY_DEVICE_ATTR_RW(btn_mapping_##_fname##_turbo, turbo); \ + static struct attribute *_fname##_turbo_attrs[] = { \ + &dev_attr_btn_mapping_##_fname##_remap.attr, \ + &dev_attr_btn_mapping_##_fname##_macro.attr, \ + &dev_attr_btn_mapping_##_fname##_turbo.attr, \ + NULL, \ + }; \ + static const struct attribute_group btn_mapping_##_fname##_attr_group = { \ + .name = __stringify(btn_##_fname), \ + .attrs = _fname##_turbo_attrs, \ + } diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index befa990b3210..f7aa5edfe9ec 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -22,6 +22,7 @@ #include #include +#include "linux/export.h" #include #include #include @@ -32,6 +33,7 @@ #include #include "hid-ids.h" +#include "hid-asus.h" MODULE_AUTHOR("Yusuke Fujimaki "); MODULE_AUTHOR("Brendan McGrath "); @@ -686,7 +688,7 @@ static int mcu_request_version(struct hid_device *hdev) return ret; } -static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) +void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) { int min_version, version; @@ -714,6 +716,7 @@ static void validate_mcu_fw_version(struct hid_device *hdev, int idProduct) set_ally_mcu_powersave(true); } } +EXPORT_SYMBOL_NS(validate_mcu_fw_version, "HID_ASUS"); static bool asus_has_report_id(struct hid_device *hdev, u16 report_id) { @@ -733,8 +736,6 @@ static bool asus_has_report_id(struct hid_device *hdev, u16 report_id) static int asus_kbd_register_leds(struct hid_device *hdev) { struct asus_drvdata *drvdata = hid_get_drvdata(hdev); - struct usb_interface *intf; - struct usb_device *udev; unsigned char kbd_func; int ret; @@ -753,12 +754,14 @@ static int asus_kbd_register_leds(struct hid_device *hdev) return ret; } +#if !IS_REACHABLE(CONFIG_HID_ASUS_ALLY) if ((drvdata->quirks & QUIRK_ROG_ALLY_XPAD) && hid_is_usb(hdev)) { - intf = to_usb_interface(hdev->dev.parent); - udev = interface_to_usbdev(intf); + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = interface_to_usbdev(intf); validate_mcu_fw_version(hdev, le16_to_cpu(udev->descriptor.idProduct)); } +#endif /* !IS_REACHABLE(CONFIG_HID_ASUS_ALLY) */ drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(struct asus_kbd_leds), @@ -1197,6 +1200,8 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) struct asus_drvdata *drvdata; struct hid_report *rep; bool is_vendor = false; + struct usb_host_endpoint *ep; + struct usb_interface *intf; int ret; drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); @@ -1209,6 +1214,18 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) drvdata->quirks = id->driver_data; + /* Ignore these endpoints as they are used by hid-asus-ally */ + #if IS_REACHABLE(CONFIG_HID_ASUS_ALLY) + if (drvdata->quirks & QUIRK_ROG_ALLY_XPAD) { + intf = to_usb_interface(hdev->dev.parent); + ep = intf->cur_altsetting->endpoint; + if (ep->desc.bEndpointAddress == ROG_ALLY_X_INTF_IN || + ep->desc.bEndpointAddress == ROG_ALLY_CFG_INTF_IN || + ep->desc.bEndpointAddress == ROG_ALLY_CFG_INTF_OUT) + return -ENODEV; + } + #endif /* IS_REACHABLE(CONFIG_HID_ASUS_ALLY) */ + /* * T90CHI's keyboard dock returns same ID values as T100CHI's dock. * Thus, identify T90CHI dock with product name string. diff --git a/drivers/hid/hid-asus.h b/drivers/hid/hid-asus.h new file mode 100644 index 000000000000..f67dd5a3a1bc --- /dev/null +++ b/drivers/hid/hid-asus.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __HID_ASUS_H +#define __HID_ASUS_H + +#include + +#define ROG_ALLY_CFG_INTF_IN 0x83 +#define ROG_ALLY_CFG_INTF_OUT 0x04 +#define ROG_ALLY_X_INTF_IN 0x87 + +void validate_mcu_fw_version(struct hid_device *hdev, int idProduct); + +#endif /* __HID_ASUS_H */ diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 561057573ca2..bb73bc6edb9e 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1081,7 +1081,12 @@ #define USB_DEVICE_ID_MOZA_R16_R21_2 0x0010 #define USB_VENDOR_ID_MSI 0x1770 +#define USB_VENDOR_ID_MSI_2 0x0db0 #define USB_DEVICE_ID_MSI_GT683R_LED_PANEL 0xff00 +#define USB_DEVICE_ID_MSI_CLAW_XINPUT 0x1901 +#define USB_DEVICE_ID_MSI_CLAW_DINPUT 0x1902 +#define USB_DEVICE_ID_MSI_CLAW_DESKTOP 0x1903 +#define USB_DEVICE_ID_MSI_CLAW_BIOS 0x1904 #define USB_VENDOR_ID_NATIONAL_SEMICONDUCTOR 0x0400 #define USB_DEVICE_ID_N_S_HARMONY 0xc359 @@ -1384,6 +1389,10 @@ #define USB_DEVICE_ID_STEAM_CONTROLLER 0x1102 #define USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS 0x1142 #define USB_DEVICE_ID_STEAM_DECK 0x1205 +#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX 0x1302 +#define USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE 0x1303 +#define USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS 0x1304 +#define USB_DEVICE_ID_STEAM_CONTROLLER_NEREID 0x1305 #define USB_VENDOR_ID_STEELSERIES 0x1038 #define USB_DEVICE_ID_STEELSERIES_SRWS1 0x1410 @@ -1609,6 +1618,10 @@ #define USB_VENDOR_ID_ZYTRONIC 0x14c8 #define USB_DEVICE_ID_ZYTRONIC_ZXY100 0x0005 +#define USB_VENDOR_ID_ZOTAC 0x1EE9 +#define USB_VENDOR_ID_ZOTAC_ALT 0x1E19 +#define USB_DEVICE_ID_ZOTAC_ZONE_GAME_CONTROLLER 0x1590 + #define USB_VENDOR_ID_PRIMAX 0x0461 #define USB_DEVICE_ID_PRIMAX_MOUSE_4D22 0x4d22 #define USB_DEVICE_ID_PRIMAX_MOUSE_4E2A 0x4e2a @@ -1650,4 +1663,7 @@ #define USB_VENDOR_ID_HUAWEI 0x12d1 #define USB_DEVICE_ID_HUAWEI_CD30KBD 0x109b +#define I2C_VENDOR_ID_FOCALTECH 0x2808 +#define I2C_DEVICE_ID_FOCALTECH_FTS3528 0x1015 + #endif diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c new file mode 100644 index 000000000000..0264a7a2bb24 --- /dev/null +++ b/drivers/hid/hid-msi.c @@ -0,0 +1,2063 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for MSI Claw Handheld PC gamepads. + * + * Provides configuration support for the MSI Claw series of handheld PC + * gamepads. Multiple iterations of the device firmware has led to some + * quirks for how certain attributes are handled. The original firmware + * did not support remapping of the M1 (right) and M2 (left) rear paddles. + * Additionally, the MCU RAM address for writing configuration data has + * changed twice. Checks are done during probe to enumerate these variances. + * + * Copyright (c) 2026 Zhouwang Huang + * Copyright (c) 2026 Denis Benato + * Copyright (c) 2026 Valve Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define CLAW_OUTPUT_REPORT_ID 0x0f +#define CLAW_INPUT_REPORT_ID 0x10 + +#define CLAW_PACKET_SIZE 64 + +#define CLAW_DINPUT_CFG_INTF_IN 0x82 +#define CLAW_XINPUT_CFG_INTF_IN 0x83 + +#define CLAW_KEYS_MAX 5 + +#define CLAW_RGB_ZONES 9 +#define CLAW_RGB_MAX_FRAMES 8 +#define CLAW_RGB_FRAME_OFFSET 0x24 + +enum claw_command_index { + CLAW_COMMAND_TYPE_NONE = 0x00, + CLAW_COMMAND_TYPE_READ_PROFILE = 0x04, + CLAW_COMMAND_TYPE_READ_PROFILE_ACK = 0x05, + CLAW_COMMAND_TYPE_ACK = 0x06, + CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA = 0x21, + CLAW_COMMAND_TYPE_SYNC_TO_ROM = 0x22, + CLAW_COMMAND_TYPE_SWITCH_MODE = 0x24, + CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE = 0x26, + CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK = 0x27, + CLAW_COMMAND_TYPE_RESET_DEVICE = 0x28, +}; + +enum claw_gamepad_mode_index { + CLAW_GAMEPAD_MODE_XINPUT = 0x01, + CLAW_GAMEPAD_MODE_DINPUT = 0x02, + CLAW_GAMEPAD_MODE_DESKTOP = 0x04, +}; + +static const char * const claw_gamepad_mode_text[] = { + [CLAW_GAMEPAD_MODE_XINPUT] = "xinput", + [CLAW_GAMEPAD_MODE_DINPUT] = "dinput", + [CLAW_GAMEPAD_MODE_DESKTOP] = "desktop", +}; + +enum claw_profile_ack_pending { + CLAW_NO_PENDING, + CLAW_M1_PENDING, + CLAW_M2_PENDING, + CLAW_RGB_PENDING, + CLAW_RUMBLE_LEFT_PENDING, + CLAW_RUMBLE_RIGHT_PENDING, +}; + +enum claw_key_index { + CLAW_KEY_M1, + CLAW_KEY_M2, +}; + +enum claw_mkeys_function_index { + CLAW_MKEY_FUNCTION_MACRO, + CLAW_MKEY_FUNCTION_DISABLED, + CLAW_MKEY_FUNCTION_COMBO, +}; + +enum claw_mode_field { + CLAW_FIELD_GAMEPAD_MODE, + CLAW_FIELD_MKEYS_FUNCTION, +}; + +static const char * const claw_mkeys_function_text[] = { + [CLAW_MKEY_FUNCTION_MACRO] = "macro", + [CLAW_MKEY_FUNCTION_DISABLED] = "disabled", + [CLAW_MKEY_FUNCTION_COMBO] = "combination", +}; + +static const struct { + u8 code; + const char *name; +} claw_button_mapping_key_map[] = { + /* Gamepad buttons */ + { 0x01, "ABS_HAT0Y_UP" }, + { 0x02, "ABS_HAT0Y_DOWN" }, + { 0x03, "ABS_HAT0X_LEFT" }, + { 0x04, "ABS_HAT0X_RIGHT" }, + { 0x05, "BTN_TL" }, + { 0x06, "BTN_TR" }, + { 0x07, "BTN_THUMBL" }, + { 0x08, "BTN_THUMBR" }, + { 0x09, "BTN_SOUTH" }, + { 0x0a, "BTN_EAST" }, + { 0x0b, "BTN_NORTH" }, + { 0x0c, "BTN_WEST" }, + { 0x0d, "BTN_MODE" }, + { 0x0e, "BTN_SELECT" }, + { 0x0f, "BTN_START" }, + { 0x13, "BTN_TL2"}, + { 0x14, "BTN_TR2"}, + { 0x15, "ABS_Y_UP"}, + { 0x16, "ABS_Y_DOWN"}, + { 0x17, "ABS_X_LEFT"}, + { 0x18, "ABS_X_RIGHT"}, + { 0x19, "ABS_RY_UP"}, + { 0x1a, "ABS_RY_DOWN"}, + { 0x1b, "ABS_RX_LEFT"}, + { 0x1c, "ABS_RX_RIGHT"}, + /* Keyboard keys */ + { 0x32, "KEY_ESC" }, + { 0x33, "KEY_F1" }, + { 0x34, "KEY_F2" }, + { 0x35, "KEY_F3" }, + { 0x36, "KEY_F4" }, + { 0x37, "KEY_F5" }, + { 0x38, "KEY_F6" }, + { 0x39, "KEY_F7" }, + { 0x3a, "KEY_F8" }, + { 0x3b, "KEY_F9" }, + { 0x3c, "KEY_F10" }, + { 0x3d, "KEY_F11" }, + { 0x3e, "KEY_F12" }, + { 0x3f, "KEY_GRAVE" }, + { 0x40, "KEY_1" }, + { 0x41, "KEY_2" }, + { 0x42, "KEY_3" }, + { 0x43, "KEY_4" }, + { 0x44, "KEY_5" }, + { 0x45, "KEY_6" }, + { 0x46, "KEY_7" }, + { 0x47, "KEY_8" }, + { 0x48, "KEY_9" }, + { 0x49, "KEY_0" }, + { 0x4a, "KEY_MINUS" }, + { 0x4b, "KEY_EQUAL" }, + { 0x4c, "KEY_BACKSPACE" }, + { 0x4d, "KEY_TAB" }, + { 0x4e, "KEY_Q" }, + { 0x4f, "KEY_W" }, + { 0x50, "KEY_E" }, + { 0x51, "KEY_R" }, + { 0x52, "KEY_T" }, + { 0x53, "KEY_Y" }, + { 0x54, "KEY_U" }, + { 0x55, "KEY_I" }, + { 0x56, "KEY_O" }, + { 0x57, "KEY_P" }, + { 0x58, "KEY_LEFTBRACE" }, + { 0x59, "KEY_RIGHTBRACE" }, + { 0x5a, "KEY_BACKSLASH" }, + { 0x5b, "KEY_CAPSLOCK" }, + { 0x5c, "KEY_A" }, + { 0x5d, "KEY_S" }, + { 0x5e, "KEY_D" }, + { 0x5f, "KEY_F" }, + { 0x60, "KEY_G" }, + { 0x61, "KEY_H" }, + { 0x62, "KEY_J" }, + { 0x63, "KEY_K" }, + { 0x64, "KEY_L" }, + { 0x65, "KEY_SEMICOLON" }, + { 0x66, "KEY_APOSTROPHE" }, + { 0x67, "KEY_ENTER" }, + { 0x68, "KEY_LEFTSHIFT" }, + { 0x69, "KEY_Z" }, + { 0x6a, "KEY_X" }, + { 0x6b, "KEY_C" }, + { 0x6c, "KEY_V" }, + { 0x6d, "KEY_B" }, + { 0x6e, "KEY_N" }, + { 0x6f, "KEY_M" }, + { 0x70, "KEY_COMMA" }, + { 0x71, "KEY_DOT" }, + { 0x72, "KEY_SLASH" }, + { 0x73, "KEY_RIGHTSHIFT" }, + { 0x74, "KEY_LEFTCTRL" }, + { 0x75, "KEY_LEFTMETA" }, + { 0x76, "KEY_LEFTALT" }, + { 0x77, "KEY_SPACE" }, + { 0x78, "KEY_RIGHTALT" }, + { 0x79, "KEY_RIGHTCTRL" }, + { 0x7a, "KEY_INSERT" }, + { 0x7b, "KEY_HOME" }, + { 0x7c, "KEY_PAGEUP" }, + { 0x7d, "KEY_DELETE" }, + { 0x7e, "KEY_END" }, + { 0x7f, "KEY_PAGEDOWN" }, + { 0x8a, "KEY_KPENTER" }, + { 0x8b, "KEY_KP0" }, + { 0x8c, "KEY_KP1" }, + { 0x8d, "KEY_KP2" }, + { 0x8e, "KEY_KP3" }, + { 0x8f, "KEY_KP4" }, + { 0x90, "KEY_KP5" }, + { 0x91, "KEY_KP6" }, + { 0x92, "KEY_KP7" }, + { 0x93, "KEY_KP8" }, + { 0x94, "KEY_KP9" }, + { 0x95, "MD_PLAY" }, + { 0x96, "MD_STOP" }, + { 0x97, "MD_NEXT" }, + { 0x98, "MD_PREV" }, + { 0x99, "MD_VOL_UP" }, + { 0x9a, "MD_VOL_DOWN" }, + { 0x9b, "MD_VOL_MUTE" }, + { 0x9c, "KEY_F23" }, + /* Mouse events */ + { 0xc8, "BTN_LEFT" }, + { 0xc9, "BTN_MIDDLE" }, + { 0xca, "BTN_RIGHT" }, + { 0xcb, "BTN_SIDE" }, + { 0xcc, "BTN_EXTRA" }, + { 0xcd, "REL_WHEEL_UP" }, + { 0xce, "REL_WHEEL_DOWN" }, + { 0xff, "DISABLED" }, +}; + +enum claw_rgb_effect_index { + CLAW_RGB_EFFECT_MONOCOLOR, + CLAW_RGB_EFFECT_BREATHE, + CLAW_RGB_EFFECT_CHROMA, + CLAW_RGB_EFFECT_RAINBOW, + CLAW_RGB_EFFECT_FROSTFIRE, +}; + +static const char * const claw_rgb_effect_text[] = { + [CLAW_RGB_EFFECT_MONOCOLOR] = "monocolor", + [CLAW_RGB_EFFECT_BREATHE] = "breathe", + [CLAW_RGB_EFFECT_CHROMA] = "chroma", + [CLAW_RGB_EFFECT_RAINBOW] = "rainbow", + [CLAW_RGB_EFFECT_FROSTFIRE] = "frostfire", +}; + +static const u16 button_mapping_addr_old[] = { + 0x007a, /* M1 */ + 0x011f, /* M2 */ +}; + +static const u16 button_mapping_addr_new[] = { + 0x00bb, /* M1 */ + 0x0164, /* M2 */ +}; + +static const u16 rgb_addr_old = 0x01fa; +static const u16 rgb_addr_new = 0x024a; + +static const u16 rumble_addr[] = { + 0x0022, /* left */ + 0x0023, /* right */ +}; + +struct claw_command_report { + u8 report_id; + u8 padding[2]; + u8 header_tail; + u8 cmd; + u8 data[59]; +} __packed; + +struct claw_profile_report { + u8 profile; + __be16 read_addr; +} __packed; + +struct claw_mkey_report { + struct claw_profile_report; + u8 padding_0; + u8 padding_1; + u8 padding_2; + u8 codes[5]; +} __packed; + +struct rgb_zone { + u8 red; + u8 green; + u8 blue; +}; + +struct rgb_frame { + struct rgb_zone zone[CLAW_RGB_ZONES]; +}; + +struct claw_rgb_report { + struct claw_profile_report; + u8 frame_bytes; + u8 padding; + u8 frame_count; + u8 state; /* Always 0x09 */ + u8 speed; + u8 brightness; + struct rgb_frame zone_data; +} __packed; + +struct claw_rumble_report { + struct claw_profile_report; + u8 padding; + u8 intensity; +} __packed; + +struct claw_drvdata { + /* MCU General Variables */ + enum claw_profile_ack_pending profile_pending; + struct completion orphan_ack_complete; + struct completion send_cmd_complete; + struct delayed_work cfg_resume; + struct delayed_work cfg_setup; + spinlock_t registration_lock; /* Lock for registration read/write */ + struct mutex profile_mutex; /* mutex for profile_pending calls */ + spinlock_t profile_lock; /* Lock for profile_pending read/write */ + struct hid_device *hdev; + bool orphan_ack_pending; + struct mutex cfg_mutex; /* mutex for synchronous data */ + struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */ + spinlock_t cmd_lock; /* Lock for cmd data read/write */ + u8 waiting_cmd; + int cmd_status; + u16 bcd_device; + u8 ep; + + /* Gamepad Variables */ + enum claw_mkeys_function_index mkeys_function; + enum claw_gamepad_mode_index gamepad_mode; + u8 m1_codes[CLAW_KEYS_MAX]; + u8 m2_codes[CLAW_KEYS_MAX]; + u8 rumble_intensity_right; + u8 rumble_intensity_left; + const u16 *bmap_addr; + spinlock_t rumble_lock; /* lock for rumble_intensity read/write */ + spinlock_t mode_lock; /* Lock for mode data read/write */ + bool rumble_support; + bool gp_registered; + bool bmap_support; + + /* RGB Variables */ + struct rgb_frame rgb_frames[CLAW_RGB_MAX_FRAMES]; + enum claw_rgb_effect_index rgb_effect; + struct led_classdev_mc led_mc; + struct delayed_work rgb_queue; + spinlock_t frame_lock; /* lock for rgb_frames read/write */ + bool rgb_registered; + u8 rgb_frame_count; + bool rgb_enabled; + u8 rgb_speed; + u16 rgb_addr; +}; + +static int get_endpoint_address(struct hid_device *hdev) +{ + struct usb_host_endpoint *ep; + struct usb_interface *intf; + + intf = to_usb_interface(hdev->dev.parent); + ep = intf->cur_altsetting->endpoint; + if (ep) + return ep->desc.bEndpointAddress; + + return -ENODEV; +} + +static int claw_gamepad_mode_event(struct claw_drvdata *drvdata, + struct claw_command_report *cmd_rep) +{ + if (cmd_rep->data[0] >= ARRAY_SIZE(claw_gamepad_mode_text) || + !claw_gamepad_mode_text[cmd_rep->data[0]] || + cmd_rep->data[1] >= ARRAY_SIZE(claw_mkeys_function_text)) + return -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { + drvdata->gamepad_mode = cmd_rep->data[0]; + drvdata->mkeys_function = cmd_rep->data[1]; + } + + return 0; +} + +static int claw_profile_event(struct claw_drvdata *drvdata, struct claw_command_report *cmd_rep) +{ + enum claw_profile_ack_pending profile; + struct claw_rumble_report *rumble; + struct claw_mkey_report *mkeys; + struct claw_rgb_report *frame; + u16 rgb_addr, read_addr; + u8 *codes, key, f_idx; + u16 frame_calc; + int i; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + profile = drvdata->profile_pending; + + switch (profile) { + case CLAW_M1_PENDING: + case CLAW_M2_PENDING: + key = (profile == CLAW_M1_PENDING) ? CLAW_KEY_M1 : CLAW_KEY_M2; + mkeys = (struct claw_mkey_report *)cmd_rep->data; + if (be16_to_cpu(mkeys->read_addr) != drvdata->bmap_addr[key]) + return -EAGAIN; + codes = (profile == CLAW_M1_PENDING) ? drvdata->m1_codes : drvdata->m2_codes; + for (i = 0; i < CLAW_KEYS_MAX; i++) + codes[i] = (mkeys->codes[i]); + break; + case CLAW_RGB_PENDING: + frame = (struct claw_rgb_report *)cmd_rep->data; + rgb_addr = drvdata->rgb_addr; + read_addr = be16_to_cpu(frame->read_addr); + + if (read_addr < drvdata->rgb_addr) + return -EAGAIN; + + frame_calc = (read_addr - rgb_addr) / CLAW_RGB_FRAME_OFFSET; + if (frame_calc >= CLAW_RGB_MAX_FRAMES) { + dev_err(&drvdata->hdev->dev, "Got unsupported frame index: %x\n", + frame_calc); + return -EAGAIN; + } + f_idx = frame_calc; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + memcpy(&drvdata->rgb_frames[f_idx], &frame->zone_data, + sizeof(struct rgb_frame)); + + /* Only use frame 0 for remaining variable assignment */ + if (f_idx != 0) + break; + + drvdata->rgb_speed = frame->speed; + drvdata->led_mc.led_cdev.brightness = frame->brightness; + drvdata->led_mc.subled_info[0].intensity = frame->zone_data.zone[0].red; + drvdata->led_mc.subled_info[1].intensity = frame->zone_data.zone[0].green; + drvdata->led_mc.subled_info[2].intensity = frame->zone_data.zone[0].blue; + } + + break; + case CLAW_RUMBLE_LEFT_PENDING: + rumble = (struct claw_rumble_report *)cmd_rep->data; + if (be16_to_cpu(rumble->read_addr) != rumble_addr[0]) + return -EAGAIN; + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + drvdata->rumble_intensity_left = rumble->intensity; + break; + case CLAW_RUMBLE_RIGHT_PENDING: + rumble = (struct claw_rumble_report *)cmd_rep->data; + if (be16_to_cpu(rumble->read_addr) != rumble_addr[1]) + return -EAGAIN; + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + drvdata->rumble_intensity_right = rumble->intensity; + break; + default: + dev_dbg(&drvdata->hdev->dev, + "Got profile event without changes pending from command: %x\n", + cmd_rep->cmd); + return -EINVAL; + } + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_NO_PENDING; + + return 0; +} + +static int claw_raw_event(struct claw_drvdata *drvdata, struct hid_report *report, + u8 *data, int size) +{ + struct claw_command_report *cmd_rep; + int ret = 0; + + if (size != CLAW_PACKET_SIZE) + return 0; + + cmd_rep = (struct claw_command_report *)data; + + if (cmd_rep->report_id != CLAW_INPUT_REPORT_ID || cmd_rep->header_tail != 0x3c) + return 0; + + dev_dbg(&drvdata->hdev->dev, "Rx data as raw input report: [%*ph]\n", + CLAW_PACKET_SIZE, data); + + guard(spinlock_irqsave)(&drvdata->cmd_lock); + switch (cmd_rep->cmd) { + case CLAW_COMMAND_TYPE_GAMEPAD_MODE_ACK: + ret = claw_gamepad_mode_event(drvdata, cmd_rep); + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) { + drvdata->cmd_status = ret; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_READ_PROFILE_ACK: + ret = claw_profile_event(drvdata, cmd_rep); + /* Stale address received, ignore and keep waiting */ + if (ret == -EAGAIN) + return 0; + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE) { + drvdata->cmd_status = ret; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_ACK: + if (drvdata->orphan_ack_pending) { + drvdata->orphan_ack_pending = false; + complete(&drvdata->orphan_ack_complete); + break; + } + + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_NONE) { + dev_warn(&drvdata->hdev->dev, "Got unexpected ACK from MCU, ignoring\n"); + break; + } + + drvdata->cmd_status = 0; + complete(&drvdata->send_cmd_complete); + + dev_dbg(&drvdata->hdev->dev, "Waiting CMD: %x\n", drvdata->waiting_cmd); + + break; + default: + dev_dbg(&drvdata->hdev->dev, "Unknown command: %x\n", cmd_rep->cmd); + return 0; + } + + return ret; +} + +static int msi_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata || (drvdata->ep != CLAW_XINPUT_CFG_INTF_IN && + drvdata->ep != CLAW_DINPUT_CFG_INTF_IN)) + return 0; + + return claw_raw_event(drvdata, report, data, size); +} + +/* Caller must hold drvdata->cfg_mutex. */ +static int __claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, + size_t len, unsigned int timeout) +{ + unsigned char *dmabuf __free(kfree) = NULL; + u8 header[] = { CLAW_OUTPUT_REPORT_ID, 0, 0, 0x3c, index }; + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + size_t header_size = ARRAY_SIZE(header); + bool orphaned; + int ret; + + lockdep_assert_held(&drvdata->cfg_mutex); + + /* If expecting an orphan ack, hold next event until MCU has time to clear it */ + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + orphaned = drvdata->orphan_ack_pending; + + if (orphaned) { + wait_for_completion_timeout(&drvdata->orphan_ack_complete, msecs_to_jiffies(25)); + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + drvdata->orphan_ack_pending = false; + } + + if (header_size + len > CLAW_PACKET_SIZE) + return -EINVAL; + + /* We can't use a devm_alloc reusable buffer without side effects during suspend */ + dmabuf = kzalloc(CLAW_PACKET_SIZE, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + + memcpy(dmabuf, header, header_size); + if (data && len) + memcpy(dmabuf + header_size, data, len); + + reinit_completion(&drvdata->send_cmd_complete); + + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { + if (timeout) { + drvdata->waiting_cmd = index; + drvdata->cmd_status = -ETIMEDOUT; + } else { + reinit_completion(&drvdata->orphan_ack_complete); + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + drvdata->orphan_ack_pending = true; + } + } + + dev_dbg(&hdev->dev, "Send data as raw output report: [%*ph]\n", + CLAW_PACKET_SIZE, dmabuf); + + ret = hid_hw_output_report(hdev, dmabuf, CLAW_PACKET_SIZE); + if (ret < 0) + goto err; + + ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO; + if (ret) + goto err; + + if (timeout) { + ret = wait_for_completion_interruptible_timeout(&drvdata->send_cmd_complete, + msecs_to_jiffies(timeout)); + + dev_dbg(&hdev->dev, "Remaining timeout: %u\n", ret); + ret = ret > 0 ? drvdata->cmd_status : ret ?: -EBUSY; + if (ret) + goto err; + } + + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + + return ret; + +err: + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + drvdata->orphan_ack_pending = false; + } + return ret; +} + +static int claw_hw_output_report(struct hid_device *hdev, u8 index, u8 *data, + size_t len, unsigned int timeout) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + guard(mutex)(&drvdata->cfg_mutex); + return __claw_hw_output_report(hdev, index, data, len, timeout); +} + +static int claw_switch_mode(struct hid_device *hdev, enum claw_mode_field field, u8 val) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 data[2]; + + guard(mutex)(&drvdata->cfg_mutex); + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) { + switch (field) { + case CLAW_FIELD_GAMEPAD_MODE: + data[0] = val; + data[1] = drvdata->mkeys_function; + break; + case CLAW_FIELD_MKEYS_FUNCTION: + data[0] = drvdata->gamepad_mode; + data[1] = val; + break; + } + } + + return __claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, + ARRAY_SIZE(data), 0); +} + +static ssize_t gamepad_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int i, ret = -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { + if (claw_gamepad_mode_text[i] && sysfs_streq(buf, claw_gamepad_mode_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + ret = claw_switch_mode(hdev, CLAW_FIELD_GAMEPAD_MODE, ret); + if (ret) + return ret; + + return count; +} + +static ssize_t gamepad_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + i = drvdata->gamepad_mode; + + if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') + return sysfs_emit(buf, "unsupported\n"); + + return sysfs_emit(buf, "%s\n", claw_gamepad_mode_text[i]); +} +static DEVICE_ATTR_RW(gamepad_mode); + +static ssize_t gamepad_mode_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t count = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(claw_gamepad_mode_text); i++) { + if (!claw_gamepad_mode_text[i] || claw_gamepad_mode_text[i][0] == '\0') + continue; + count += sysfs_emit_at(buf, count, "%s ", claw_gamepad_mode_text[i]); + } + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(gamepad_mode_index); + +static ssize_t mkeys_function_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int i, ret = -EINVAL; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) { + if (claw_mkeys_function_text[i] && sysfs_streq(buf, claw_mkeys_function_text[i])) { + ret = i; + break; + } + } + if (ret < 0) + return ret; + + ret = claw_switch_mode(hdev, CLAW_FIELD_MKEYS_FUNCTION, ret); + if (ret) + return ret; + + return count; +} + +static ssize_t mkeys_function_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, NULL, 0, 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + i = drvdata->mkeys_function; + + if (i >= ARRAY_SIZE(claw_mkeys_function_text)) + return sysfs_emit(buf, "unsupported\n"); + + return sysfs_emit(buf, "%s\n", claw_mkeys_function_text[i]); +} +static DEVICE_ATTR_RW(mkeys_function); + +static ssize_t mkeys_function_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_mkeys_function_text); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_mkeys_function_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(mkeys_function_index); + +static ssize_t reset_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + bool val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + if (!val) + return -EINVAL; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_RESET_DEVICE, NULL, 0, 0); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(reset); + +static int mkey_mapping_name_to_code(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { + if (!strcmp(name, claw_button_mapping_key_map[i].name)) + return claw_button_mapping_key_map[i].code; + } + + return -EINVAL; +} + +static const char *mkey_mapping_code_to_name(u8 code) +{ + int i; + + if (code == 0xff) + return NULL; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) { + if (claw_button_mapping_key_map[i].code == code) + return claw_button_mapping_key_map[i].name; + } + + return NULL; +} + +static int claw_mkey_store(struct device *dev, const char *buf, u8 mkey) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[mkey])}, + 0x07, 0x04, 0x00, {0xff, 0xff, 0xff, 0xff, 0xff} }; + char **raw_keys __free(argv_free) = NULL; + int ret, key_count, i; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + raw_keys = argv_split(GFP_KERNEL, buf, &key_count); + if (!raw_keys) + return -ENOMEM; + + if (key_count > CLAW_KEYS_MAX) + return -EINVAL; + + if (key_count == 0) + goto set_buttons; + + for (i = 0; i < key_count; i++) { + ret = mkey_mapping_name_to_code(raw_keys[i]); + if (ret < 0) + return ret; + + report.codes[i] = ret; + } + +set_buttons: + scoped_guard(mutex, &drvdata->rom_mutex) { + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + } + + return ret; +} + +static int claw_mkey_show(struct device *dev, char *buf, enum claw_key_index m_key) +{ + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + struct claw_mkey_report report = { {0x01, cpu_to_be16(drvdata->bmap_addr[m_key])}, 0x07 }; + int i, ret, count = 0; + const char *name; + u8 *codes; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + codes = (m_key == CLAW_KEY_M1) ? drvdata->m1_codes : drvdata->m2_codes; + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = (m_key == CLAW_KEY_M1) ? CLAW_M1_PENDING + : CLAW_M2_PENDING; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + for (i = 0; i < CLAW_KEYS_MAX; i++) { + name = mkey_mapping_code_to_name(codes[i]); + if (name) + count += sysfs_emit_at(buf, count, "%s ", name); + } + + if (!count) + return sysfs_emit(buf, "(not set)\n"); + + buf[count - 1] = '\n'; + + return count; +} + +static ssize_t button_m1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = claw_mkey_store(dev, buf, CLAW_KEY_M1); + if (ret) + return ret; + + return count; +} + +static ssize_t button_m1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return claw_mkey_show(dev, buf, CLAW_KEY_M1); +} +static DEVICE_ATTR_RW(button_m1); + +static ssize_t button_m2_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = claw_mkey_store(dev, buf, CLAW_KEY_M2); + if (ret) + return ret; + + return count; +} + +static ssize_t button_m2_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return claw_mkey_show(dev, buf, CLAW_KEY_M2); +} +static DEVICE_ATTR_RW(button_m2); + +static ssize_t button_mapping_options_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_button_mapping_key_map); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_button_mapping_key_map[i].name); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(button_mapping_options); + +static ssize_t rumble_intensity_left_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 100) + return -EINVAL; + + report.intensity = val; + + guard(mutex)(&drvdata->rom_mutex); + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + if (ret) + return ret; + + return count; +} + +static ssize_t rumble_intensity_left_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[0])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + u8 val; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RUMBLE_LEFT_PENDING; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + val = drvdata->rumble_intensity_left; + + return sysfs_emit(buf, "%u\n", val); +} +static DEVICE_ATTR_RW(rumble_intensity_left); + +static ssize_t rumble_intensity_right_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u8 val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + ret = kstrtou8(buf, 10, &val); + if (ret) + return ret; + + if (val > 100) + return -EINVAL; + + report.intensity = val; + + guard(mutex)(&drvdata->rom_mutex); + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + if (ret) + return ret; + + return count; +} + +static ssize_t rumble_intensity_right_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct claw_rumble_report report = { {0x01, cpu_to_be16(rumble_addr[1])}, 0x01 }; + struct hid_device *hdev = to_hid_device(dev); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + int ret; + u8 val; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered)) + return -ENODEV; + } + + guard(mutex)(&drvdata->profile_mutex); + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RUMBLE_RIGHT_PENDING; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->rumble_lock) + val = drvdata->rumble_intensity_right; + + return sysfs_emit(buf, "%u\n", val); +} +static DEVICE_ATTR_RW(rumble_intensity_right); + +static ssize_t rumble_intensity_range_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "0-100\n"); +} +static DEVICE_ATTR_RO(rumble_intensity_range); + +static umode_t claw_gamepad_attr_is_visible(struct kobject *kobj, struct attribute *attr, + int n) +{ + struct hid_device *hdev = to_hid_device(kobj_to_dev(kobj)); + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) { + dev_warn(&hdev->dev, + "Failed to get drvdata from kobj. Gamepad attributes are not available.\n"); + return 0; + } + + /* Always show attrs available on all firmware */ + if (attr == &dev_attr_gamepad_mode.attr || + attr == &dev_attr_gamepad_mode_index.attr || + attr == &dev_attr_mkeys_function.attr || + attr == &dev_attr_mkeys_function_index.attr || + attr == &dev_attr_reset.attr) + return attr->mode; + + /* Hide rumble attrs if not supported */ + if (attr == &dev_attr_rumble_intensity_left.attr || + attr == &dev_attr_rumble_intensity_right.attr || + attr == &dev_attr_rumble_intensity_range.attr) + return drvdata->rumble_support ? attr->mode : 0; + + /* Hide button mapping attrs if it isn't supported */ + return drvdata->bmap_support ? attr->mode : 0; +} + +static struct attribute *claw_gamepad_attrs[] = { + &dev_attr_button_m1.attr, + &dev_attr_button_m2.attr, + &dev_attr_button_mapping_options.attr, + &dev_attr_gamepad_mode.attr, + &dev_attr_gamepad_mode_index.attr, + &dev_attr_mkeys_function.attr, + &dev_attr_mkeys_function_index.attr, + &dev_attr_reset.attr, + &dev_attr_rumble_intensity_left.attr, + &dev_attr_rumble_intensity_right.attr, + &dev_attr_rumble_intensity_range.attr, + NULL, +}; + +static const struct attribute_group claw_gamepad_attr_group = { + .attrs = claw_gamepad_attrs, + .is_visible = claw_gamepad_attr_is_visible, +}; + +/* Read RGB config from device */ +static int claw_read_rgb_config(struct hid_device *hdev) +{ + u8 data[4] = { 0x01, 0x00, 0x00, CLAW_RGB_FRAME_OFFSET }; + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + u16 read_addr = drvdata->rgb_addr; + size_t len = ARRAY_SIZE(data); + int ret, i; + + if (!drvdata->rgb_addr) + return -ENODEV; + + /* Loop through all 8 pages of RGB data */ + guard(mutex)(&drvdata->profile_mutex); + for (i = 0; i < CLAW_RGB_MAX_FRAMES; i++) { + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_RGB_PENDING; + data[1] = (read_addr >> 8) & 0xff; + data[2] = read_addr & 0x00ff; + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_READ_PROFILE, data, len, 25); + if (ret) + return ret; + + read_addr += CLAW_RGB_FRAME_OFFSET; + } + + return 0; +} + +/* Send RGB configuration to device */ +static int claw_write_rgb_state(struct claw_drvdata *drvdata) +{ + struct claw_rgb_report report = { {0x01, 0}, CLAW_RGB_FRAME_OFFSET, 0x00, + drvdata->rgb_frame_count, 0x09, drvdata->rgb_speed, + drvdata->led_mc.led_cdev.brightness }; + u16 write_addr = drvdata->rgb_addr; + int f, ret; + + if (!drvdata->rgb_addr) + return -ENODEV; + + if (!drvdata->rgb_frame_count) + return -EINVAL; + + guard(mutex)(&drvdata->rom_mutex); + /* Loop through (up to) 8 pages of RGB data */ + for (f = 0; f < drvdata->rgb_frame_count; f++) { + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) + report.zone_data = drvdata->rgb_frames[f]; + + /* Set the MCU address to write the frame data to */ + report.read_addr = cpu_to_be16(write_addr); + + /* Serialize the rgb_report and write it to MCU */ + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_WRITE_PROFILE_DATA, + (u8 *)&report, sizeof(report), 25); + if (ret) + return ret; + + /* Increment the write addr by the offset for the next frame */ + write_addr += CLAW_RGB_FRAME_OFFSET; + } + + /* MCU will not send ACK until the USB transaction completes. ACK is sent + * immediately after and will hit the stale state machine, before the next + * command re-arms the state machine. Timeout 0 ensures no deadlock waiting + * for ACK that ill never come. + */ + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_SYNC_TO_ROM, NULL, 0, 0); + + return ret; +} + +/* Fill all zones with the same color */ +static void claw_frame_fill_solid(struct rgb_frame *frame, struct rgb_zone zone) +{ + int z; + + for (z = 0; z < CLAW_RGB_ZONES; z++) + frame->zone[z] = zone; +} + +/* Apply solid effect (1 frame, no color) */ +static int claw_apply_disabled(struct claw_drvdata *drvdata) +{ + struct rgb_zone off = { 0x00, 0x00, 0x00}; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 1; + claw_frame_fill_solid(&drvdata->rgb_frames[0], off); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply solid effect (1 frame, all zones same color) */ +static int claw_apply_monocolor(struct claw_drvdata *drvdata) +{ + struct mc_subled *subleds = drvdata->led_mc.subled_info; + struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, + subleds[2].intensity }; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 1; + claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply breathe effect (2 frames: color -> off) */ +static int claw_apply_breathe(struct claw_drvdata *drvdata) +{ + struct mc_subled *subleds = drvdata->led_mc.subled_info; + struct rgb_zone zone = { subleds[0].intensity, subleds[1].intensity, + subleds[2].intensity }; + static const struct rgb_zone off = { 0, 0, 0 }; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = 2; + claw_frame_fill_solid(&drvdata->rgb_frames[0], zone); + claw_frame_fill_solid(&drvdata->rgb_frames[1], off); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply chroma effect (6 frames: rainbow cycle, all zones sync) */ +static int claw_apply_chroma(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* red */ + {255, 255, 0}, /* yellow */ + { 0, 255, 0}, /* green */ + { 0, 255, 255}, /* cyan */ + { 0, 0, 255}, /* blue */ + {255, 0, 255}, /* magenta */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) + claw_frame_fill_solid(&drvdata->rgb_frames[f], colors[f]); + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply rainbow effect (4 frames: rotating colors around joysticks) */ +static int claw_apply_rainbow(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* red */ + { 0, 255, 0}, /* green */ + { 0, 255, 255}, /* cyan */ + { 0, 0, 255}, /* blue */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f, z; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) { + for (z = 0; z < 4; z++) { + drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; + drvdata->rgb_frames[f].zone[z + 4] = colors[(z + f) % 4]; + } + drvdata->rgb_frames[f].zone[8] = colors[f]; + } + } + + return claw_write_rgb_state(drvdata); +} + +/* + * Apply frostfire effect (4 frames: fire vs ice rotating) + * Right joystick: fire red -> dark -> ice blue -> dark (clockwise) + * Left joystick: ice blue -> dark -> fire red -> dark (counter-clockwise) + * ABXY: fire red -> dark -> ice blue -> dark + */ +static int claw_apply_frostfire(struct claw_drvdata *drvdata) +{ + static const struct rgb_zone colors[] = { + {255, 0, 0}, /* fire red */ + { 0, 0, 0}, /* dark */ + { 0, 0, 255}, /* ice blue */ + { 0, 0, 0}, /* dark */ + }; + u8 frame_count = ARRAY_SIZE(colors); + int f, z; + + scoped_guard(spinlock_irqsave, &drvdata->frame_lock) { + drvdata->rgb_frame_count = frame_count; + + for (f = 0; f < frame_count; f++) { + for (z = 0; z < 4; z++) { + drvdata->rgb_frames[f].zone[z] = colors[(z + f) % 4]; + drvdata->rgb_frames[f].zone[z + 4] = colors[(z - f + 6) % 4]; + } + drvdata->rgb_frames[f].zone[8] = colors[f]; + } + } + + return claw_write_rgb_state(drvdata); +} + +/* Apply current state to device */ +static int claw_apply_rgb_state(struct claw_drvdata *drvdata) +{ + if (!drvdata->rgb_enabled) + return claw_apply_disabled(drvdata); + + switch (drvdata->rgb_effect) { + case CLAW_RGB_EFFECT_MONOCOLOR: + return claw_apply_monocolor(drvdata); + case CLAW_RGB_EFFECT_BREATHE: + return claw_apply_breathe(drvdata); + case CLAW_RGB_EFFECT_CHROMA: + return claw_apply_chroma(drvdata); + case CLAW_RGB_EFFECT_RAINBOW: + return claw_apply_rainbow(drvdata); + case CLAW_RGB_EFFECT_FROSTFIRE: + return claw_apply_frostfire(drvdata); + default: + dev_err(&drvdata->hdev->dev, "No supported rgb_effect selected\n"); + return -EINVAL; + } +} + +static void claw_rgb_queue_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, rgb_queue); + int ret; + + if (!drvdata) + return; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return; + } + + ret = claw_apply_rgb_state(drvdata); + if (ret) + dev_err(&drvdata->hdev->dev, "Failed to apply RGB state: %d\n", ret); +} + +static ssize_t effect_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = sysfs_match_string(claw_rgb_effect_text, buf); + if (ret < 0) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_effect = ret; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + if (drvdata->rgb_effect >= ARRAY_SIZE(claw_rgb_effect_text)) + return -EINVAL; + + return sysfs_emit(buf, "%s\n", claw_rgb_effect_text[drvdata->rgb_effect]); +} + +static DEVICE_ATTR_RW(effect); + +static ssize_t effect_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int i, count = 0; + + for (i = 0; i < ARRAY_SIZE(claw_rgb_effect_text); i++) + count += sysfs_emit_at(buf, count, "%s ", claw_rgb_effect_text[i]); + + if (count) + buf[count - 1] = '\n'; + + return count; +} +static DEVICE_ATTR_RO(effect_index); + +static ssize_t enabled_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + bool val; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = kstrtobool(buf, &val); + if (ret) + return ret; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_enabled = val; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t enabled_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + return sysfs_emit(buf, "%s\n", drvdata->rgb_enabled ? "true" : "false"); +} +static DEVICE_ATTR_RW(enabled); + +static ssize_t enabled_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "true false\n"); +} +static DEVICE_ATTR_RO(enabled_index); + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + unsigned int val, speed; + int ret; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + ret = kstrtouint(buf, 10, &val); + if (ret) + return ret; + + if (val > 20) + return -EINVAL; + + /* 0 is fastest, invert value for intuitive userspace speed */ + speed = 20 - val; + + scoped_guard(spinlock_irqsave, &drvdata->profile_lock) + drvdata->rgb_speed = speed; + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); + + return count; +} + +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + u8 speed = 20 - drvdata->rgb_speed; + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return -ENODEV; + } + + return sysfs_emit(buf, "%u\n", speed); +} +static DEVICE_ATTR_RW(speed); + +static ssize_t speed_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0-20\n"); +} +static DEVICE_ATTR_RO(speed_range); + +static void claw_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness _brightness) +{ + struct led_classdev_mc *led_mc = container_of(led_cdev, struct led_classdev_mc, led_cdev); + struct claw_drvdata *drvdata = container_of(led_mc, struct claw_drvdata, led_mc); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->rgb_registered)) + return; + } + + mod_delayed_work(system_wq, &drvdata->rgb_queue, msecs_to_jiffies(50)); +} + +static struct attribute *claw_rgb_attrs[] = { + &dev_attr_effect.attr, + &dev_attr_effect_index.attr, + &dev_attr_enabled.attr, + &dev_attr_enabled_index.attr, + &dev_attr_speed.attr, + &dev_attr_speed_range.attr, + NULL, +}; + +static const struct attribute_group claw_rgb_attr_group = { + .attrs = claw_rgb_attrs, +}; + +static struct mc_subled claw_rgb_subled_info[] = { + { + .color_index = LED_COLOR_ID_RED, + .channel = 0x1, + }, + { + .color_index = LED_COLOR_ID_GREEN, + .channel = 0x2, + }, + { + .color_index = LED_COLOR_ID_BLUE, + .channel = 0x3, + }, +}; + +static void cfg_setup_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_setup); + bool gamepad_ready = false, rgb_ready = false, gp_registered, rgb_registered; + int ret; + + ret = claw_hw_output_report(drvdata->hdev, CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE, + NULL, 0, 25); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to read gamepad mode: %d\n", ret); + goto prep_rgb; + } + gamepad_ready = true; + +prep_rgb: + ret = claw_read_rgb_config(drvdata->hdev); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to read RGB config: %d\n", ret); + goto try_gamepad; + } + rgb_ready = true; + + /* Add sysfs attributes after we get the device state */ +try_gamepad: + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) + /* Pairs with smp_store_release from below */ + gp_registered = smp_load_acquire(&drvdata->gp_registered); + + if (!gp_registered && gamepad_ready) { + ret = device_add_group(&drvdata->hdev->dev, &claw_gamepad_attr_group); + if (ret) { + dev_err(&drvdata->hdev->dev, + "Failed to create gamepad attrs: %d\n", ret); + goto try_rgb; + } + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->gp_registered, true); + gp_registered = true; + } + } + +try_rgb: + /* Add and enable RGB interface once we have the device state */ + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) + /* Pairs with smp_store_release from below */ + rgb_registered = smp_load_acquire(&drvdata->rgb_registered); + + if (!rgb_registered && rgb_ready) { + ret = led_classdev_multicolor_register(&drvdata->hdev->dev, + &drvdata->led_mc); + if (ret) { + dev_err(&drvdata->hdev->dev, "Failed to create led device: %d\n", ret); + goto update_kobjects; + } + + ret = device_add_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); + if (ret) { + dev_err(&drvdata->hdev->dev, "Failed to create RGB attrs: %d\n", ret); + led_classdev_multicolor_unregister(&drvdata->led_mc); + goto update_kobjects; + } + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->rgb_registered, true); + rgb_registered = true; + } + } + +update_kobjects: + if (gp_registered) + kobject_uevent(&drvdata->hdev->dev.kobj, KOBJ_CHANGE); + if (rgb_registered) + kobject_uevent(&drvdata->led_mc.led_cdev.dev->kobj, KOBJ_CHANGE); +} + +static void cfg_resume_fn(struct work_struct *work) +{ + struct delayed_work *dwork = container_of(work, struct delayed_work, work); + struct claw_drvdata *drvdata = container_of(dwork, struct claw_drvdata, cfg_resume); + + guard(spinlock_irqsave)(&drvdata->registration_lock); + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + if (!smp_load_acquire(&drvdata->gp_registered) || + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + !smp_load_acquire(&drvdata->rgb_registered)) + schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500)); +} + +static void claw_features_supported(struct claw_drvdata *drvdata) +{ + u8 major = (drvdata->bcd_device >> 8) & 0xff; + u8 minor = drvdata->bcd_device & 0xff; + + if (major == 0x01) { + drvdata->bmap_support = true; + if (minor >= 0x66) { + drvdata->bmap_addr = button_mapping_addr_new; + drvdata->rumble_support = true; + drvdata->rgb_addr = rgb_addr_new; + } else { + drvdata->bmap_addr = button_mapping_addr_old; + drvdata->rgb_addr = rgb_addr_old; + } + return; + } + + if ((major == 0x02 && minor >= 0x17) || major >= 0x03) { + drvdata->bmap_support = true; + drvdata->bmap_addr = button_mapping_addr_new; + drvdata->rumble_support = true; + drvdata->rgb_addr = rgb_addr_new; + return; + } + + drvdata->rgb_addr = rgb_addr_old; +} + +static int claw_probe(struct hid_device *hdev, u8 ep) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = interface_to_usbdev(intf); + struct claw_drvdata *drvdata; + int ret; + + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + + drvdata->gamepad_mode = CLAW_GAMEPAD_MODE_XINPUT; + drvdata->rgb_enabled = true; + drvdata->hdev = hdev; + drvdata->ep = ep; + + /* Determine feature level from firmware version */ + drvdata->bcd_device = le16_to_cpu(udev->descriptor.bcdDevice); + claw_features_supported(drvdata); + + if (!drvdata->bmap_support) + dev_dbg(&hdev->dev, "M-Key mapping is not supported. Update firmware to enable.\n"); + + /* Device is hardwired and name is guaranteed to be unique */ + drvdata->led_mc.led_cdev.name = "go:rgb:joystick_rings"; + drvdata->led_mc.led_cdev.brightness = 0x50; + drvdata->led_mc.led_cdev.max_brightness = 0x64; + drvdata->led_mc.led_cdev.color = LED_COLOR_ID_RGB; + drvdata->led_mc.led_cdev.brightness_set = claw_led_brightness_set; + drvdata->led_mc.num_colors = 3; + drvdata->led_mc.subled_info = devm_kmemdup(&hdev->dev, claw_rgb_subled_info, + sizeof(claw_rgb_subled_info), GFP_KERNEL); + if (!drvdata->led_mc.subled_info) + return -ENOMEM; + + mutex_init(&drvdata->cfg_mutex); + mutex_init(&drvdata->profile_mutex); + mutex_init(&drvdata->rom_mutex); + spin_lock_init(&drvdata->registration_lock); + spin_lock_init(&drvdata->cmd_lock); + spin_lock_init(&drvdata->mode_lock); + spin_lock_init(&drvdata->profile_lock); + spin_lock_init(&drvdata->frame_lock); + spin_lock_init(&drvdata->rumble_lock); + init_completion(&drvdata->orphan_ack_complete); + init_completion(&drvdata->send_cmd_complete); + INIT_DELAYED_WORK(&drvdata->cfg_resume, &cfg_resume_fn); + INIT_DELAYED_WORK(&drvdata->cfg_setup, &cfg_setup_fn); + INIT_DELAYED_WORK(&drvdata->rgb_queue, &claw_rgb_queue_fn); + + /* For control interface: open the HID transport for sending commands. */ + ret = hid_hw_open(hdev); + if (ret) + return ret; + + hid_set_drvdata(hdev, drvdata); + schedule_delayed_work(&drvdata->cfg_setup, msecs_to_jiffies(500)); + + return 0; +} + +static int msi_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int ret; + u8 ep; + + if (!hid_is_usb(hdev)) { + ret = -ENODEV; + goto err_probe; + } + + ret = hid_parse(hdev); + if (ret) + goto err_probe; + + /* Set quirk to create separate input devices per HID application */ + hdev->quirks |= HID_QUIRK_INPUT_PER_APP | HID_QUIRK_MULTI_INPUT; + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (ret) + goto err_probe; + + /* For non-control interfaces (keyboard/mouse), allow userspace to grab the devices. */ + ret = get_endpoint_address(hdev); + if (ret < 0) + goto err_stop_hw; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) { + ret = claw_probe(hdev, ep); + if (ret) + goto err_stop_hw; + } + + return 0; + +err_stop_hw: + hid_hw_stop(hdev); +err_probe: + return dev_err_probe(&hdev->dev, ret, "Failed to init device\n"); +} + +static void claw_remove(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + bool gp_registered; + bool rgb_registered; + + if (!drvdata) + return; + + cancel_delayed_work_sync(&drvdata->cfg_resume); + cancel_delayed_work_sync(&drvdata->cfg_setup); + + scoped_guard(spinlock_irqsave, &drvdata->registration_lock) { + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + gp_registered = smp_load_acquire(&drvdata->gp_registered); + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->gp_registered, false); + /* Pairs with smp_store_release from cfg_setup_fn in system_wq context */ + rgb_registered = smp_load_acquire(&drvdata->rgb_registered); + /* Pairs with smp_load_acquire in attribute show/store functions */ + smp_store_release(&drvdata->rgb_registered, false); + } + + if (gp_registered) + device_remove_group(&hdev->dev, &claw_gamepad_attr_group); + + if (rgb_registered) { + device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); + led_classdev_multicolor_unregister(&drvdata->led_mc); + } + cancel_delayed_work_sync(&drvdata->rgb_queue); + + hid_hw_close(hdev); +} + +static void msi_remove(struct hid_device *hdev) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + goto hw_stop; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + claw_remove(hdev); + +hw_stop: + hid_hw_stop(hdev); +} + +static int claw_resume(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) + return -ENODEV; + + /* MCU can take up to 500ms to be ready after resume */ + schedule_delayed_work(&drvdata->cfg_resume, msecs_to_jiffies(500)); + return 0; +} + +static int msi_resume(struct hid_device *hdev) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + return 0; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + return claw_resume(hdev); + + return 0; +} + +static int claw_suspend(struct hid_device *hdev) +{ + struct claw_drvdata *drvdata = hid_get_drvdata(hdev); + + if (!drvdata) + return -ENODEV; + + cancel_delayed_work_sync(&drvdata->cfg_resume); + cancel_delayed_work_sync(&drvdata->cfg_setup); + cancel_delayed_work_sync(&drvdata->rgb_queue); + + return 0; +} + +static int msi_suspend(struct hid_device *hdev, pm_message_t msg) +{ + int ret; + u8 ep; + + /* Safe assumption. SET_INTERFACE ioctl can't be used while driver is bound */ + ret = get_endpoint_address(hdev); + if (ret <= 0) + return 0; + + ep = ret; + if (ep == CLAW_XINPUT_CFG_INTF_IN || ep == CLAW_DINPUT_CFG_INTF_IN) + return claw_suspend(hdev); + + return 0; +} + +static const struct hid_device_id msi_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_XINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DINPUT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_DESKTOP) }, + { HID_USB_DEVICE(USB_VENDOR_ID_MSI_2, USB_DEVICE_ID_MSI_CLAW_BIOS) }, + { } +}; +MODULE_DEVICE_TABLE(hid, msi_devices); + +static struct hid_driver msi_driver = { + .name = "hid-msi", + .id_table = msi_devices, + .raw_event = msi_raw_event, + .probe = msi_probe, + .remove = msi_remove, + .resume = pm_ptr(msi_resume), + .suspend = pm_ptr(msi_suspend), +}; +module_hid_driver(msi_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Denis Benato "); +MODULE_AUTHOR("Zhouwang Huang "); +MODULE_AUTHOR("Derek J. Clark "); +MODULE_DESCRIPTION("HID driver for MSI Claw Handheld PC gamepads"); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index edb37b4c867e..41516d2a7f03 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -1715,6 +1715,28 @@ static void mt_yogabook9_fixup(struct hid_device *hdev, __u8 *rdesc, } } +static void mt_report_replace(struct device *dev, __u8 *rdesc, size_t rsize, + const u8 *confirm, const u8 *replace, + size_t pattern_len, const size_t *offsets, + unsigned int num_offsets) +{ + size_t offset; + int i; + + for (i = 0; i < num_offsets; i++) { + offset = offsets[i]; + if (offset + pattern_len >= rsize) { + dev_err(dev, "Failed to fixup hid report: invalid length\n"); + break; + } + if (memcmp(rdesc + offset, confirm, pattern_len)) { + dev_err(dev, "Failed to fixup hid report: failed to validate: %d\n", i); + break; + } + memcpy(rdesc + offset, replace, pattern_len); + } +} + static const __u8 *mt_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *size) { @@ -1748,6 +1770,23 @@ got: %x\n", hdev->product == USB_DEVICE_ID_LENOVO_YOGABOOK9I) mt_yogabook9_fixup(hdev, rdesc, size); + if (hdev->vendor == I2C_VENDOR_ID_FOCALTECH && + hdev->product == I2C_DEVICE_ID_FOCALTECH_FTS3528) { + static u8 x_replace[] = {0x46, 0x0b, 0x0a}; /* 2571 *10^-2 cm */ + static u8 x_with[] = {0x46, 0x40, 0x06}; /* 1600 * 10^-2 cm */ + static size_t x_offsets[] = {0x37, 0x92, 0xed, 0x148}; + static u8 y_replace[] = {0x46, 0xaa, 0x05}; /* 1450 *10^-2 cm */ + static u8 y_with[] = {0x46, 0xe8, 0x03}; /* 1000 * 10^-2 cm */ + static size_t y_offsets[] = {0x42, 0x9d, 0xf8, 0x153}; + + mt_report_replace(&hdev->dev, rdesc, *size, x_replace, x_with, + ARRAY_SIZE(x_replace), x_offsets, + ARRAY_SIZE(x_offsets)); + mt_report_replace(&hdev->dev, rdesc, *size, y_replace, y_with, + ARRAY_SIZE(y_replace), y_offsets, + ARRAY_SIZE(y_offsets)); + } + return rdesc; } diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c index 197126d6e081..21d825d0952d 100644 --- a/drivers/hid/hid-steam.c +++ b/drivers/hid/hid-steam.c @@ -43,11 +43,14 @@ #include #include #include +#include +#include #include "hid-ids.h" MODULE_DESCRIPTION("HID driver for Valve Steam Controller"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Rodrigo Rivas Costa "); +MODULE_AUTHOR("Vicki Pfau "); static bool lizard_mode = true; @@ -56,6 +59,8 @@ static LIST_HEAD(steam_devices); #define STEAM_QUIRK_WIRELESS BIT(0) #define STEAM_QUIRK_DECK BIT(1) +#define STEAM_QUIRK_IBEX BIT(2) +#define STEAM_QUIRK_BLE BIT(3) /* Touch pads are 40 mm in diameter and 65535 units */ #define STEAM_PAD_RESOLUTION 1638 @@ -68,13 +73,14 @@ static LIST_HEAD(steam_devices); /* Joystick runs are about 5 mm and 32768 units */ #define STEAM_DECK_JOYSTICK_RESOLUTION 6553 /* Accelerometer has 16 bit resolution and a range of +/- 2g */ -#define STEAM_DECK_ACCEL_RES_PER_G 16384 -#define STEAM_DECK_ACCEL_RANGE 32768 +#define STEAM_ACCEL_RES_PER_G 16384 +#define STEAM_ACCEL_RANGE 32768 +#define STEAM_ACCEL_FUZZ 128 #define STEAM_DECK_ACCEL_FUZZ 32 /* Gyroscope has 16 bit resolution and a range of +/- 2000 dps */ -#define STEAM_DECK_GYRO_RES_PER_DPS 16 -#define STEAM_DECK_GYRO_RANGE 32768 -#define STEAM_DECK_GYRO_FUZZ 1 +#define STEAM_GYRO_RES_PER_DPS 16 +#define STEAM_GYRO_RANGE 32768 +#define STEAM_GYRO_FUZZ 0 #define STEAM_PAD_FUZZ 256 @@ -149,7 +155,7 @@ enum { SETTING_USB_DEBUG_MODE, SETTING_LEFT_TRACKPAD_MODE, SETTING_RIGHT_TRACKPAD_MODE, - SETTING_MOUSE_POINTER_ENABLED, + SETTING_LIZARD_MODE, /* 10 */ SETTING_DPAD_DEADZONE, @@ -243,14 +249,39 @@ enum { /* Input report identifiers */ enum { - ID_CONTROLLER_STATE = 1, - ID_CONTROLLER_DEBUG = 2, - ID_CONTROLLER_WIRELESS = 3, - ID_CONTROLLER_STATUS = 4, - ID_CONTROLLER_DEBUG2 = 5, - ID_CONTROLLER_SECONDARY_STATE = 6, - ID_CONTROLLER_BLE_STATE = 7, - ID_CONTROLLER_DECK_STATE = 9 + ID_CONTROLLER_STATE = 1, + ID_CONTROLLER_DEBUG = 2, + ID_CONTROLLER_WIRELESS = 3, + ID_CONTROLLER_STATUS = 4, + ID_CONTROLLER_DEBUG2 = 5, + ID_CONTROLLER_SECONDARY_STATE = 6, + ID_CONTROLLER_BLE_STATE = 7, + ID_CONTROLLER_DECK_STATE = 9, +}; + +/* Read-only attributes */ +enum { + ATTRIB_UNIQUE_ID, // deprecated + ATTRIB_PRODUCT_ID, + ATTRIB_PRODUCT_REVISON, // deprecated + ATTRIB_CAPABILITIES = ATTRIB_PRODUCT_REVISON, // intentional aliasing + ATTRIB_FIRMWARE_VERSION, // deprecated + ATTRIB_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_FIRMWARE_BUILD_TIME, + ATTRIB_RADIO_DEVICE_ID0, + ATTRIB_RADIO_DEVICE_ID1, + ATTRIB_DONGLE_FIRMWARE_BUILD_TIME, + ATTRIB_HW_ID, // AKA BOARD_REVISION, + ATTRIB_BOOTLOADER_BUILD_TIME, + ATTRIB_CONNECTION_INTERVAL_IN_US, + ATTRIB_SECONDARY_FIRMWARE_BUILD_TIME, + ATTRIB_SECONDARY_BOOTLOADER_BUILD_TIME, + ATTRIB_SECONDARY_HW_ID, // AKA BOARD_REVISION, + ATTRIB_STREAMING, + ATTRIB_TRACKPAD_ID, + ATTRIB_SECONDARY_TRACKPAD_ID, + + ATTRIB_COUNT }; /* String attribute identifiers */ @@ -259,14 +290,14 @@ enum { ATTRIB_STR_UNIT_SERIAL, }; -/* Values for GYRO_MODE (bitmask) */ +/* Values for IMU_MODE (bitmask) */ enum { - SETTING_GYRO_MODE_OFF = 0, - SETTING_GYRO_MODE_STEERING = BIT(0), - SETTING_GYRO_MODE_TILT = BIT(1), - SETTING_GYRO_MODE_SEND_ORIENTATION = BIT(2), - SETTING_GYRO_MODE_SEND_RAW_ACCEL = BIT(3), - SETTING_GYRO_MODE_SEND_RAW_GYRO = BIT(4), + SETTING_IMU_MODE_OFF = 0, + SETTING_IMU_MODE_STEERING = BIT(0), + SETTING_IMU_MODE_TILT = BIT(1), + SETTING_IMU_MODE_SEND_ORIENTATION = BIT(2), + SETTING_IMU_MODE_SEND_RAW_ACCEL = BIT(3), + SETTING_IMU_MODE_SEND_RAW_GYRO = BIT(4), }; /* Trackpad modes */ @@ -282,6 +313,83 @@ enum { TRACKPAD_GESTURE_KEYBOARD, }; +/* Report identifiers (Triton only) */ +enum { + /* Feature */ + REPORT_ID_FEATURES_CONTROLLER = 1, + REPORT_ID_FEATURES_DONGLE = 2, + + /* Input */ + REPORT_ID_INPUT = 0x42, + REPORT_ID_BATTERY = 0x43, + REPORT_ID_INPUT2 = 0x45, + REPORT_ID_WIRELESS_EVENT = 0x79, + + /* Output */ + REPORT_ID_HAPTIC_RUMBLE = 0x80, + REPORT_ID_HAPTIC_PULSE = 0x81, + REPORT_ID_HAPTIC_COMMAND = 0x82, + REPORT_ID_HAPTIC_LFO_TONE = 0x83, + REPORT_ID_HAPTIC_LOG_SWEEP = 0x84, + REPORT_ID_HAPTIC_SCRIPT = 0x85, +}; + +/* Triton charge state */ +enum { + CHARGE_STATE_DISCHARGING = 1, + CHARGE_STATE_CHARGING = 2, + CHARGE_STATE_CHARGING_DONE = 4, +}; + +/* Triton wireless events */ +enum { + WIRELESS_EVENT_DISCONNECT = 1, + WIRELESS_EVENT_CONNECT = 2, + WIRELESS_EVENT_PAIR = 3, +}; + +struct steam_controller_attribute { + unsigned char tag; + __le32 value; +} __packed; + +struct steam_triton_battery_status { + u8 charge_state; + u8 battery_level; + __le16 battery_voltage; + __le16 system_voltage; + __le16 input_voltage; + __le16 battery_current; + __le16 input_current; + __le16 temperature; +}; + +struct steam_triton_haptic_rumble { + u8 type; + __le16 intensity; + struct { + __le16 speed; + u8 gain; + } __packed left, right; +} __packed; +static_assert(sizeof(struct steam_triton_haptic_rumble) == 9); + +struct steam_triton_haptic_pulse { + u8 side; + __le16 on_us; + __le16 off_us; + __le16 repeat_count; +} __packed; +static_assert(sizeof(struct steam_triton_haptic_pulse) == 7); + +struct steam_triton_output_report { + u8 id; + union { + struct steam_triton_haptic_rumble rumble; + struct steam_triton_haptic_pulse pulse; + }; +} __packed; + /* Pad identifiers for the deck */ #define STEAM_PAD_LEFT 0 #define STEAM_PAD_RIGHT 1 @@ -305,31 +413,38 @@ struct steam_device { struct power_supply_desc battery_desc; struct power_supply __rcu *battery; u8 battery_charge; - u16 voltage; + u8 battery_status; + u16 battery_temp; + u16 battery_voltage; + u16 battery_current; struct delayed_work mode_switch; bool did_mode_switch; bool gamepad_mode; struct work_struct rumble_work; + struct delayed_work coalesce_rumble_work; u16 rumble_left; u16 rumble_right; unsigned int sensor_timestamp_us; - struct work_struct unregister_work; + unsigned int sensor_update_rate_us; }; -static int steam_recv_report(struct steam_device *steam, - u8 *data, int size) +static int steam_recv_report_id(struct steam_device *steam, + u8 *data, int size, u8 report_id) { struct hid_report *r; u8 *buf; + unsigned int retries = 50; int ret; + u32 len; - r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0]; + r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[report_id]; if (!r) { - hid_err(steam->hdev, "No HID_FEATURE_REPORT submitted - nothing to read\n"); + hid_err(steam->hdev, "No HID_FEATURE_REPORT present for ID %u\n", report_id); return -EINVAL; } - if (hid_report_len(r) < 64) + len = hid_report_len(r); + if (len < 64) return -EINVAL; buf = hid_alloc_report_buf(r, GFP_KERNEL); @@ -337,53 +452,105 @@ static int steam_recv_report(struct steam_device *steam, return -ENOMEM; /* - * The report ID is always 0, so strip the first byte from the output. + * The report ID is consistent, so strip the first byte from the output. * hid_report_len() is not counting the report ID, so +1 to the length * or else we get a EOVERFLOW. We are safe from a buffer overflow * because hid_alloc_report_buf() allocates +7 bytes. */ - ret = hid_hw_raw_request(steam->hdev, 0x00, - buf, hid_report_len(r) + 1, - HID_FEATURE_REPORT, HID_REQ_GET_REPORT); - if (ret > 0) - memcpy(data, buf + 1, min(size, ret - 1)); + if (!(steam->quirks & STEAM_QUIRK_IBEX)) + len += 1; + + /* + * Sometimes the wireless controller fails with EPIPE + * when sending a feature report. + * Doing a HID_REQ_GET_REPORT and waiting for a while + * seems to fix that. + */ + do { + ret = hid_hw_raw_request(steam->hdev, report_id, + buf, len, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret != -EPIPE) + break; + msleep(20); + } while (--retries); + if (ret > 0) { + ret = min(size, ret - 1); + memcpy(data, buf + 1, ret); + } + kfree(buf); + /* + * Don't log if the failure is -ENODEV, as this + * can happen normally on disconnect. + */ + if (ret < 0 && ret != -ENODEV) + hid_err(steam->hdev, "%s: error %d\n", __func__, ret); + else if (ret > 0) + hid_dbg(steam->hdev, "Received report %*ph\n", ret, data); + + if (ret >= 2 && data[1] > ret + 2) { + hid_err(steam->hdev, "%s: expected %u bytes, read %i\n", + __func__, data[1] + 2, ret); + return -EPROTO; + } return ret; } -static int steam_send_report(struct steam_device *steam, - u8 *cmd, int size) +static int steam_recv_report(struct steam_device *steam, + u8 *data, int size) +{ + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_CONTROLLER; + else + report_id = 0; + + return steam_recv_report_id(steam, data, size, report_id); +} + +static int steam_send_report_id(struct steam_device *steam, + u8 *cmd, int size, u8 report_id) { struct hid_report *r; u8 *buf; unsigned int retries = 50; int ret; + u32 len; - r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[0]; + r = steam->hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[report_id]; if (!r) { - hid_err(steam->hdev, "No HID_FEATURE_REPORT submitted - nothing to read\n"); + hid_err(steam->hdev, "No HID_FEATURE_REPORT present for ID %u\n", report_id); return -EINVAL; } - if (hid_report_len(r) < 64) + len = hid_report_len(r); + if (len < 64) return -EINVAL; buf = hid_alloc_report_buf(r, GFP_KERNEL); if (!buf) return -ENOMEM; - /* The report ID is always 0 */ + /* The report ID is always consistent */ + buf[0] = report_id; memcpy(buf + 1, cmd, size); + hid_dbg(steam->hdev, "Sending report %*ph\n", size, cmd); + + if (!(steam->quirks & STEAM_QUIRK_IBEX)) + len += 1; + /* * Sometimes the wireless controller fails with EPIPE * when sending a feature report. - * Doing a HID_REQ_GET_REPORT and waiting for a while + * Doing a HID_REQ_SET_REPORT and waiting for a while * seems to fix that. */ do { - ret = hid_hw_raw_request(steam->hdev, 0, - buf, max(size, 64) + 1, + ret = hid_hw_raw_request(steam->hdev, report_id, + buf, max(size + 1, len), HID_FEATURE_REPORT, HID_REQ_SET_REPORT); if (ret != -EPIPE) break; @@ -391,12 +558,29 @@ static int steam_send_report(struct steam_device *steam, } while (--retries); kfree(buf); - if (ret < 0) + /* + * Don't log if the failure is -ENODEV, as this + * can happen normally on disconnect. + */ + if (ret < 0 && ret != -ENODEV) hid_err(steam->hdev, "%s: error %d (%*ph)\n", __func__, ret, size, cmd); return ret; } +static int steam_send_report(struct steam_device *steam, + u8 *cmd, int size) +{ + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_CONTROLLER; + else + report_id = 0; + + return steam_send_report_id(steam, cmd, size, report_id); +} + static inline int steam_send_report_byte(struct steam_device *steam, u8 cmd) { return steam_send_report(steam, &cmd, 1); @@ -409,7 +593,6 @@ static int steam_write_settings(struct steam_device *steam, u8 reg; u16 val; u8 cmd[64] = {ID_SET_SETTINGS_VALUES, 0x00}; - int ret; va_list args; va_start(args, steam); @@ -425,16 +608,44 @@ static int steam_write_settings(struct steam_device *steam, } va_end(args); - ret = steam_send_report(steam, cmd, 2 + cmd[1]); - if (ret < 0) - return ret; + return steam_send_report(steam, cmd, 2 + cmd[1]); +} - /* - * Sometimes a lingering report for this command can - * get read back instead of the last set report if - * this isn't explicitly queried - */ - return steam_recv_report(steam, cmd, 2 + cmd[1]); +static int steam_exchange_report(struct steam_device *steam, u8 *cmd, int csize, + u8 *reply, int rsize) +{ + unsigned int retries = 5; + int ret; + + guard(mutex)(&steam->report_mutex); + do { + ret = steam_send_report(steam, cmd, csize); + if (ret < 0) + return ret; + ret = steam_recv_report(steam, reply, rsize); + /* + * Sometimes this can fail on the first few tries on the Steam + * Controller (2015). It appears to be a firmware bug, and Steam + * itself just retries, so we should also retry a few times to + * see if we get it. + */ + if (ret == -EPROTO) + continue; + if (ret < 0) { + hid_err(steam->hdev, "%s: error reading reply (%*ph)\n", + __func__, csize, cmd); + return ret; + } + if (reply[0] == cmd[0] && reply[1] >= 1) + break; + if (retries > 0) + continue; + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + rsize, reply); + return -EPROTO; + } while (retries--); + + return ret; } static int steam_get_serial(struct steam_device *steam) @@ -447,37 +658,76 @@ static int steam_get_serial(struct steam_device *steam) u8 cmd[] = {ID_GET_STRING_ATTRIBUTE, sizeof(steam->serial_no), ATTRIB_STR_UNIT_SERIAL}; u8 reply[3 + STEAM_SERIAL_LEN + 1]; - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, cmd, sizeof(cmd)); - if (ret < 0) - goto out; - ret = steam_recv_report(steam, reply, sizeof(reply)); + ret = steam_exchange_report(steam, cmd, sizeof(cmd), reply, sizeof(reply)); if (ret < 0) - goto out; - if (reply[0] != ID_GET_STRING_ATTRIBUTE || reply[1] < 1 || - reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) { - ret = -EIO; - goto out; + return ret; + if (reply[1] > sizeof(steam->serial_no) || reply[2] != ATTRIB_STR_UNIT_SERIAL) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; } reply[3 + STEAM_SERIAL_LEN] = 0; strscpy(steam->serial_no, reply + 3, reply[1]); -out: - mutex_unlock(&steam->report_mutex); return ret; } -/* - * This command requests the wireless adaptor to post an event - * with the connection status. Useful if this driver is loaded when - * the controller is already connected. - */ -static inline int steam_request_conn_status(struct steam_device *steam) +static int steam_get_attributes(struct steam_device *steam) { - int ret; - mutex_lock(&steam->report_mutex); - ret = steam_send_report_byte(steam, ID_DONGLE_GET_WIRELESS_STATE); - mutex_unlock(&steam->report_mutex); - return ret; + int ret = 0; + u8 cmd[] = {ID_GET_ATTRIBUTES_VALUES, 0}; + u8 reply[64] = {}; + u8 size; + int i; + struct steam_controller_attribute *attr; + + ret = steam_exchange_report(steam, cmd, sizeof(cmd), reply, sizeof(reply)); + if (ret < 0) + return ret; + if (reply[1] < 2) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; + } + + size = min(reply[1], sizeof(reply) - 2); + for (i = 0; i + sizeof(*attr) <= size; i += sizeof(*attr)) { + attr = (struct steam_controller_attribute *)&reply[i]; + if (attr->tag == ATTRIB_CONNECTION_INTERVAL_IN_US) { + steam->sensor_update_rate_us = get_unaligned_le32(&attr->value); + hid_dbg(steam->hdev, "Sensor update rate: %uus\n", + steam->sensor_update_rate_us); + } + } + + return 0; +} + +static int steam_get_conn_status(struct steam_device *steam) +{ + int ret = 0; + u8 cmd[] = {ID_DONGLE_GET_WIRELESS_STATE}; + u8 reply[3] = {}; + u8 report_id; + + if (steam->quirks & STEAM_QUIRK_IBEX) + report_id = REPORT_ID_FEATURES_DONGLE; + else + report_id = 0; + + guard(mutex)(&steam->report_mutex); + ret = steam_send_report_id(steam, cmd, sizeof(cmd), report_id); + if (ret < 0) + return ret; + ret = steam_recv_report_id(steam, reply, sizeof(reply), report_id); + if (ret < 0) + return ret; + if (reply[0] != ID_DONGLE_GET_WIRELESS_STATE || reply[1] < 1) { + hid_err(steam->hdev, "%s: invalid reply (%*ph)\n", __func__, + (int)sizeof(reply), reply); + return -EIO; + } + + return reply[2]; } /* @@ -490,24 +740,42 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, u16 duration, u16 interval, u16 count, u8 gain) { int ret; - u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8}; /* Left and right are swapped on this report for legacy reasons */ if (pad < STEAM_PAD_BOTH) pad ^= 1; - report[2] = pad; - report[3] = duration & 0xFF; - report[4] = duration >> 8; - report[5] = interval & 0xFF; - report[6] = interval >> 8; - report[7] = count & 0xFF; - report[8] = count >> 8; - report[9] = gain; + if (steam->quirks & STEAM_QUIRK_IBEX) { + struct steam_triton_output_report *report = + kzalloc(sizeof(struct steam_triton_output_report), GFP_KERNEL); + + if (!report) + return -ENOMEM; + + report->id = REPORT_ID_HAPTIC_PULSE; + report->pulse.side = pad; + put_unaligned_le16(duration, &report->pulse.on_us); + put_unaligned_le16(interval, &report->pulse.off_us); + put_unaligned_le16(count, &report->pulse.repeat_count); + + ret = hid_hw_output_report(steam->hdev, (u8 *)report, 8); + + kfree(report); + } else { + u8 report[10] = {ID_TRIGGER_HAPTIC_PULSE, 8, pad}; + + report[3] = duration & 0xFF; + report[4] = duration >> 8; + report[5] = interval & 0xFF; + report[6] = interval >> 8; + report[7] = count & 0xFF; + report[8] = count >> 8; + report[9] = gain; + + guard(mutex)(&steam->report_mutex); + ret = steam_send_report(steam, report, 10); + } - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, report, sizeof(report)); - mutex_unlock(&steam->report_mutex); return ret; } @@ -516,20 +784,39 @@ static inline int steam_haptic_rumble(struct steam_device *steam, u8 left_gain, u8 right_gain) { int ret; - u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9}; - report[3] = intensity & 0xFF; - report[4] = intensity >> 8; - report[5] = left_speed & 0xFF; - report[6] = left_speed >> 8; - report[7] = right_speed & 0xFF; - report[8] = right_speed >> 8; - report[9] = left_gain; - report[10] = right_gain; + if (steam->quirks & STEAM_QUIRK_IBEX) { + struct steam_triton_output_report *report = + kzalloc(sizeof(struct steam_triton_output_report), GFP_KERNEL); - mutex_lock(&steam->report_mutex); - ret = steam_send_report(steam, report, sizeof(report)); - mutex_unlock(&steam->report_mutex); + if (!report) + return -ENOMEM; + + report->id = REPORT_ID_HAPTIC_RUMBLE; + put_unaligned_le16(intensity, &report->rumble.intensity); + put_unaligned_le16(left_speed, &report->rumble.left.speed); + report->rumble.left.gain = left_gain; + put_unaligned_le16(right_speed, &report->rumble.right.speed); + report->rumble.right.gain = right_gain; + + ret = hid_hw_output_report(steam->hdev, (u8 *)report, 10); + + kfree(report); + } else { + u8 report[11] = {ID_TRIGGER_RUMBLE_CMD, 9}; + + report[3] = intensity & 0xFF; + report[4] = intensity >> 8; + report[5] = left_speed & 0xFF; + report[6] = left_speed >> 8; + report[7] = right_speed & 0xFF; + report[8] = right_speed >> 8; + report[9] = left_gain; + report[10] = right_gain; + + guard(mutex)(&steam->report_mutex); + ret = steam_send_report(steam, report, sizeof(report)); + } return ret; } @@ -537,10 +824,26 @@ static void steam_haptic_rumble_cb(struct work_struct *work) { struct steam_device *steam = container_of(work, struct steam_device, rumble_work); + + guard(mutex)(&steam->report_mutex); steam_haptic_rumble(steam, 0, steam->rumble_left, steam->rumble_right, 2, 0); } +static void steam_coalesce_rumble_cb(struct work_struct *work) +{ + struct steam_device *steam = container_of(to_delayed_work(work), + struct steam_device, + coalesce_rumble_work); + + guard(mutex)(&steam->report_mutex); + steam_haptic_rumble(steam, 0, steam->rumble_left, + steam->rumble_right, 2, 0); + + if (steam->rumble_left || steam->rumble_right) + schedule_delayed_work(&steam->coalesce_rumble_work, HZ / 20); +} + #ifdef CONFIG_STEAM_FF static int steam_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect) @@ -550,6 +853,14 @@ static int steam_play_effect(struct input_dev *dev, void *data, steam->rumble_left = effect->u.rumble.strong_magnitude; steam->rumble_right = effect->u.rumble.weak_magnitude; + /* + * The interface gets somewhat overloaded when too many rumble + * packets are sent in a row, so Steam throttles it to 20 Hz + */ + if (delayed_work_pending(&steam->coalesce_rumble_work)) + return 0; + + schedule_delayed_work(&steam->coalesce_rumble_work, HZ / 20); return schedule_work(&steam->rumble_work); } #endif @@ -569,20 +880,18 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) /* disable esc, enter, cursor */ steam_send_report_byte(steam, ID_CLEAR_DIGITAL_MAPPINGS); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) steam_write_settings(steam, - SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ - SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ - SETTING_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */ - SETTING_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable haptic click */ - SETTING_STEAM_WATCHDOG_ENABLE, 0, /* disable watchdog that tests if Steam is active */ + /* disable lizard mode */ + SETTING_LIZARD_MODE, 0, + /* disable watchdog that tests if Steam is active */ + SETTING_STEAM_WATCHDOG_ENABLE, 0, 0); - } else { + else steam_write_settings(steam, SETTING_LEFT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ SETTING_RIGHT_TRACKPAD_MODE, TRACKPAD_NONE, /* disable mouse */ 0); - } } mutex_unlock(&steam->report_mutex); } @@ -598,7 +907,7 @@ static int steam_input_open(struct input_dev *dev) * Controller. On the Steam Deck, this is toggled manually by holding * the options button instead, handled by steam_mode_switch_cb. */ - if (!(steam->quirks & STEAM_QUIRK_DECK)) { + if (!(steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX))) { spin_lock_irqsave(&steam->lock, flags); set_lizard_mode = !steam->client_opened && lizard_mode; spin_unlock_irqrestore(&steam->lock, flags); @@ -615,7 +924,7 @@ static void steam_input_close(struct input_dev *dev) unsigned long flags; bool set_lizard_mode; - if (!(steam->quirks & STEAM_QUIRK_DECK)) { + if (!(steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX))) { spin_lock_irqsave(&steam->lock, flags); set_lizard_mode = !steam->client_opened && lizard_mode; spin_unlock_irqrestore(&steam->lock, flags); @@ -624,6 +933,42 @@ static void steam_input_close(struct input_dev *dev) } } +static int steam_sensor_open(struct input_dev *dev) +{ + struct steam_device *steam = input_get_drvdata(dev); + unsigned long flags; + bool client_opened; + + spin_lock_irqsave(&steam->lock, flags); + client_opened = steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + if (client_opened) + return 0; + + guard(mutex)(&steam->report_mutex); + steam_write_settings(steam, SETTING_IMU_MODE, + SETTING_IMU_MODE_SEND_RAW_ACCEL | SETTING_IMU_MODE_SEND_RAW_GYRO, + 0); + + return 0; +} + +static void steam_sensor_close(struct input_dev *dev) +{ + struct steam_device *steam = input_get_drvdata(dev); + unsigned long flags; + bool client_opened; + + spin_lock_irqsave(&steam->lock, flags); + client_opened = steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + if (client_opened) + return; + + guard(mutex)(&steam->report_mutex); + steam_write_settings(steam, SETTING_IMU_MODE, 0, 0); +} + static enum power_supply_property steam_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_SCOPE, @@ -637,25 +982,40 @@ static int steam_battery_get_property(struct power_supply *psy, { struct steam_device *steam = power_supply_get_drvdata(psy); unsigned long flags; - s16 volts; + u16 volts; + u16 curr; + u16 temp; u8 batt; + u8 status; int ret = 0; spin_lock_irqsave(&steam->lock, flags); - volts = steam->voltage; + volts = steam->battery_voltage; + curr = steam->battery_current; batt = steam->battery_charge; + temp = steam->battery_temp; + status = steam->battery_status; spin_unlock_irqrestore(&steam->lock, flags); switch (psp) { case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = status; + break; case POWER_SUPPLY_PROP_SCOPE: val->intval = POWER_SUPPLY_SCOPE_DEVICE; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: val->intval = volts * 1000; /* mV -> uV */ break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = curr * 1000; /* mA -> uA */ + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = temp / 100; /* thousandths °C -> tenths °C */ + break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = batt; break; @@ -666,6 +1026,16 @@ static int steam_battery_get_property(struct power_supply *psy, return ret; } +static enum power_supply_property steam_triton_battery_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_TEMP, +}; + static int steam_battery_register(struct steam_device *steam) { struct power_supply *battery; @@ -674,19 +1044,35 @@ static int steam_battery_register(struct steam_device *steam) int ret; steam->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; - steam->battery_desc.properties = steam_battery_props; - steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props); + if (steam->quirks & STEAM_QUIRK_IBEX) { + steam->battery_desc.properties = steam_triton_battery_props; + steam->battery_desc.num_properties = ARRAY_SIZE(steam_triton_battery_props); + /* + * Triton needs a shorter name as it has a temperature and the + * thermal zone name length limit is 20 characters. It's more + * ambiguous sounding, so let's only use it when needed. + */ + steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, + GFP_KERNEL, "steam-%s", + steam->serial_no); + } else { + steam->battery_desc.properties = steam_battery_props; + steam->battery_desc.num_properties = ARRAY_SIZE(steam_battery_props); + steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, + GFP_KERNEL, "steam-controller-%s-battery", + steam->serial_no); + } steam->battery_desc.get_property = steam_battery_get_property; - steam->battery_desc.name = devm_kasprintf(&steam->hdev->dev, - GFP_KERNEL, "steam-controller-%s-battery", - steam->serial_no); if (!steam->battery_desc.name) return -ENOMEM; /* avoid the warning of 0% battery while waiting for the first info */ spin_lock_irqsave(&steam->lock, flags); - steam->voltage = 3000; + steam->battery_voltage = 3000; steam->battery_charge = 100; + steam->battery_current = 0; + steam->battery_temp = 200; + steam->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; spin_unlock_irqrestore(&steam->lock, flags); battery = power_supply_register(&steam->hdev->dev, @@ -757,7 +1143,7 @@ static int steam_input_register(struct steam_device *steam) input_set_capability(input, EV_KEY, BTN_THUMB2); input_set_capability(input, EV_KEY, BTN_GRIPL); input_set_capability(input, EV_KEY, BTN_GRIPR); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_capability(input, EV_KEY, BTN_BASE); input_set_capability(input, EV_KEY, BTN_GRIPL2); input_set_capability(input, EV_KEY, BTN_GRIPR2); @@ -771,7 +1157,7 @@ static int steam_input_register(struct steam_device *steam) input_set_abs_params(input, ABS_HAT0Y, -32767, 32767, STEAM_PAD_FUZZ, 0); - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_abs_params(input, ABS_HAT2Y, 0, 32767, 0, 0); input_set_abs_params(input, ABS_HAT2X, 0, 32767, 0, 0); @@ -811,7 +1197,7 @@ static int steam_input_register(struct steam_device *steam) input_abs_set_res(input, ABS_HAT0Y, STEAM_PAD_RESOLUTION); #ifdef CONFIG_STEAM_FF - if (steam->quirks & STEAM_QUIRK_DECK) { + if (steam->quirks & (STEAM_QUIRK_DECK | STEAM_QUIRK_IBEX)) { input_set_capability(input, EV_FF, FF_RUMBLE); ret = input_ff_create_memless(input, NULL, steam_play_effect); if (ret) @@ -836,9 +1222,7 @@ static int steam_sensors_register(struct steam_device *steam) struct hid_device *hdev = steam->hdev; struct input_dev *sensors; int ret; - - if (!(steam->quirks & STEAM_QUIRK_DECK)) - return 0; + bool needs_open_close; rcu_read_lock(); sensors = rcu_dereference(steam->sensors); @@ -855,7 +1239,33 @@ static int steam_sensors_register(struct steam_device *steam) input_set_drvdata(sensors, steam); sensors->dev.parent = &hdev->dev; - sensors->name = "Steam Deck Motion Sensors"; + /* + * The open/close calls are needed in these specific cases: + * + * - Steam Controller (2015): Always + * - Steam Deck: Never + * - Steam Controller (2026): Only when wireless/BLE + */ + if (steam->quirks & STEAM_QUIRK_DECK) + needs_open_close = false; + else if (steam->quirks & (STEAM_QUIRK_WIRELESS | STEAM_QUIRK_BLE)) + /* Both wireless Steam Controller setups */ + needs_open_close = true; + else if (steam->quirks & STEAM_QUIRK_IBEX) + /* Wired Steam Controller (2026) */ + needs_open_close = false; + else + /* Wired Steam Controller (2015) */ + needs_open_close = true; + + if (needs_open_close) { + sensors->open = steam_sensor_open; + sensors->close = steam_sensor_close; + } + + sensors->name = steam->quirks & STEAM_QUIRK_DECK ? + "Steam Deck Motion Sensors" : + "Steam Controller Motion Sensors"; sensors->phys = hdev->phys; sensors->uniq = steam->serial_no; sensors->id.bustype = hdev->bus; @@ -867,25 +1277,34 @@ static int steam_sensors_register(struct steam_device *steam) __set_bit(EV_MSC, sensors->evbit); __set_bit(MSC_TIMESTAMP, sensors->mscbit); - input_set_abs_params(sensors, ABS_X, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_set_abs_params(sensors, ABS_Y, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_set_abs_params(sensors, ABS_Z, -STEAM_DECK_ACCEL_RANGE, - STEAM_DECK_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); - input_abs_set_res(sensors, ABS_X, STEAM_DECK_ACCEL_RES_PER_G); - input_abs_set_res(sensors, ABS_Y, STEAM_DECK_ACCEL_RES_PER_G); - input_abs_set_res(sensors, ABS_Z, STEAM_DECK_ACCEL_RES_PER_G); - - input_set_abs_params(sensors, ABS_RX, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_set_abs_params(sensors, ABS_RY, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_set_abs_params(sensors, ABS_RZ, -STEAM_DECK_GYRO_RANGE, - STEAM_DECK_GYRO_RANGE, STEAM_DECK_GYRO_FUZZ, 0); - input_abs_set_res(sensors, ABS_RX, STEAM_DECK_GYRO_RES_PER_DPS); - input_abs_set_res(sensors, ABS_RY, STEAM_DECK_GYRO_RES_PER_DPS); - input_abs_set_res(sensors, ABS_RZ, STEAM_DECK_GYRO_RES_PER_DPS); + if (steam->quirks & STEAM_QUIRK_DECK) { + input_set_abs_params(sensors, ABS_X, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Y, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Z, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_DECK_ACCEL_FUZZ, 0); + } else { + input_set_abs_params(sensors, ABS_X, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Y, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + input_set_abs_params(sensors, ABS_Z, -STEAM_ACCEL_RANGE, + STEAM_ACCEL_RANGE, STEAM_ACCEL_FUZZ, 0); + } + input_abs_set_res(sensors, ABS_X, STEAM_ACCEL_RES_PER_G); + input_abs_set_res(sensors, ABS_Y, STEAM_ACCEL_RES_PER_G); + input_abs_set_res(sensors, ABS_Z, STEAM_ACCEL_RES_PER_G); + + input_set_abs_params(sensors, ABS_RX, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_set_abs_params(sensors, ABS_RY, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_set_abs_params(sensors, ABS_RZ, -STEAM_GYRO_RANGE, + STEAM_GYRO_RANGE, STEAM_GYRO_FUZZ, 0); + input_abs_set_res(sensors, ABS_RX, STEAM_GYRO_RES_PER_DPS); + input_abs_set_res(sensors, ABS_RY, STEAM_GYRO_RES_PER_DPS); + input_abs_set_res(sensors, ABS_RZ, STEAM_GYRO_RES_PER_DPS); ret = input_register_device(sensors); if (ret) @@ -916,9 +1335,6 @@ static void steam_sensors_unregister(struct steam_device *steam) { struct input_dev *sensors; - if (!(steam->quirks & STEAM_QUIRK_DECK)) - return; - rcu_read_lock(); sensors = rcu_dereference(steam->sensors); rcu_read_unlock(); @@ -948,8 +1364,6 @@ static void steam_battery_unregister(struct steam_device *steam) static int steam_register(struct steam_device *steam) { int ret; - unsigned long client_opened; - unsigned long flags; /* * This function can be called several times in a row with the @@ -957,41 +1371,43 @@ static int steam_register(struct steam_device *steam) * another client send a get_connection_status command, for example. * The battery and serial number are set just once per device. */ - if (!steam->serial_no[0]) { - /* - * Unlikely, but getting the serial could fail, and it is not so - * important, so make up a serial number and go on. - */ - if (steam_get_serial(steam) < 0) - strscpy(steam->serial_no, "XXXXXXXXXX", - sizeof(steam->serial_no)); + if (steam->serial_no[0]) + return 0; - hid_info(steam->hdev, "Steam Controller '%s' connected", - steam->serial_no); + /* + * Unlikely, but getting the serial could fail, and it is not so + * important, so make up a serial number and go on. + */ + if (steam_get_serial(steam) < 0) + strscpy(steam->serial_no, "XXXXXXXXXX", + sizeof(steam->serial_no)); - /* ignore battery errors, we can live without it */ - if (steam->quirks & STEAM_QUIRK_WIRELESS) - steam_battery_register(steam); + ret = steam_get_attributes(steam); + if (ret < 0) + hid_err(steam->hdev, + "%s:steam_get_attributes failed with error %d\n", + __func__, ret); - mutex_lock(&steam_devices_lock); - if (list_empty(&steam->list)) - list_add(&steam->list, &steam_devices); - mutex_unlock(&steam_devices_lock); - } + hid_info(steam->hdev, "Steam %s '%s' connected", + steam->quirks & STEAM_QUIRK_DECK ? "Deck" : "Controller", + steam->serial_no); - spin_lock_irqsave(&steam->lock, flags); - client_opened = steam->client_opened; - spin_unlock_irqrestore(&steam->lock, flags); + /* ignore battery errors, we can live without it */ + if (steam->quirks & (STEAM_QUIRK_WIRELESS | STEAM_QUIRK_IBEX)) + steam_battery_register(steam); - if (!client_opened) { - steam_set_lizard_mode(steam, lizard_mode); - ret = steam_input_register(steam); - if (ret != 0) - goto steam_register_input_fail; - ret = steam_sensors_register(steam); - if (ret != 0) - goto steam_register_sensors_fail; - } + mutex_lock(&steam_devices_lock); + if (list_empty(&steam->list)) + list_add(&steam->list, &steam_devices); + mutex_unlock(&steam_devices_lock); + + steam_set_lizard_mode(steam, lizard_mode); + ret = steam_input_register(steam); + if (ret != 0) + goto steam_register_input_fail; + ret = steam_sensors_register(steam); + if (ret != 0) + goto steam_register_sensors_fail; return 0; steam_register_sensors_fail: @@ -1002,38 +1418,42 @@ static int steam_register(struct steam_device *steam) static void steam_unregister(struct steam_device *steam) { + if (!steam->serial_no[0]) + return; + + hid_info(steam->hdev, "Steam %s '%s' disconnected", + steam->quirks & STEAM_QUIRK_DECK ? "Deck" : "Controller", + steam->serial_no); steam_battery_unregister(steam); steam_sensors_unregister(steam); steam_input_unregister(steam); - if (steam->serial_no[0]) { - hid_info(steam->hdev, "Steam Controller '%s' disconnected", - steam->serial_no); - mutex_lock(&steam_devices_lock); - list_del_init(&steam->list); - mutex_unlock(&steam_devices_lock); - steam->serial_no[0] = 0; - } + mutex_lock(&steam_devices_lock); + list_del_init(&steam->list); + mutex_unlock(&steam_devices_lock); + steam->serial_no[0] = 0; } static void steam_work_connect_cb(struct work_struct *work) { struct steam_device *steam = container_of(work, struct steam_device, work_connect); + unsigned long flags; bool connected; + bool opened; int ret; spin_lock_irqsave(&steam->lock, flags); + opened = steam->client_opened; connected = steam->connected; spin_unlock_irqrestore(&steam->lock, flags); - if (connected) { + if (connected && !opened) { ret = steam_register(steam); - if (ret) { + if (ret) hid_err(steam->hdev, "%s:steam_register failed with error %d\n", __func__, ret); - } } else { steam_unregister(steam); } @@ -1049,6 +1469,7 @@ static void steam_mode_switch_cb(struct work_struct *work) return; steam->gamepad_mode = !steam->gamepad_mode; + hid_dbg(steam->hdev, "%s: switching gamepad mode to %i\n", __func__, steam->gamepad_mode); if (steam->gamepad_mode) steam_set_lizard_mode(steam, false); else { @@ -1067,46 +1488,46 @@ static void steam_mode_switch_cb(struct work_struct *work) } } -static void steam_work_unregister_cb(struct work_struct *work) -{ - struct steam_device *steam = container_of(work, struct steam_device, - unregister_work); - unsigned long flags; - bool connected; - bool opened; - - spin_lock_irqsave(&steam->lock, flags); - opened = steam->client_opened; - connected = steam->connected; - spin_unlock_irqrestore(&steam->lock, flags); - - if (connected) { - if (opened) { - steam_sensors_unregister(steam); - steam_input_unregister(steam); - } else { - steam_set_lizard_mode(steam, lizard_mode); - steam_input_register(steam); - steam_sensors_register(steam); - } - } -} - -static bool steam_is_valve_interface(struct hid_device *hdev) +static bool steam_is_valve_interface(struct hid_device *hdev, int quirks) { struct hid_report_enum *rep_enum; /* - * The wired device creates 3 interfaces: + * The 2015 wired controller creates 3 interfaces: * 0: emulated mouse. * 1: emulated keyboard. * 2: the real game pad. - * The wireless device creates 5 interfaces: + * The 2015 wireless adapter creates 5 interfaces: * 0: emulated keyboard. - * 1-4: slots where up to 4 real game pads will be connected to. - * We know which one is the real gamepad interface because they are the - * only ones with a feature report. + * 1-4: slots where up to 4 real controllers will be connected to. + * The Steam Deck creates 5 interfaces: + * 0: emulated mouse. + * 1: emulated keyboard. + * 2: the real game pad. + * 3-4: internal device comms (not HID). + * The 2026 wired controller creates 1 unified interface. + * The 2026 wireless puck creates 7 interfaces: + * 0-1: internal device comms (not HID). + * 2-5: slots where up to 4 real controllers will be connected to. + * 6: basic pogo pin interface. + * + * We know which one is the real controller interface for the pre-2026 + * controllers because they are the only ones with a feature report. + * + * The puck's pogo pin interface should be ignored as it's stripped + * down. It has one collection with usage page FF00 with usage ID 2. */ + if (quirks & STEAM_QUIRK_IBEX) { + /* There is only one BLE HID interface */ + if (quirks & STEAM_QUIRK_BLE) + return true; + + if (hdev->maxcollection < 1) + return true; + + return hdev->collection[0].usage != 0xFF000002; + } + rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; return !list_empty(&rep_enum->report_list); } @@ -1137,7 +1558,7 @@ static int steam_client_ll_open(struct hid_device *hdev) steam->client_opened++; spin_unlock_irqrestore(&steam->lock, flags); - schedule_work(&steam->unregister_work); + schedule_work(&steam->work_connect); return 0; } @@ -1152,7 +1573,7 @@ static void steam_client_ll_close(struct hid_device *hdev) steam->client_opened--; spin_unlock_irqrestore(&steam->lock, flags); - schedule_work(&steam->unregister_work); + schedule_work(&steam->work_connect); } static int steam_client_ll_raw_request(struct hid_device *hdev, @@ -1166,6 +1587,14 @@ static int steam_client_ll_raw_request(struct hid_device *hdev, report_type, reqtype); } +static int steam_client_ll_output_report(struct hid_device *hdev, + u8 *buf, size_t count) +{ + struct steam_device *steam = hdev->driver_data; + + return hid_hw_output_report(steam->hdev, buf, count); +} + static const struct hid_ll_driver steam_client_ll_driver = { .parse = steam_client_ll_parse, .start = steam_client_ll_start, @@ -1173,6 +1602,7 @@ static const struct hid_ll_driver steam_client_ll_driver = { .open = steam_client_ll_open, .close = steam_client_ll_close, .raw_request = steam_client_ll_raw_request, + .output_report = steam_client_ll_output_report, }; static struct hid_device *steam_create_client_hid(struct hid_device *hdev) @@ -1227,7 +1657,7 @@ static int steam_probe(struct hid_device *hdev, * The non-valve interfaces (mouse and keyboard emulation) are * connected without changes. */ - if (!steam_is_valve_interface(hdev)) + if (!steam_is_valve_interface(hdev, id->driver_data)) return hid_hw_start(hdev, HID_CONNECT_DEFAULT); steam = devm_kzalloc(&hdev->dev, sizeof(*steam), GFP_KERNEL); @@ -1243,8 +1673,12 @@ static int steam_probe(struct hid_device *hdev, INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb); INIT_LIST_HEAD(&steam->list); INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb); + INIT_DELAYED_WORK(&steam->coalesce_rumble_work, steam_coalesce_rumble_cb); steam->sensor_timestamp_us = 0; - INIT_WORK(&steam->unregister_work, steam_work_unregister_cb); + if (steam->quirks & STEAM_QUIRK_DECK) + steam->sensor_update_rate_us = 4000; + else + steam->sensor_update_rate_us = 9000; /* * With the real steam controller interface, do not connect hidraw. @@ -1262,14 +1696,21 @@ static int steam_probe(struct hid_device *hdev, goto err_hw_stop; } + steam->connected = true; + if (steam->quirks & STEAM_QUIRK_WIRELESS) { hid_info(hdev, "Steam wireless receiver connected"); /* If using a wireless adaptor ask for connection status */ steam->connected = false; - steam_request_conn_status(steam); - } else { - /* A wired connection is always present */ - steam->connected = true; + ret = steam_get_conn_status(steam); + if (ret < 0) + hid_err(hdev, + "%s:steam_get_conn_status failed with error %d\n", + __func__, ret); + else if (ret == WIRELESS_EVENT_CONNECT) + steam->connected = true; + } + if (steam->connected) { ret = steam_register(steam); if (ret) { hid_err(hdev, @@ -1305,7 +1746,7 @@ static int steam_probe(struct hid_device *hdev, cancel_work_sync(&steam->work_connect); cancel_delayed_work_sync(&steam->mode_switch); cancel_work_sync(&steam->rumble_work); - cancel_work_sync(&steam->unregister_work); + cancel_delayed_work_sync(&steam->coalesce_rumble_work); return ret; } @@ -1320,18 +1761,18 @@ static void steam_remove(struct hid_device *hdev) } hid_destroy_device(steam->client_hdev); - cancel_delayed_work_sync(&steam->mode_switch); - cancel_work_sync(&steam->work_connect); - cancel_work_sync(&steam->rumble_work); - cancel_work_sync(&steam->unregister_work); steam->client_hdev = NULL; + hid_hw_close(hdev); + cancel_work_sync(&steam->work_connect); steam->client_opened = 0; if (steam->quirks & STEAM_QUIRK_WIRELESS) { hid_info(hdev, "Steam wireless receiver disconnected"); } - hid_hw_close(hdev); - hid_hw_stop(hdev); steam_unregister(steam); + cancel_work_sync(&steam->rumble_work); + cancel_delayed_work_sync(&steam->mode_switch); + cancel_delayed_work_sync(&steam->coalesce_rumble_work); + hid_hw_stop(hdev); } static void steam_do_connect_event(struct steam_device *steam, bool connected) @@ -1354,13 +1795,45 @@ static void steam_do_connect_event(struct steam_device *steam, bool connected) * Clamp the values to 32767..-32767 so that the range is * symmetrical and can be negated safely. */ -static inline s16 steam_le16(u8 *data) +static inline s16 steam_le16(const u8 *data) { - s16 x = (s16) le16_to_cpup((__le16 *)data); + s16 x = (s16) get_unaligned_le16((__le16 *)data); return x == -32768 ? -32767 : x; } +struct steam_button_mapping { + int code; + u8 byte; + u8 bit; +}; + +struct steam_axis_mapping { + int code; + s8 sign; + u8 byte; +}; + +static void steam_map_buttons(struct input_dev *input, + const struct steam_button_mapping *mappings, const u8 *data) +{ + const struct steam_button_mapping *mapping; + + for (mapping = mappings; mapping->code; mapping++) + input_report_key(input, mapping->code, + data[mapping->byte] & BIT(mapping->bit)); +} + +static void steam_map_axes(struct input_dev *input, + const struct steam_axis_mapping *mappings, const u8 *data) +{ + const struct steam_axis_mapping *mapping; + + for (mapping = mappings; mapping->sign; mapping++) + input_report_abs(input, mapping->code, + mapping->sign * steam_le16(&data[mapping->byte])); +} + /* * The size for this message payload is 60. * The known values are: @@ -1413,9 +1886,9 @@ static inline s16 steam_le16(u8 *data) * 9.1 | BTN_DPAD_RIGHT | left-pad right * 9.2 | BTN_DPAD_LEFT | left-pad left * 9.3 | BTN_DPAD_DOWN | left-pad down - * 9.4 | BTN_SELECT | menu left + * 9.4 | BTN_SELECT | view * 9.5 | BTN_MODE | steam logo - * 9.6 | BTN_START | menu right + * 9.6 | BTN_START | menu * 9.7 | BTN_GRIPL | left back lever * 10.0 | BTN_GRIPR | right back lever * 10.1 | -- | left-pad clicked @@ -1427,18 +1900,52 @@ static inline s16 steam_le16(u8 *data) * 10.7 | -- | lpad_and_joy */ +static const struct steam_button_mapping steam_controller_button_mappings[] = { + { BTN_TR2, 8, 0 }, + { BTN_TL2, 8, 1 }, + { BTN_TR, 8, 2 }, + { BTN_TL, 8, 3 }, + { BTN_Y, 8, 4 }, + { BTN_B, 8, 5 }, + { BTN_X, 8, 6 }, + { BTN_A, 8, 7 }, + { BTN_SELECT, 9, 4 }, + { BTN_MODE, 9, 5 }, + { BTN_START, 9, 6 }, + { BTN_GRIPL, 9, 7 }, + { BTN_GRIPR, 10, 0 }, + { BTN_THUMBR, 10, 2 }, + { BTN_THUMBL, 10, 6 }, + { BTN_THUMB2, 10, 4 }, + { BTN_DPAD_UP, 9, 0 }, + { BTN_DPAD_RIGHT, 9, 1 }, + { BTN_DPAD_LEFT, 9, 2 }, + { BTN_DPAD_DOWN, 9, 3 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_controller_axis_mappings[] = { + { ABS_RX, 1, 20 }, + { ABS_RY, -1, 22 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_controller_imu_mappings[] = { + { ABS_X, 1, 28 }, + { ABS_Z, -1, 30 }, + { ABS_Y, 1, 32 }, + { ABS_RX, 1, 34 }, + { ABS_RZ, 1, 36 }, + { ABS_RY, 1, 38 }, + { /* sentinel */ }, +}; + static void steam_do_input_event(struct steam_device *steam, struct input_dev *input, u8 *data) { - /* 24 bits of buttons */ - u8 b8, b9, b10; s16 x, y; bool lpad_touched, lpad_and_joy; - b8 = data[8]; - b9 = data[9]; - b10 = data[10]; - input_report_abs(input, ABS_HAT2Y, data[11]); input_report_abs(input, ABS_HAT2X, data[12]); @@ -1450,8 +1957,8 @@ static void steam_do_input_event(struct steam_device *steam, * joystick values. * (lpad_touched || lpad_and_joy) tells if the lpad is really touched. */ - lpad_touched = b10 & BIT(3); - lpad_and_joy = b10 & BIT(7); + lpad_touched = data[10] & BIT(3); + lpad_and_joy = data[10] & BIT(7); x = steam_le16(data + 16); y = -steam_le16(data + 18); @@ -1467,35 +1974,25 @@ static void steam_do_input_event(struct steam_device *steam, input_report_abs(input, ABS_HAT0X, 0); input_report_abs(input, ABS_HAT0Y, 0); } + input_report_key(input, BTN_THUMB, lpad_touched || lpad_and_joy); - input_report_abs(input, ABS_RX, steam_le16(data + 20)); - input_report_abs(input, ABS_RY, -steam_le16(data + 22)); - - input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0))); - input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1))); - input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2))); - input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3))); - input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4))); - input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5))); - input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6))); - input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7))); - input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4))); - input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5))); - input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6))); - input_event(input, EV_KEY, BTN_GRIPL, !!(b9 & BIT(7))); - input_event(input, EV_KEY, BTN_GRIPR, !!(b10 & BIT(0))); - input_event(input, EV_KEY, BTN_THUMBR, !!(b10 & BIT(2))); - input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6))); - input_event(input, EV_KEY, BTN_THUMB, lpad_touched || lpad_and_joy); - input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(4))); - input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0))); - input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1))); - input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3))); + steam_map_buttons(input, steam_controller_button_mappings, data); + steam_map_axes(input, steam_controller_axis_mappings, data); input_sync(input); } +static void steam_do_sensors_event(struct steam_device *steam, + struct input_dev *sensors, u8 *data) +{ + steam->sensor_timestamp_us += steam->sensor_update_rate_us; + + input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us); + steam_map_axes(sensors, steam_controller_imu_mappings, data); + + input_sync(sensors); +} + /* * The size for this message payload is 56. * The known values are: @@ -1541,9 +2038,9 @@ static void steam_do_input_event(struct steam_device *steam, * 9.1 | BTN_DPAD_RIGHT | left-pad right * 9.2 | BTN_DPAD_LEFT | left-pad left * 9.3 | BTN_DPAD_DOWN | left-pad down - * 9.4 | BTN_SELECT | menu left + * 9.4 | BTN_SELECT | view * 9.5 | BTN_MODE | steam logo - * 9.6 | BTN_START | menu right + * 9.6 | BTN_START | menu * 9.7 | BTN_GRIPL2 | left bottom grip button * 10.0 | BTN_GRIPR2 | right bottom grip button * 10.1 | BTN_THUMB | left pad pressed @@ -1594,23 +2091,68 @@ static void steam_do_input_event(struct steam_device *steam, * 15.6 | -- | unknown * 15.7 | -- | unknown */ + +static const struct steam_button_mapping steam_deck_button_mappings[] = { + { BTN_TR2, 8, 0 }, + { BTN_TL2, 8, 1 }, + { BTN_TR, 8, 2 }, + { BTN_TL, 8, 3 }, + { BTN_Y, 8, 4 }, + { BTN_B, 8, 5 }, + { BTN_X, 8, 6 }, + { BTN_A, 8, 7 }, + { BTN_SELECT, 9, 4 }, + { BTN_MODE, 9, 5 }, + { BTN_START, 9, 6 }, + { BTN_GRIPL2, 9, 7 }, + { BTN_GRIPR2, 10, 0 }, + { BTN_THUMBL, 10, 6 }, + { BTN_THUMBR, 11, 2 }, + { BTN_DPAD_UP, 9, 0 }, + { BTN_DPAD_RIGHT, 9, 1 }, + { BTN_DPAD_LEFT, 9, 2 }, + { BTN_DPAD_DOWN, 9, 3 }, + { BTN_THUMB, 10, 1 }, + { BTN_THUMB2, 10, 2 }, + { BTN_GRIPL, 13, 1 }, + { BTN_GRIPR, 13, 2 }, + { BTN_BASE, 14, 2 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_deck_axis_mappings[] = { + { ABS_X, 1, 48 }, + { ABS_Y, -1, 50 }, + { ABS_RX, 1, 52 }, + { ABS_RY, -1, 54 }, + { ABS_HAT2Y, 1, 44 }, + { ABS_HAT2X, 1, 46 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_deck_imu_mappings[] = { + { ABS_X, 1, 24 }, + { ABS_Z, -1, 26 }, + { ABS_Y, 1, 28 }, + { ABS_RX, 1, 30 }, + { ABS_RZ, -1, 32 }, + { ABS_RY, 1, 34 }, + { /* sentinel */ }, +}; + static void steam_do_deck_input_event(struct steam_device *steam, struct input_dev *input, u8 *data) { - u8 b8, b9, b10, b11, b13, b14; + bool start_pressed; bool lpad_touched, rpad_touched; - b8 = data[8]; - b9 = data[9]; - b10 = data[10]; - b11 = data[11]; - b13 = data[13]; - b14 = data[14]; + start_pressed = data[9] & BIT(6); - if (!(b9 & BIT(6)) && steam->did_mode_switch) { + if (!start_pressed && steam->did_mode_switch) { steam->did_mode_switch = false; cancel_delayed_work(&steam->mode_switch); - } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) { + } else if (!steam->client_opened && start_pressed && !steam->did_mode_switch) { + hid_dbg(steam->hdev, "%s: doing mode switch\n", __func__); steam->did_mode_switch = true; schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100); } @@ -1618,8 +2160,8 @@ static void steam_do_deck_input_event(struct steam_device *steam, if (!steam->gamepad_mode && lizard_mode) return; - lpad_touched = b10 & BIT(3); - rpad_touched = b10 & BIT(4); + lpad_touched = data[10] & BIT(3); + rpad_touched = data[10] & BIT(4); if (lpad_touched) { input_report_abs(input, ABS_HAT0X, steam_le16(data + 16)); @@ -1637,38 +2179,8 @@ static void steam_do_deck_input_event(struct steam_device *steam, input_report_abs(input, ABS_HAT1Y, 0); } - input_report_abs(input, ABS_X, steam_le16(data + 48)); - input_report_abs(input, ABS_Y, -steam_le16(data + 50)); - input_report_abs(input, ABS_RX, steam_le16(data + 52)); - input_report_abs(input, ABS_RY, -steam_le16(data + 54)); - - input_report_abs(input, ABS_HAT2Y, steam_le16(data + 44)); - input_report_abs(input, ABS_HAT2X, steam_le16(data + 46)); - - input_event(input, EV_KEY, BTN_TR2, !!(b8 & BIT(0))); - input_event(input, EV_KEY, BTN_TL2, !!(b8 & BIT(1))); - input_event(input, EV_KEY, BTN_TR, !!(b8 & BIT(2))); - input_event(input, EV_KEY, BTN_TL, !!(b8 & BIT(3))); - input_event(input, EV_KEY, BTN_Y, !!(b8 & BIT(4))); - input_event(input, EV_KEY, BTN_B, !!(b8 & BIT(5))); - input_event(input, EV_KEY, BTN_X, !!(b8 & BIT(6))); - input_event(input, EV_KEY, BTN_A, !!(b8 & BIT(7))); - input_event(input, EV_KEY, BTN_SELECT, !!(b9 & BIT(4))); - input_event(input, EV_KEY, BTN_MODE, !!(b9 & BIT(5))); - input_event(input, EV_KEY, BTN_START, !!(b9 & BIT(6))); - input_event(input, EV_KEY, BTN_GRIPL2, !!(b9 & BIT(7))); - input_event(input, EV_KEY, BTN_GRIPR2, !!(b10 & BIT(0))); - input_event(input, EV_KEY, BTN_THUMBL, !!(b10 & BIT(6))); - input_event(input, EV_KEY, BTN_THUMBR, !!(b11 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_UP, !!(b9 & BIT(0))); - input_event(input, EV_KEY, BTN_DPAD_RIGHT, !!(b9 & BIT(1))); - input_event(input, EV_KEY, BTN_DPAD_LEFT, !!(b9 & BIT(2))); - input_event(input, EV_KEY, BTN_DPAD_DOWN, !!(b9 & BIT(3))); - input_event(input, EV_KEY, BTN_THUMB, !!(b10 & BIT(1))); - input_event(input, EV_KEY, BTN_THUMB2, !!(b10 & BIT(2))); - input_event(input, EV_KEY, BTN_GRIPL, !!(b13 & BIT(1))); - input_event(input, EV_KEY, BTN_GRIPR, !!(b13 & BIT(2))); - input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2))); + steam_map_buttons(input, steam_deck_button_mappings, data); + steam_map_axes(input, steam_deck_axis_mappings, data); input_sync(input); } @@ -1676,25 +2188,13 @@ static void steam_do_deck_input_event(struct steam_device *steam, static void steam_do_deck_sensors_event(struct steam_device *steam, struct input_dev *sensors, u8 *data) { - /* - * The deck input report is received every 4 ms on average, - * with a jitter of +/- 4 ms even though the USB descriptor claims - * that it uses 1 kHz. - * Since the HID report does not include a sensor timestamp, - * use a fixed increment here. - */ - steam->sensor_timestamp_us += 4000; + steam->sensor_timestamp_us += steam->sensor_update_rate_us; if (!steam->gamepad_mode && lizard_mode) return; input_event(sensors, EV_MSC, MSC_TIMESTAMP, steam->sensor_timestamp_us); - input_report_abs(sensors, ABS_X, steam_le16(data + 24)); - input_report_abs(sensors, ABS_Z, -steam_le16(data + 26)); - input_report_abs(sensors, ABS_Y, steam_le16(data + 28)); - input_report_abs(sensors, ABS_RX, steam_le16(data + 30)); - input_report_abs(sensors, ABS_RZ, -steam_le16(data + 32)); - input_report_abs(sensors, ABS_RY, steam_le16(data + 34)); + steam_map_axes(sensors, steam_deck_imu_mappings, data); input_sync(sensors); } @@ -1722,7 +2222,7 @@ static void steam_do_battery_event(struct steam_device *steam, battery = rcu_dereference(steam->battery); if (likely(battery)) { spin_lock_irqsave(&steam->lock, flags); - steam->voltage = volts; + steam->battery_voltage = volts; steam->battery_charge = batt; spin_unlock_irqrestore(&steam->lock, flags); power_supply_changed(battery); @@ -1730,6 +2230,216 @@ static void steam_do_battery_event(struct steam_device *steam, rcu_read_unlock(); } +/* + * The size for this message payload is 53 in REPORT_ID_INPUT and 45 in REPORT_ID_INPUT2. + * The values are: + * (* values only in REPORT_ID_INPUT) + * Offset| Type | Mapped to |Meaning + * -------+-------+-----------+-------------------------- + * 1 | u8 | -- | sequence number + * 2-5 | u32 | see below | buttons + * 6-7 | s16 | ABS_HAT2Y | left trigger (uncalibrated) + * 8-9 | s16 | ABS_HAT2X | right trigger (uncalibrated) + * 10-11 | s16 | ABS_X | left joystick X + * 12-13 | s16 | ABS_Y | left joystick Y + * 14-15 | s16 | ABS_RX | right joystick X + * 16-17 | s16 | ABS_RY | right joystick Y + * 18-19 | s16 | ABS_HAT0X | left-pad X value + * 20-21 | s16 | ABS_HAT0Y | left-pad Y value + * 22-23 | u16 | -- | left pad pressure + * 24-25 | s16 | ABS_HAT1X | right-pad X value + * 26-27 | s16 | ABS_HAT1Y | right-pad Y value + * 28-29 | u16 | -- | right pad pressure + * 30-33 | u32 | IMU MSC_TIMESTAMP | IMU timestamp + * 34-35 | s16 | IMU ABS_X | accelerometer X value + * 36-37 | s16 | IMU ABS_Z | accelerometer Y value + * 38-39 | s16 | IMU ABS_Y | accelerometer Z value + * 40-41 | s16 | IMU ABS_RX | gyro X value + * 42-43 | s16 | IMU ABS_RZ | gyro Y value + * 44-45 | s16 | IMU ABS_RY | gyro Z value + * 46-47 | s16 | -- | * quaternion W value + * 48-49 | s16 | -- | * quaternion X value + * 50-51 | s16 | -- | * quaternion Y value + * 52-53 | s16 | -- | * quaternion Z value + * + * The buttons are: + * Bit | Mapped to | Description + * ------+------------+-------------------------------- + * 2.0 | BTN_A | button A + * 2.1 | BTN_B | button B + * 2.2 | BTN_X | button X + * 2.3 | BTN_Y | button Y + * 2.4 | BTN_BASE | quick access button + * 2.5 | BTN_THUMBR | right joystick clicked + * 2.6 | BTN_START | menu + * 2.7 | BTN_GRIPR | right top grip button + * 3.0 | BTN_GRIPR2 | right bottom grip button + * 3.1 | BTN_TR | right shoulder + * 3.2 | BTN_DPAD_DOWN | left-pad down + * 3.3 | BTN_DPAD_RIGHT | left-pad right + * 3.4 | BTN_DPAD_LEFT | left-pad left + * 3.5 | BTN_DPAD_UP | left-pad up + * 3.6 | BTN_SELECT | view + * 3.7 | BTN_THUMBL | left joystick clicked + * 4.0 | BTN_MODE | steam logo + * 4.1 | BTN_GRIPL | left top grip button + * 4.2 | BTN_GRIPL2 | left bottom grip button + * 4.3 | BTN_TL | left shoulder + * 4.4 | -- | right joystick touched + * 4.5 | -- | right pad touched + * 4.6 | BTN_THUMB2 | right pad pressed + * 4.7 | BTN_TR2 | right trigger fully pressed + * 5.0 | -- | left joystick touched + * 5.1 | -- | left pad touched + * 5.2 | BTN_THUMB | left pad pressed + * 5.3 | BTN_TL2 | left trigger fully pressed + * 5.4 | -- | right grip touch + * 5.5 | -- | left grip touch + * 5.6 | -- | unmapped + * 5.7 | -- | unmapped + */ + +static const struct steam_button_mapping steam_triton_button_mappings[] = { + { BTN_A, 2, 0 }, + { BTN_B, 2, 1 }, + { BTN_X, 2, 2 }, + { BTN_Y, 2, 3 }, + { BTN_BASE, 2, 4 }, + { BTN_THUMBR, 2, 5 }, + { BTN_START, 2, 6 }, + { BTN_GRIPR, 2, 7 }, + { BTN_GRIPR2, 3, 0 }, + { BTN_TR, 3, 1 }, + { BTN_DPAD_DOWN, 3, 2 }, + { BTN_DPAD_RIGHT, 3, 3 }, + { BTN_DPAD_LEFT, 3, 4 }, + { BTN_DPAD_UP, 3, 5 }, + { BTN_SELECT, 3, 6 }, + { BTN_THUMBL, 3, 7 }, + { BTN_MODE, 4, 0 }, + { BTN_GRIPL, 4, 1 }, + { BTN_GRIPL2, 4, 2 }, + { BTN_TL, 4, 3 }, + { BTN_THUMB2, 4, 6 }, + { BTN_TR2, 4, 7 }, + { BTN_THUMB, 5, 2 }, + { BTN_TL2, 5, 3 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_triton_axis_mappings[] = { + { ABS_X, 1, 10 }, + { ABS_Y, -1, 12 }, + { ABS_RX, 1, 14 }, + { ABS_RY, -1, 16 }, + { ABS_HAT2Y, 1, 6 }, + { ABS_HAT2X, 1, 8 }, + { /* sentinel */ }, +}; + +static const struct steam_axis_mapping steam_triton_imu_mappings[] = { + { ABS_X, 1, 34 }, + { ABS_Z, -1, 36 }, + { ABS_Y, 1, 38 }, + { ABS_RX, 1, 40 }, + { ABS_RZ, -1, 42 }, + { ABS_RY, 1, 44 }, + { /* sentinel */ }, +}; + +static void steam_do_triton_input_event(struct steam_device *steam, + struct input_dev *input, const u8 *data) +{ + bool start_pressed; + bool lpad_touched, rpad_touched; + + start_pressed = data[2] & BIT(6); + + if (!start_pressed && steam->did_mode_switch) { + steam->did_mode_switch = false; + cancel_delayed_work(&steam->mode_switch); + } else if (!steam->client_opened && start_pressed && !steam->did_mode_switch) { + steam->did_mode_switch = true; + schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100); + } + + if (!steam->gamepad_mode && lizard_mode) + return; + + lpad_touched = data[5] & BIT(1); + rpad_touched = data[4] & BIT(5); + + if (lpad_touched) { + input_report_abs(input, ABS_HAT0X, steam_le16(data + 18)); + input_report_abs(input, ABS_HAT0Y, steam_le16(data + 20)); + } else { + input_report_abs(input, ABS_HAT0X, 0); + input_report_abs(input, ABS_HAT0Y, 0); + } + + if (rpad_touched) { + input_report_abs(input, ABS_HAT1X, steam_le16(data + 24)); + input_report_abs(input, ABS_HAT1Y, steam_le16(data + 26)); + } else { + input_report_abs(input, ABS_HAT1X, 0); + input_report_abs(input, ABS_HAT1Y, 0); + } + steam_map_buttons(input, steam_triton_button_mappings, data); + steam_map_axes(input, steam_triton_axis_mappings, data); + + input_sync(input); +} + +static void steam_do_triton_sensors_event(struct steam_device *steam, + struct input_dev *sensors, const u8 *data) +{ + u32 timestamp; + + if (!steam->gamepad_mode && lizard_mode) + return; + + timestamp = (u32) get_unaligned_le32((__le32 *)&data[30]); + input_event(sensors, EV_MSC, MSC_TIMESTAMP, timestamp); + steam_map_axes(sensors, steam_triton_imu_mappings, data); + + input_sync(sensors); +} + +static void steam_do_triton_battery_event(struct steam_device *steam, + struct power_supply *battery, + const struct steam_triton_battery_status *data) +{ + unsigned long flags; + + /* Creating the battery may have failed */ + guard(rcu)(); + battery = rcu_dereference(steam->battery); + if (!likely(battery)) + return; + + spin_lock_irqsave(&steam->lock, flags); + steam->battery_voltage = get_unaligned_le16(&data->battery_voltage); + steam->battery_current = get_unaligned_le16(&data->battery_current); + steam->battery_temp = get_unaligned_le16(&data->temperature); + steam->battery_charge = data->battery_level; + switch (data->charge_state) { + case CHARGE_STATE_CHARGING: + steam->battery_status = POWER_SUPPLY_STATUS_CHARGING; + break; + case CHARGE_STATE_DISCHARGING: + steam->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case CHARGE_STATE_CHARGING_DONE: + steam->battery_status = POWER_SUPPLY_STATUS_FULL; + break; + default: + steam->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + spin_unlock_irqrestore(&steam->lock, flags); + power_supply_changed(battery); +} + static int steam_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) @@ -1743,8 +2453,84 @@ static int steam_raw_event(struct hid_device *hdev, return 0; if (steam->client_opened) - hid_input_report(steam->client_hdev, HID_FEATURE_REPORT, - data, size, 0); + hid_input_report(steam->client_hdev, report->type, data, size, 0); + + /* Triton uses a different report format */ + if (steam->quirks & STEAM_QUIRK_IBEX) { + if (report->type != HID_INPUT_REPORT) + return 0; + + switch (report->id) { + case REPORT_ID_INPUT: + if (size != 54) + return 0; + if (steam->client_opened) + return 0; + rcu_read_lock(); + input = rcu_dereference(steam->input); + if (likely(input)) { + steam_do_triton_input_event(steam, input, data); + } else { + dbg_hid("%s: input data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_triton_sensors_event(steam, sensors, data); + rcu_read_unlock(); + break; + case REPORT_ID_INPUT2: + if (size != 46) + return 0; + if (steam->client_opened) + return 0; + rcu_read_lock(); + input = rcu_dereference(steam->input); + if (likely(input)) { + steam_do_triton_input_event(steam, input, data); + } else { + dbg_hid("%s: input data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_triton_sensors_event(steam, sensors, data); + rcu_read_unlock(); + break; + case REPORT_ID_BATTERY: + if (size != 15) + return 0; + rcu_read_lock(); + battery = rcu_dereference(steam->battery); + if (likely(battery)) { + steam_do_triton_battery_event(steam, battery, + (const struct steam_triton_battery_status *)&data[1]); + } else { + dbg_hid("%s: battery data without connect event\n", + __func__); + steam_do_connect_event(steam, true); + } + rcu_read_unlock(); + break; + case REPORT_ID_WIRELESS_EVENT: + if (size != 2) + return 0; + switch (data[1]) { + case WIRELESS_EVENT_DISCONNECT: + steam_do_connect_event(steam, false); + break; + case WIRELESS_EVENT_CONNECT: + steam_do_connect_event(steam, true); + break; + } + break; + } + + return 0; + } + /* * All messages are size=64, all values little-endian. * The format is: @@ -1773,6 +2559,9 @@ static int steam_raw_event(struct hid_device *hdev, input = rcu_dereference(steam->input); if (likely(input)) steam_do_input_event(steam, input, data); + sensors = rcu_dereference(steam->sensors); + if (likely(sensors)) + steam_do_sensors_event(steam, sensors, data); rcu_read_unlock(); break; case ID_CONTROLLER_DECK_STATE: @@ -1850,11 +2639,11 @@ MODULE_PARM_DESC(lizard_mode, "Enable mouse and keyboard emulation (lizard mode) when the gamepad is not in use"); static const struct hid_device_id steam_controllers[] = { - { /* Wired Steam Controller */ + { /* Wired Steam Controller (2015) */ HID_USB_DEVICE(USB_VENDOR_ID_VALVE, USB_DEVICE_ID_STEAM_CONTROLLER) }, - { /* Wireless Steam Controller */ + { /* Wireless Steam Controller (2015) */ HID_USB_DEVICE(USB_VENDOR_ID_VALVE, USB_DEVICE_ID_STEAM_CONTROLLER_WIRELESS), .driver_data = STEAM_QUIRK_WIRELESS @@ -1864,6 +2653,26 @@ static const struct hid_device_id steam_controllers[] = { USB_DEVICE_ID_STEAM_DECK), .driver_data = STEAM_QUIRK_DECK }, + { /* Steam Controller (2026) wired */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_IBEX), + .driver_data = STEAM_QUIRK_IBEX + }, + { /* Steam Controller (2026) BLE */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_IBEX_BLE), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_BLE + }, + { /* Steam Controller (2026) Puck */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_PROTEUS), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_WIRELESS + }, + { /* Steam Controller (2026) Steam Machine internal receiver */ + HID_USB_DEVICE(USB_VENDOR_ID_VALVE, + USB_DEVICE_ID_STEAM_CONTROLLER_NEREID), + .driver_data = STEAM_QUIRK_IBEX | STEAM_QUIRK_WIRELESS + }, {} }; diff --git a/drivers/hid/zotac-zone-hid/Kconfig b/drivers/hid/zotac-zone-hid/Kconfig new file mode 100644 index 000000000000..89fb2ac080bb --- /dev/null +++ b/drivers/hid/zotac-zone-hid/Kconfig @@ -0,0 +1,8 @@ +config ZOTAC_ZONE_HID + tristate "Zotac Zone handheld support" + depends on USB_HID + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + select POWER_SUPPLY + help + Support for the Zotac Zone handheld gaming console. diff --git a/drivers/hid/zotac-zone-hid/Makefile b/drivers/hid/zotac-zone-hid/Makefile new file mode 100644 index 000000000000..2f7053b981ac --- /dev/null +++ b/drivers/hid/zotac-zone-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Makefile - Zotac Zone handheld device driver +# +zotac-zone-hid-y := zotac-zone-hid-core.o zotac-zone-hid-rgb.o zotac-zone-hid-input.o zotac-zone-hid-config.o +obj-$(CONFIG_ZOTAC_ZONE_HID) := zotac-zone-hid.o diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c new file mode 100644 index 000000000000..049615194392 --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-config.c @@ -0,0 +1,2231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* +* HID driver for ZOTAC Gaming Zone Controller - RGB LED control +* +* Copyright (c) 2025 Luke D. Jones +*/ + +#include "asm-generic/errno-base.h" +#include "linux/kstrtox.h" +#include +#include +#include +#include +#include + +#include "zotac-zone.h" + +#define REPORT_SIZE 64 + +#define HEADER_TAG_POS 0 +#define RESERVED_POS 1 +#define SEQUENCE_POS 2 +#define PAYLOADSIZE_POS 3 +#define COMMAND_POS 4 +#define SETTING_POS 5 +#define VALUE_POS 6 +#define CRC_H_POS 0x3E +#define CRC_L_POS 0x3F + +#define HEADER_TAG 0xE1 +#define PAYLOAD_SIZE 0x3C + +/* + * Button mapping structure indices (relative to data buffer) + * The data buffer is copied to the correct location in the HID report. + */ +#define HEADER_LEN 5 +#define BTN_MAP_SOURCE_IDX (0x05 - HEADER_LEN) /* Source button ID */ +#define BTN_MAP_GAMEPAD_START_IDX (0x06 - HEADER_LEN) +#define BTN_MAP_GAMEPAD_SIZE 4 +#define BTN_MAP_MODIFIER_IDX (0x0A - HEADER_LEN) +#define BTN_MAP_KEYBOARD_START_IDX (0x0C - HEADER_LEN) +#define BTN_MAP_KEYBOARD_SIZE 6 +#define BTN_MAP_MOUSE_IDX (0x12 - HEADER_LEN) +#define BTN_MAP_RESPONSE_MIN_SIZE 14 + +#define GAMEPAD_DPAD_STICK_IDX 0 /* DPad, Stick buttons */ +#define GAMEPAD_FACE_BUMPER_IDX 1 /* Face buttons, Bumpers */ +#define GAMEPAD_TRIGGER_IDX 2 /* Triggers */ +#define GAMEPAD_RESERVED_IDX 3 /* Reserved/unused */ +/* + * Note: The above indices are relative to our data buffer, not the full protocol packet. + * In the full protocol packet (as described in documentation), these fields would be + * at different positions due to the packet header. + * + * For example, BTN_MAP_MOUSE_IDX (13 in our buffer) corresponds to offset 0x12-0x13 + * in the full protocol packet. + */ + +/* Mouse speed constants */ +#define CMD_SET_MOUSE_SPEED 0xA3 +#define CMD_GET_MOUSE_SPEED 0xA4 +#define MOUSE_SPEED_MIN 0x01 /* Slow */ +#define MOUSE_SPEED_MAX 0x0A /* Fast */ + +#define STICK_SENSITIVITY_NUM_IDX 0 +#define STICK_SENSITIVITY_DATA_IDX 1 +#define STICK_SENSITIVITY_SIZE 8 + +#define DZ_LEFT_INNER_IDX 0 +#define DZ_LEFT_OUTER_IDX 1 +#define DZ_RIGHT_INNER_IDX 2 +#define DZ_RIGHT_OUTER_IDX 3 +#define DZ_RESPONSE_SIZE 4 + +#define VIB_LEFT_TRIGGER_IDX 0 +#define VIB_RIGHT_TRIGGER_IDX 1 +#define VIB_LEFT_RUMBLE_IDX 2 +#define VIB_RIGHT_RUMBLE_IDX 3 +#define VIB_RESPONSE_SIZE 4 + +#define CMD_SET_PROFILE 0xB1 +#define CMD_GET_PROFILE 0xB2 +#define CMD_GET_PROFILE_NUM 0xB3 +#define CMD_RESTORE_PROFILE 0xF1 +#define CMD_SAVE_CONFIG 0xFB + +#define CMD_SET_VIBRATION_STRENGTH 0xA9 +#define CMD_GET_VIBRATION_STRENGTH 0xAA +#define CMD_SET_STICK_DEADZONES 0xA5 +#define CMD_GET_STICK_DEADZONES 0xA6 +#define CMD_SET_TRIGGER_DEADZONES 0xB4 +#define CMD_GET_TRIGGER_DEADZONES 0xB5 +#define CMD_SET_STICK_SENSITIVITY 0xBA +#define CMD_GET_STICK_SENSITIVITY 0xBB +#define CMD_SET_BUTTON_TURBO 0xB8 +#define CMD_GET_BUTTON_TURBO 0xB9 +#define CMD_GET_DEVICE_INFO 0xFA +#define CMD_MOTOR_TEST 0xBD + +#define MOTOR_TEST_SIZE 4 + +#define STICK_LEFT 0 +#define STICK_RIGHT 1 + +#define PROFILE_DEFAULT 0x00 +#define PROFILE_SECONDARY 0x01 + +/* Button bit positions within the turbo byte */ +#define A_BTN_POS 0 +#define B_BTN_POS 1 +#define X_BTN_POS 2 +#define Y_BTN_POS 3 +#define LB_BTN_POS 4 +#define RB_BTN_POS 5 +#define LT_BTN_POS 6 +#define RT_BTN_POS 7 + +/* Button ID definitions */ +#define BUTTON_NONE 0x00 +#define BUTTON_M1 0x01 +#define BUTTON_M2 0x02 +#define BUTTON_L_TOUCH_UP 0x03 +#define BUTTON_L_TOUCH_DOWN 0x04 +#define BUTTON_L_TOUCH_LEFT 0x05 +#define BUTTON_L_TOUCH_RIGHT 0x06 +#define BUTTON_R_TOUCH_UP 0x07 +#define BUTTON_R_TOUCH_DOWN 0x08 +#define BUTTON_R_TOUCH_LEFT 0x09 +#define BUTTON_R_TOUCH_RIGHT 0x0A +#define BUTTON_LB 0x0B +#define BUTTON_RB 0x0C +#define BUTTON_LT 0x0D +#define BUTTON_RT 0x0E +#define BUTTON_A 0x0F +#define BUTTON_B 0x10 +#define BUTTON_X 0x11 +#define BUTTON_Y 0x12 +#define BUTTON_DPAD_UP 0x13 +#define BUTTON_DPAD_DOWN 0x14 +#define BUTTON_DPAD_LEFT 0x15 +#define BUTTON_DPAD_RIGHT 0x16 +#define BUTTON_LS 0x17 +#define BUTTON_RS 0x18 + +/* Modifier key definitions */ +#define MOD_NONE 0x00 +#define MOD_LEFT_CTRL 0x01 +#define MOD_LEFT_SHIFT 0x02 +#define MOD_LEFT_ALT 0x04 +#define MOD_LEFT_WIN 0x08 +#define MOD_RIGHT_CTRL 0x10 +#define MOD_RIGHT_SHIFT 0x20 +#define MOD_RIGHT_ALT 0x40 +#define MOD_RIGHT_WIN 0x80 + +int zotac_cfg_refresh(struct zotac_device *zotac); + +struct button_directory { + const char *name; /* Directory name (e.g., "btn_a") */ + u8 button_id; /* Associated button ID */ + bool has_turbo; /* Whether this button supports turbo */ + struct kobject * + kobj; /* kobject for this directory (filled during registration) */ + struct attribute_group *main_group; /* Main button attributes */ + struct attribute_group *remap_group; /* Remap subdirectory attributes */ +}; + +/* Structure to map button names to their byte and bit positions */ +struct button_mapping_entry { + const char *name; + u8 byte_index; + u8 bit_mask; +}; + +/* Define all supported buttons with their byte index and bit mask */ +static const struct button_mapping_entry button_map[] = { + { "dpad_up", 0, 0x01 }, + { "dpad_down", 0, 0x02 }, + { "dpad_left", 0, 0x04 }, + { "dpad_right", 0, 0x08 }, + { "ls", 0, 0x40 }, + { "rs", 0, 0x80 }, + { "lb", 1, 0x01 }, + { "rb", 1, 0x02 }, + { "a", 1, 0x10 }, + { "b", 1, 0x20 }, + { "x", 1, 0x40 }, + { "y", 1, 0x80 }, + { "lt", 2, 0x01 }, + { "rt", 2, 0x02 }, + { NULL, 0, 0 } /* Terminator */ +}; + +static const struct { + const char *name; + u8 id; +} gamepad_button_names[] = { + { "none", BUTTON_NONE }, + { "a", BUTTON_A }, + { "b", BUTTON_B }, + { "x", BUTTON_X }, + { "y", BUTTON_Y }, + { "lb", BUTTON_LB }, + { "rb", BUTTON_RB }, + { "lt", BUTTON_LT }, + { "rt", BUTTON_RT }, + { "ls", BUTTON_LS }, + { "rs", BUTTON_RS }, + { "dpad_up", BUTTON_DPAD_UP }, + { "dpad_down", BUTTON_DPAD_DOWN }, + { "dpad_left", BUTTON_DPAD_LEFT }, + { "dpad_right", BUTTON_DPAD_RIGHT }, +}; + +static const struct { + const char *name; + u8 value; +} modifier_names[] = { + { "none", MOD_NONE }, + { "left_ctrl", MOD_LEFT_CTRL }, + { "left_shift", MOD_LEFT_SHIFT }, + { "left_alt", MOD_LEFT_ALT }, + { "left_win", MOD_LEFT_WIN }, + { "right_ctrl", MOD_RIGHT_CTRL }, + { "right_shift", MOD_RIGHT_SHIFT }, + { "right_alt", MOD_RIGHT_ALT }, + { "right_win", MOD_RIGHT_WIN }, +}; + +/* Keyboard key definitions */ +struct key_name_mapping { + const char *name; + u8 keycode; +}; + +static const struct key_name_mapping keyboard_keys[] = { + { "none", 0x00 }, { "a", 0x04 }, { "b", 0x05 }, + { "c", 0x06 }, { "d", 0x07 }, { "e", 0x08 }, + { "f", 0x09 }, { "g", 0x0a }, { "h", 0x0b }, + { "i", 0x0c }, { "j", 0x0d }, { "k", 0x0e }, + { "l", 0x0f }, { "m", 0x10 }, { "n", 0x11 }, + { "o", 0x12 }, { "p", 0x13 }, { "q", 0x14 }, + { "r", 0x15 }, { "s", 0x16 }, { "t", 0x17 }, + { "u", 0x18 }, { "v", 0x19 }, { "w", 0x1a }, + { "x", 0x1b }, { "y", 0x1c }, { "z", 0x1d }, + { "1", 0x1e }, { "2", 0x1f }, { "3", 0x20 }, + { "4", 0x21 }, { "5", 0x22 }, { "6", 0x23 }, + { "7", 0x24 }, { "8", 0x25 }, { "9", 0x26 }, + { "0", 0x27 }, { "enter", 0x28 }, { "esc", 0x29 }, + { "backspace", 0x2a }, { "tab", 0x2b }, { "space", 0x2c }, + { "minus", 0x2d }, { "equals", 0x2e }, { "leftbrace", 0x2f }, + { "rightbrace", 0x30 }, { "backslash", 0x31 }, { "semicolon", 0x33 }, + { "apostrophe", 0x34 }, { "grave", 0x35 }, { "comma", 0x36 }, + { "dot", 0x37 }, { "slash", 0x38 }, { "capslock", 0x39 }, + { "f1", 0x3a }, { "f2", 0x3b }, { "f3", 0x3c }, + { "f4", 0x3d }, { "f5", 0x3e }, { "f6", 0x3f }, + { "f7", 0x40 }, { "f8", 0x41 }, { "f9", 0x42 }, + { "f10", 0x43 }, { "f11", 0x44 }, { "f12", 0x45 }, + { "printscreen", 0x46 }, { "scrolllock", 0x47 }, { "pause", 0x48 }, + { "insert", 0x49 }, { "home", 0x4a }, { "pageup", 0x4b }, + { "delete", 0x4c }, { "end", 0x4d }, { "pagedown", 0x4e }, + { "right", 0x4f }, { "left", 0x50 }, { "down", 0x51 }, + { "up", 0x52 }, { "numlock", 0x53 }, { "kpslash", 0x54 }, + { "kpasterisk", 0x55 }, { "kpminus", 0x56 }, { "kpplus", 0x57 }, + { "kpenter", 0x58 }, { "kp1", 0x59 }, { "kp2", 0x5a }, + { "kp3", 0x5b }, { "kp4", 0x5c }, { "kp5", 0x5d }, + { "kp6", 0x5e }, { "kp7", 0x5f }, { "kp8", 0x60 }, + { "kp9", 0x61 }, { "kp0", 0x62 }, { "kpdot", 0x63 }, + { "application", 0x65 } +}; + +/* Mouse button definitions */ +#define MOUSE_LEFT 0x01 +#define MOUSE_RIGHT 0x02 +#define MOUSE_MIDDLE 0x04 + +static const struct { + const char *name; + u8 value; +} mouse_button_names[] = { + { "left", MOUSE_LEFT }, + { "right", MOUSE_RIGHT }, + { "middle", MOUSE_MIDDLE }, +}; + +struct zotac_device_info { + u64 device_id; + u16 vid; + u16 pid; + u8 num_led_zones; + struct { + u8 major; + u8 mid; + u8 minor; + u16 revision; + } fw_version; + struct { + u8 major; + u8 mid; + u8 minor; + } hw_version; +}; + +static u16 zotac_calc_crc(u8 *data) +{ + const int payload_end = 0x3D; + const int payload_start = 4; + const u16 crc_seed = 0; + u16 crc = crc_seed; + u32 h1, h2, h3, h4; + int i; + + for (i = payload_start; i <= payload_end; i++) { + h1 = (u32)((crc ^ data[i]) & 0xFF); + h2 = h1 & 0x0F; + h3 = (h2 << 4) ^ h1; + h4 = h3 >> 4; + + crc = (u16)((((((h3 << 1) ^ h4) << 4) ^ h2) << 3) ^ h4 ^ + (crc >> 8)); + } + + return crc; +} + +static int zotac_send_command_and_get_response_usb(struct hid_device *hdev, + u8 *send_buf, int send_len, + u8 *recv_buf, int recv_len) +{ + struct usb_device *udev = + interface_to_usbdev(to_usb_interface(hdev->dev.parent)); + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + const int polling_interval_ms = 50; + int actual_length = 0; + + unsigned int pipe_in, pipe_out; + int ret; + + if (!intf || !udev) { + hid_err(hdev, "Failed to get USB interface or device\n"); + return -ENODEV; + } + + pipe_out = usb_sndintpipe(udev, 0x05); + pipe_in = usb_rcvintpipe(udev, 0x84); + + ret = usb_interrupt_msg(udev, pipe_out, send_buf, send_len, + &actual_length, 1000); + if (ret < 0) { + hid_err(hdev, "Failed to send USB command: %d\n", ret); + return ret; + } + + memset(recv_buf, 0, recv_len); + ret = usb_interrupt_msg(udev, pipe_in, recv_buf, recv_len, + &actual_length, polling_interval_ms); + if (ret == 0 && actual_length > 0) { + return actual_length; + } + + hid_err(hdev, "Timeout waiting for USB response\n"); + return -ETIMEDOUT; +} + +static int zotac_send_command_raw(struct zotac_device *zotac, u8 cmd_code, + u8 setting, const u8 *data, size_t data_len, + u8 *response_buffer, size_t *response_len) +{ + bool is_cmd = (cmd_code == CMD_SET_RGB || cmd_code == CMD_GET_RGB); + int reply_len, result = -EIO; + + struct zotac_cfg_data *cfg; + size_t copy_len; + u8 *buffer; + u16 crc; + + if (!zotac->cfg_data || !zotac->hdev) + return -ENODEV; + + cfg = zotac->cfg_data; + + buffer = kzalloc(REPORT_SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + if (mutex_lock_interruptible(&cfg->command_mutex)) { + kfree(buffer); + return -EINTR; + } + + memset(buffer, 0, REPORT_SIZE); + buffer[HEADER_TAG_POS] = HEADER_TAG; + buffer[RESERVED_POS] = 0x00; + buffer[SEQUENCE_POS] = cfg->sequence_num; + buffer[PAYLOADSIZE_POS] = PAYLOAD_SIZE; + buffer[COMMAND_POS] = cmd_code; + + if (is_cmd) { + buffer[SETTING_POS] = setting; + + if (data && data_len > 0) { + copy_len = min_t(size_t, data_len, PAYLOAD_SIZE - 2); + memcpy(&buffer[VALUE_POS], data, copy_len); + } + } else { + if (data && data_len > 0) { + copy_len = min_t(size_t, data_len, PAYLOAD_SIZE - 1); + memcpy(&buffer[SETTING_POS], data, copy_len); + } else { + buffer[SETTING_POS] = setting; + } + } + + crc = zotac_calc_crc(buffer); + buffer[CRC_H_POS] = (crc >> 8) & 0xFF; + buffer[CRC_L_POS] = crc & 0xFF; + + reply_len = zotac_send_command_and_get_response_usb( + zotac->hdev, buffer, REPORT_SIZE, response_buffer, REPORT_SIZE); + + if (reply_len > 0) { + if (response_buffer[COMMAND_POS] == cmd_code) { + *response_len = reply_len; + cfg->sequence_num = (cfg->sequence_num + 1) & 0xFF; + result = 0; + } else { + hid_err(zotac->hdev, + "Command mismatch in response: expected 0x%02x, got 0x%02x\n", + cmd_code, response_buffer[COMMAND_POS]); + result = -EIO; + } + } else { + hid_err(zotac->hdev, + "No response received for command 0x%02x: %d\n", + cmd_code, reply_len); + result = reply_len < 0 ? reply_len : -EIO; + } + + mutex_unlock(&cfg->command_mutex); + kfree(buffer); + + return result; +} + +int zotac_send_get_command(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *req_data, size_t req_data_len, + u8 *output_data, size_t *output_len) +{ + bool is_status_offset = + (cmd_code == CMD_SET_RGB || cmd_code == CMD_SET_BUTTON_MAPPING); + size_t response_size = REPORT_SIZE, available, to_copy; + int data_offset, ret; + u8 *response_buffer; + + response_buffer = kzalloc(REPORT_SIZE, GFP_KERNEL); + if (!response_buffer) + return -ENOMEM; + + ret = zotac_send_command_raw(zotac, cmd_code, setting, req_data, + req_data_len, response_buffer, + &response_size); + if (ret < 0) { + kfree(response_buffer); + return ret; + } + + if (response_size <= COMMAND_POS || + response_buffer[COMMAND_POS] != cmd_code) { + kfree(response_buffer); + return -EIO; + } + + data_offset = is_status_offset ? 7 : 5; + + if (output_data && output_len && *output_len > 0 && + response_size > data_offset) { + available = response_size - data_offset; + to_copy = min_t(size_t, available, *output_len); + + memcpy(output_data, &response_buffer[data_offset], to_copy); + *output_len = to_copy; + } + + kfree(response_buffer); + return 0; +} + +int zotac_send_set_command(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *data, size_t data_len) +{ + bool is_status_offset = + (cmd_code == CMD_SET_RGB || cmd_code == CMD_SET_BUTTON_MAPPING); + size_t response_size = REPORT_SIZE; + + int ret, status_offset; + u8 *response_buffer; + + response_buffer = kzalloc(REPORT_SIZE, GFP_KERNEL); + if (!response_buffer) { + hid_err(zotac->hdev, + "SET_COMMAND: Failed to allocate response buffer"); + return -ENOMEM; + } + + ret = zotac_send_command_raw(zotac, cmd_code, setting, data, data_len, + response_buffer, &response_size); + + if (ret < 0) { + hid_err(zotac->hdev, + "SET_COMMAND: Command failed with error %d", ret); + kfree(response_buffer); + return ret; + } + + if (response_size <= COMMAND_POS || + response_buffer[COMMAND_POS] != cmd_code) { + hid_err(zotac->hdev, + "SET_COMMAND: Invalid response - size=%zu, cmd=0x%02x", + response_size, response_buffer[COMMAND_POS]); + kfree(response_buffer); + return -EIO; + } + + status_offset = is_status_offset ? 6 : 5; + + if (response_size > status_offset) { + if (response_buffer[status_offset] != 0) { + hid_err(zotac->hdev, + "SET_COMMAND: Command rejected by device, status=0x%02x", + response_buffer[status_offset]); + kfree(response_buffer); + return -EIO; + } + } + + kfree(response_buffer); + return 0; +} + +int zotac_send_get_byte(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *req_data, size_t req_data_len) +{ + size_t output_len = 1; + u8 output_data = 0; + int ret; + + ret = zotac_send_get_command(zotac, cmd_code, setting, req_data, + req_data_len, &output_data, &output_len); + if (ret < 0) + return ret; + + if (output_len < 1) + return -EIO; + + return output_data; +} + +static int zotac_get_device_info(struct zotac_device *zotac, + struct zotac_device_info *info) +{ + u8 data[21]; + size_t data_len = sizeof(data); + int ret; + + if (!zotac || !info) + return -EINVAL; + + ret = zotac_send_get_command(zotac, CMD_GET_DEVICE_INFO, 0, NULL, 0, + data, &data_len); + if (ret < 0) + return ret; + + if (data_len < 20) { + dev_err(&zotac->hdev->dev, + "Incomplete device info received: %zu bytes\n", + data_len); + return -EIO; + } + + info->device_id = ((u64)data[0] | ((u64)data[1] << 8) | + ((u64)data[2] << 16) | ((u64)data[3] << 24) | + ((u64)data[4] << 32) | ((u64)data[5] << 40) | + ((u64)data[6] << 48) | ((u64)data[7] << 56)); + + info->vid = data[8] | (data[9] << 8); + info->pid = data[10] | (data[11] << 8); + + info->num_led_zones = data[12]; + + info->fw_version.major = data[13]; + info->fw_version.mid = data[14]; + info->fw_version.minor = data[15]; + info->fw_version.revision = data[19] | (data[20] << 8); + + info->hw_version.major = data[16]; + info->hw_version.mid = data[17]; + info->hw_version.minor = data[18]; + + return 0; +} + +static void zotac_log_device_info(struct zotac_device *zotac) +{ + struct zotac_device_info info; + int ret; + + ret = zotac_get_device_info(zotac, &info); + if (ret < 0) { + dev_err(&zotac->hdev->dev, "Failed to get device info: %d\n", + ret); + return; + } + + dev_info(&zotac->hdev->dev, + "Device Info:\n" + " Device ID: %016llx\n" + " VID/PID: %04x:%04x\n" + " LED Zones: %u\n" + " Firmware: %u.%u.%u (revision %u)\n" + " Hardware: %u.%u.%u\n", + info.device_id, info.vid, info.pid, info.num_led_zones, + info.fw_version.major, info.fw_version.mid, + info.fw_version.minor, info.fw_version.revision, + info.hw_version.major, info.hw_version.mid, + info.hw_version.minor); +} + +static const struct button_mapping_entry *find_button_by_name(const char *name) +{ + int i; + for (i = 0; button_map[i].name != NULL; i++) { + if (strcmp(button_map[i].name, name) == 0) + return &button_map[i]; + } + return NULL; +} + +static bool is_button_in_mapping(u8 *mapping_bytes, + const struct button_mapping_entry *button) +{ + return (mapping_bytes[button->byte_index] & button->bit_mask) != 0; +} + +static void add_button_to_mapping(u8 *mapping_bytes, + const struct button_mapping_entry *button) +{ + mapping_bytes[button->byte_index] |= button->bit_mask; +} + +static int zotac_get_button_mapping(struct zotac_device *zotac, u8 button_id) +{ + if (!zotac || !zotac->cfg_data || button_id > BUTTON_MAX || + button_id == 0) + return -EINVAL; + return 0; +} + +static int zotac_set_button_mapping(struct zotac_device *zotac, u8 button_id) +{ + struct button_mapping *mapping = + &zotac->cfg_data->button_mappings[button_id]; + u8 data[BTN_MAP_RESPONSE_MIN_SIZE] = { 0 }; + u8 *gamepad_bytes = (u8 *)&mapping->target_gamepad_buttons; + + /* Source button goes at offset 0 */ + data[BTN_MAP_SOURCE_IDX] = button_id; + + /* Controller button mappings */ + data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_DPAD_STICK_IDX] = + gamepad_bytes[GAMEPAD_DPAD_STICK_IDX]; + data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_FACE_BUMPER_IDX] = + gamepad_bytes[GAMEPAD_FACE_BUMPER_IDX]; + data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_TRIGGER_IDX] = + gamepad_bytes[GAMEPAD_TRIGGER_IDX]; + data[BTN_MAP_GAMEPAD_START_IDX + GAMEPAD_RESERVED_IDX] = + gamepad_bytes[GAMEPAD_RESERVED_IDX]; + + data[BTN_MAP_MODIFIER_IDX] = mapping->target_modifier_keys; + + memcpy(&data[BTN_MAP_KEYBOARD_START_IDX], mapping->target_keyboard_keys, + BTN_MAP_KEYBOARD_SIZE); + + data[BTN_MAP_MOUSE_IDX] = mapping->target_mouse_buttons; + + return zotac_send_set_command(zotac, CMD_SET_BUTTON_MAPPING, 0, data, + BTN_MAP_RESPONSE_MIN_SIZE); +} + +static void zotac_modifier_value_to_names(u8 modifiers, char *buf, + size_t buf_size) +{ + bool first = true; + int i, pos = 0; + + if (modifiers == 0) { + strscpy(buf, "none", buf_size); + return; + } + + for (i = 0; i < ARRAY_SIZE(modifier_names); i++) { + if (modifier_names[i].value != MOD_NONE && + (modifiers & modifier_names[i].value)) { + if (!first) + pos += scnprintf(buf + pos, buf_size - pos, + " "); + pos += scnprintf(buf + pos, buf_size - pos, "%s", + modifier_names[i].name); + first = false; + + if (pos >= buf_size - 1) + break; + } + } +} + +static ssize_t gamepad_show(struct device *dev, struct device_attribute *attr, + char *buf, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + bool found = false; + char *p = buf; + + u8 *mapping_bytes; + int i; + + if (zotac_get_button_mapping(&zotac, button_id) < 0) + return -EIO; + + mapping_bytes = (u8 *)&mapping->target_gamepad_buttons; + + for (i = 0; button_map[i].name != NULL; i++) { + if (is_button_in_mapping(mapping_bytes, &button_map[i])) { + p += sprintf(p, "%s ", button_map[i].name); + found = true; + } + } + + if (found) { + *(p - 1) = '\n'; + } else { + p += sprintf(p, "none\n"); + } + + return p - buf; +} + +static ssize_t gamepad_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + u8 *mapping_bytes = (u8 *)&mapping->target_gamepad_buttons; + char *buffer, *token, *cursor; + bool any_valid = false; + + /* Make a copy of the input buffer for tokenization */ + buffer = kstrndup(buf, count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memset(mapping_bytes, 0, 4); + + cursor = buffer; + while ((token = strsep(&cursor, " \t\n")) != NULL) { + if (*token == '\0') + continue; + + if (strcmp(token, "none") == 0) { + any_valid = true; + break; + } + + const struct button_mapping_entry *button = + find_button_by_name(token); + if (button) { + add_button_to_mapping(mapping_bytes, button); + any_valid = true; + } + } + + kfree(buffer); + + if (!any_valid) + return -EINVAL; + + return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count; +} + +static ssize_t modifier_show(struct device *dev, struct device_attribute *attr, + char *buf, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + u8 modifiers; + + modifiers = mapping->target_modifier_keys; + /* Leave room for newline and null */ + zotac_modifier_value_to_names(modifiers, buf, PAGE_SIZE - 2); + strcat(buf, "\n"); + return strlen(buf); +} + +static ssize_t modifier_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + char *buffer, *token, *cursor; + u8 new_modifiers = 0; + bool any_valid = false; + + buffer = kstrndup(buf, count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + cursor = buffer; + while ((token = strsep(&cursor, " \t\n")) != NULL) { + if (*token == '\0') + continue; + + if (strcmp(token, "none") == 0) { + any_valid = true; + new_modifiers = 0; + break; + } + + int i; + for (i = 0; i < ARRAY_SIZE(modifier_names); i++) { + if (strcmp(token, modifier_names[i].name) == 0) { + new_modifiers |= modifier_names[i].value; + any_valid = true; + break; + } + } + } + + kfree(buffer); + + if (!any_valid) + return -EINVAL; + + mapping->target_modifier_keys = new_modifiers; + + return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count; +} + +static u8 find_key_code_by_name(const char *name) +{ + int i; + for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) { + if (strcmp(keyboard_keys[i].name, name) == 0) + return keyboard_keys[i].keycode; + } + return 0; /* Return "none" if not found */ +} + +static const char *find_key_name_by_code(u8 code) +{ + int i; + for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) { + if (keyboard_keys[i].keycode == code) + return keyboard_keys[i].name; + } + return "none"; +} + +static ssize_t keyboard_keys_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + bool any_key = false; + char *p = buf; + int i; + + if (zotac_get_button_mapping(&zotac, button_id) < 0) + return -EIO; + + /* Check each key code in the mapping */ + for (i = 0; i < BTN_MAP_KEYBOARD_SIZE; i++) { + u8 keycode = mapping->target_keyboard_keys[i]; + if (keycode != 0) { + p += sprintf(p, "%s ", find_key_name_by_code(keycode)); + any_key = true; + } + } + + if (any_key) { + *(p - 1) = '\n'; + } else { + p += sprintf(p, "none\n"); + } + + return p - buf; +} + +static ssize_t keyboard_keys_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + u8 new_keys[BTN_MAP_KEYBOARD_SIZE] = { 0 }; + bool any_valid = false; + int key_count = 0; + + char *buffer, *token, *cursor; + + buffer = kstrndup(buf, count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + cursor = buffer; + while ((token = strsep(&cursor, " \t\n")) != NULL) { + if (*token == '\0') + continue; + + if (strcmp(token, "none") == 0) { + any_valid = true; + key_count = 0; /* Clear all keys */ + break; + } + + if (key_count < BTN_MAP_KEYBOARD_SIZE) { + u8 keycode = find_key_code_by_name(token); + if (keycode != 0) { + new_keys[key_count++] = keycode; + any_valid = true; + } + } + } + + kfree(buffer); + + if (!any_valid) + return -EINVAL; + + memcpy(mapping->target_keyboard_keys, new_keys, BTN_MAP_KEYBOARD_SIZE); + + return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count; +} + +static ssize_t keyboard_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(keyboard_keys); i++) { + if (keyboard_keys[i].keycode != 0x00) { + int len = sprintf(p, "%s ", keyboard_keys[i].name); + p += len; + + if (p - buf > PAGE_SIZE - 32) { + /* Add an indication that the list was truncated */ + p += sprintf(p, "..."); + break; + } + } + } + + if (p > buf) + *(p - 1) = '\n'; + else + *p++ = '\n'; + + return p - buf; +} +static DEVICE_ATTR_RO_NAMED(keyboard_list, "keyboard_list"); + +static ssize_t mouse_buttons_show(struct device *dev, + struct device_attribute *attr, char *buf, + u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + u8 mouse_buttons = mapping->target_mouse_buttons; + bool found = false; + char *p = buf; + int i; + + if (zotac_get_button_mapping(&zotac, button_id) < 0) + return -EIO; + + for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) { + if (mouse_buttons & mouse_button_names[i].value) { + p += sprintf(p, "%s ", mouse_button_names[i].name); + found = true; + } + } + + if (found) { + *(p - 1) = '\n'; + } else { + p += sprintf(p, "none\n"); + } + + return p - buf; +} + +static ssize_t mouse_buttons_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, u8 button_id) +{ + struct button_mapping *mapping = + &zotac.cfg_data->button_mappings[button_id]; + char *buffer, *token, *cursor; + u8 new_mouse_buttons = 0; + bool any_valid = false; + + buffer = kstrndup(buf, count, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + cursor = buffer; + while ((token = strsep(&cursor, " \t\n")) != NULL) { + if (*token == '\0') + continue; + + if (strcmp(token, "none") == 0) { + any_valid = true; + new_mouse_buttons = 0; + break; + } + + /* Find the button value */ + int i; + for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) { + if (strcmp(token, mouse_button_names[i].name) == 0) { + new_mouse_buttons |= + mouse_button_names[i].value; + any_valid = true; + break; + } + } + } + + kfree(buffer); + + if (!any_valid) + return -EINVAL; + + mapping->target_mouse_buttons = new_mouse_buttons; + + return zotac_set_button_mapping(&zotac, button_id) ? -EIO : count; +} + +static ssize_t mouse_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(mouse_button_names); i++) { + p += sprintf(p, "%s ", mouse_button_names[i].name); + } + + if (p > buf) + *(p - 1) = '\n'; + else + *p++ = '\n'; + + return p - buf; +} +static DEVICE_ATTR_RO_NAMED(mouse_list, "mouse_list"); + +static ssize_t modifier_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(modifier_names); i++) { + p += sprintf(p, "%s ", modifier_names[i].name); + } + + if (p > buf) + *(p - 1) = '\n'; + else + *p++ = '\n'; + + return p - buf; +} +static DEVICE_ATTR_RO_NAMED(modifier_list, "modifier_list"); + +/* List attributes for showing available options */ +static ssize_t gamepad_list_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + char *p = buf; + int i; + + for (i = 0; i < ARRAY_SIZE(gamepad_button_names); i++) { + if (gamepad_button_names[i].id != BUTTON_NONE) + p += sprintf(p, "%s ", gamepad_button_names[i].name); + } + + if (p > buf) + *(p - 1) = '\n'; + else + *p++ = '\n'; + + return p - buf; +} + +static ssize_t gamepad_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", MAX_GAMEPAD_BUTTONS); +} + +static ssize_t keyboard_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", MAX_KEYBOARD_KEYS); +} + +static ssize_t mouse_max_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", MAX_MOUSE_BUTTONS); +} + +static DEVICE_ATTR_RO_NAMED(gamepad_list, "gamepad_list"); +static DEVICE_ATTR_RO_NAMED(gamepad_max, "gamepad_max"); +static DEVICE_ATTR_RO_NAMED(keyboard_max, "keyboard_max"); +static DEVICE_ATTR_RO_NAMED(mouse_max, "mouse_max"); + +#define DEFINE_BUTTON_REMAP_ATTRS(btn_name, btn_id) \ + static ssize_t btn_name##_gamepad_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return gamepad_show(dev, attr, buf, btn_id); \ + } \ + \ + static ssize_t btn_name##_gamepad_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return gamepad_store(dev, attr, buf, count, btn_id); \ + } \ + \ + static ssize_t btn_name##_modifier_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return modifier_show(dev, attr, buf, btn_id); \ + } \ + \ + static ssize_t btn_name##_modifier_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return modifier_store(dev, attr, buf, count, btn_id); \ + } \ + \ + static ssize_t btn_name##_keyboard_keys_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return keyboard_keys_show(dev, attr, buf, btn_id); \ + } \ + \ + static ssize_t btn_name##_keyboard_keys_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return keyboard_keys_store(dev, attr, buf, count, btn_id); \ + } \ + \ + static ssize_t btn_name##_mouse_buttons_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return mouse_buttons_show(dev, attr, buf, btn_id); \ + } \ + \ + static ssize_t btn_name##_mouse_buttons_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return mouse_buttons_store(dev, attr, buf, count, btn_id); \ + } \ + \ + static DEVICE_ATTR_RW_NAMED(btn_name##_gamepad, "gamepad"); \ + static DEVICE_ATTR_RW_NAMED(btn_name##_modifier, "modifier"); \ + static DEVICE_ATTR_RW_NAMED(btn_name##_keyboard_keys, "keyboard"); \ + static DEVICE_ATTR_RW_NAMED(btn_name##_mouse_buttons, "mouse"); + +/* Create all button attribute groups */ +DEFINE_BUTTON_REMAP_ATTRS(btn_a, BUTTON_A); +DEFINE_BUTTON_REMAP_ATTRS(btn_b, BUTTON_B); +DEFINE_BUTTON_REMAP_ATTRS(btn_x, BUTTON_X); +DEFINE_BUTTON_REMAP_ATTRS(btn_y, BUTTON_Y); +DEFINE_BUTTON_REMAP_ATTRS(btn_lb, BUTTON_LB); +DEFINE_BUTTON_REMAP_ATTRS(btn_rb, BUTTON_RB); +DEFINE_BUTTON_REMAP_ATTRS(btn_lt, BUTTON_LT); +DEFINE_BUTTON_REMAP_ATTRS(btn_rt, BUTTON_RT); +DEFINE_BUTTON_REMAP_ATTRS(btn_ls, BUTTON_LS); +DEFINE_BUTTON_REMAP_ATTRS(btn_rs, BUTTON_RS); +DEFINE_BUTTON_REMAP_ATTRS(dpad_up, BUTTON_DPAD_UP); +DEFINE_BUTTON_REMAP_ATTRS(dpad_down, BUTTON_DPAD_DOWN); +DEFINE_BUTTON_REMAP_ATTRS(dpad_left, BUTTON_DPAD_LEFT); +DEFINE_BUTTON_REMAP_ATTRS(dpad_right, BUTTON_DPAD_RIGHT); +DEFINE_BUTTON_REMAP_ATTRS(btn_m1, BUTTON_M1); +DEFINE_BUTTON_REMAP_ATTRS(btn_m2, BUTTON_M2); + +static int zotac_get_button_turbo(struct zotac_device *zotac) +{ + size_t data_len = 1; + u8 turbo_byte; + int ret; + + ret = zotac_send_get_command(zotac, CMD_GET_BUTTON_TURBO, 0, NULL, 0, + &turbo_byte, &data_len); + if (ret < 0) + return ret; + + if (data_len < 1) + return -EIO; + + zotac->cfg_data->button_turbo = turbo_byte; + return 0; +} + +static int zotac_set_button_turbo(struct zotac_device *zotac, u8 turbo_byte) +{ + int ret; + + ret = zotac_send_set_command(zotac, CMD_SET_BUTTON_TURBO, 0, + &turbo_byte, 1); + if (ret < 0) + return ret; + + zotac->cfg_data->button_turbo = turbo_byte; + return 0; +} + +static ssize_t button_turbo_show(struct device *dev, + struct device_attribute *attr, char *buf, + int btn_pos) +{ + u8 turbo_val; + int ret; + + ret = zotac_get_button_turbo(&zotac); + if (ret < 0) + return ret; + + turbo_val = (zotac.cfg_data->button_turbo >> btn_pos) & 0x01; + return sprintf(buf, "%d\n", turbo_val); +} + +static ssize_t button_turbo_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, int btn_pos) +{ + u8 turbo_byte; + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val != 0 && val != 1) + return -EINVAL; + + ret = zotac_get_button_turbo(&zotac); + if (ret < 0) + return ret; + + turbo_byte = zotac.cfg_data->button_turbo; + + if (val) + turbo_byte |= (1 << btn_pos); + else + turbo_byte &= ~(1 << btn_pos); + + ret = zotac_set_button_turbo(&zotac, turbo_byte); + if (ret < 0) + return ret; + + return count; +} + +#define DEFINE_BUTTON_TURBO_ATTRS(btn_name, btn_pos) \ + static ssize_t btn_##btn_name##_turbo_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return button_turbo_show(dev, attr, buf, btn_pos); \ + } \ + \ + static ssize_t btn_##btn_name##_turbo_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return button_turbo_store(dev, attr, buf, count, btn_pos); \ + } \ + static DEVICE_ATTR_RW_NAMED(btn_##btn_name##_turbo, "turbo"); + +DEFINE_BUTTON_TURBO_ATTRS(a, A_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(b, B_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(x, X_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(y, Y_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(lb, LB_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(rb, RB_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(lt, LT_BTN_POS); +DEFINE_BUTTON_TURBO_ATTRS(rt, RT_BTN_POS); + +/* Create attribute groups for buttons with/without turbo */ +#define DEFINE_BUTTON_TURBO_GROUP(btn_name, btn_id) \ + static struct attribute *btn_name##_main_attrs[] = { \ + &dev_attr_##btn_name##_turbo.attr, NULL \ + }; \ + static const struct attribute_group btn_name##_main_group = { \ + .attrs = btn_name##_main_attrs, \ + }; + +#define DEFINE_BUTTON_NO_TURBO_GROUP(btn_name, btn_id) \ + static struct attribute *btn_name##_main_attrs[] = { NULL }; \ + static const struct attribute_group btn_name##_main_group = { \ + .attrs = btn_name##_main_attrs, \ + }; + +/* Define remap subgroups */ +#define DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id) \ + static struct attribute *btn_name##_remap_attrs[] = { \ + &dev_attr_##btn_name##_gamepad.attr, \ + &dev_attr_##btn_name##_modifier.attr, \ + &dev_attr_##btn_name##_keyboard_keys.attr, \ + &dev_attr_##btn_name##_mouse_buttons.attr, \ + &dev_attr_gamepad_list.attr, \ + &dev_attr_gamepad_max.attr, \ + &dev_attr_modifier_list.attr, \ + &dev_attr_keyboard_list.attr, \ + &dev_attr_keyboard_max.attr, \ + &dev_attr_mouse_list.attr, \ + &dev_attr_mouse_max.attr, \ + NULL \ + }; \ + static const struct attribute_group btn_name##_remap_group = { \ + .name = "remap", \ + .attrs = btn_name##_remap_attrs, \ + }; + +/* Define button directory */ +#define DEFINE_BUTTON_GROUP_WITH_TURBO(btn_name, btn_id) \ + DEFINE_BUTTON_TURBO_GROUP(btn_name, btn_id) \ + DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id) + +#define DEFINE_BUTTON_DIR_WITH_TURBO(btn_name, btn_id) \ + { \ + .name = #btn_name, \ + .button_id = btn_id, \ + .has_turbo = true, \ + .kobj = NULL, \ + .main_group = \ + (struct attribute_group *)&btn_name##_main_group, \ + .remap_group = \ + (struct attribute_group *)&btn_name##_remap_group, \ + } + +#define DEFINE_BUTTON_GROUP_NO_TURBO(btn_name, btn_id) \ + DEFINE_BUTTON_NO_TURBO_GROUP(btn_name, btn_id) \ + DEFINE_BUTTON_REMAP_GROUP(btn_name, btn_id) + +#define DEFINE_BUTTON_DIR_NO_TURBO(btn_name, btn_id) \ + { \ + .name = #btn_name, \ + .button_id = btn_id, \ + .has_turbo = false, \ + .kobj = NULL, \ + .main_group = \ + (struct attribute_group *)&btn_name##_main_group, \ + .remap_group = \ + (struct attribute_group *)&btn_name##_remap_group, \ + } + +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_a, BUTTON_A); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_b, BUTTON_B); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_x, BUTTON_X); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_y, BUTTON_Y); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_lb, BUTTON_LB); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_rb, BUTTON_RB); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_lt, BUTTON_LT); +DEFINE_BUTTON_GROUP_WITH_TURBO(btn_rt, BUTTON_RT); +DEFINE_BUTTON_GROUP_NO_TURBO(btn_ls, BUTTON_LS); +DEFINE_BUTTON_GROUP_NO_TURBO(btn_rs, BUTTON_RS); +DEFINE_BUTTON_GROUP_NO_TURBO(dpad_up, BUTTON_DPAD_UP); +DEFINE_BUTTON_GROUP_NO_TURBO(dpad_down, BUTTON_DPAD_DOWN); +DEFINE_BUTTON_GROUP_NO_TURBO(dpad_left, BUTTON_DPAD_LEFT); +DEFINE_BUTTON_GROUP_NO_TURBO(dpad_right, BUTTON_DPAD_RIGHT); +DEFINE_BUTTON_GROUP_NO_TURBO(btn_m1, BUTTON_M1); +DEFINE_BUTTON_GROUP_NO_TURBO(btn_m2, BUTTON_M2); + +/* Define all button directories */ +static struct button_directory button_dirs[] = { + DEFINE_BUTTON_DIR_WITH_TURBO(btn_a, BUTTON_A), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_b, BUTTON_B), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_x, BUTTON_X), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_y, BUTTON_Y), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_lb, BUTTON_LB), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_rb, BUTTON_RB), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_lt, BUTTON_LT), + DEFINE_BUTTON_DIR_WITH_TURBO(btn_rt, BUTTON_RT), + DEFINE_BUTTON_DIR_NO_TURBO(btn_ls, BUTTON_LS), + DEFINE_BUTTON_DIR_NO_TURBO(btn_rs, BUTTON_RS), + DEFINE_BUTTON_DIR_NO_TURBO(dpad_up, BUTTON_DPAD_UP), + DEFINE_BUTTON_DIR_NO_TURBO(dpad_down, BUTTON_DPAD_DOWN), + DEFINE_BUTTON_DIR_NO_TURBO(dpad_left, BUTTON_DPAD_LEFT), + DEFINE_BUTTON_DIR_NO_TURBO(dpad_right, BUTTON_DPAD_RIGHT), + DEFINE_BUTTON_DIR_NO_TURBO(btn_m1, BUTTON_M1), + DEFINE_BUTTON_DIR_NO_TURBO(btn_m2, BUTTON_M2), + { NULL, 0, false, NULL, NULL, NULL } /* Terminator */ +}; + +static int zotac_get_stick_sensitivity(struct zotac_device *zotac, + int stick_num, u8 *output_values) +{ + u8 request_data = stick_num; + u8 temp_values[STICK_SENSITIVITY_SIZE]; + size_t output_len = STICK_SENSITIVITY_SIZE; + int i, ret; + + ret = zotac_send_get_command(zotac, CMD_GET_STICK_SENSITIVITY, 0, + &request_data, 1, temp_values, + &output_len); + + if (ret == 0 && output_len == STICK_SENSITIVITY_SIZE) { + /* Scale from percentage (0-100) to device values (0-255) */ + for (i = 0; i < STICK_SENSITIVITY_SIZE; i++) { + output_values[i] = temp_values[i] * 255 / 100; + } + } + + return ret; +} + +static struct stick_sensitivity * +get_sensitivity_for_stick(struct zotac_device *zotac, int stick_num) +{ + if (stick_num == STICK_LEFT) + return &zotac->cfg_data->left_stick_sensitivity; + else + return &zotac->cfg_data->right_stick_sensitivity; +} + +static ssize_t curve_response_show(struct device *dev, + struct device_attribute *attr, char *buf, + int stick_num, int point_index) +{ + struct stick_sensitivity *sensitivity = + get_sensitivity_for_stick(&zotac, stick_num); + int base_idx = point_index * 2; + + // Dev input is 0-255, but it outputs 0-100. So we store as 0-255 + int x_pct = sensitivity->values[base_idx] * 100 / 255; + int y_pct = sensitivity->values[base_idx + 1] * 100 / 255; + + return sprintf(buf, "%d %d\n", x_pct, y_pct); +} + +static ssize_t curve_response_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + int stick_num, int point_index) +{ + struct stick_sensitivity *sensitivity = + get_sensitivity_for_stick(&zotac, stick_num); + u8 data[STICK_SENSITIVITY_SIZE + 1]; // +1 for stick ID + int base_idx = point_index * 2; + + int x_pct, y_pct; + int ret; + + ret = sscanf(buf, "%d %d", &x_pct, &y_pct); + if (ret != 2) + return -EINVAL; + + x_pct = clamp_val(x_pct, 0, 100); + y_pct = clamp_val(y_pct, 0, 100); + + sensitivity->values[base_idx] = x_pct * 255 / 100; + sensitivity->values[base_idx + 1] = y_pct * 255 / 100; + + data[STICK_SENSITIVITY_NUM_IDX] = stick_num; + + memcpy(&data[STICK_SENSITIVITY_DATA_IDX], sensitivity->values, + STICK_SENSITIVITY_SIZE); + + ret = zotac_send_set_command(&zotac, CMD_SET_STICK_SENSITIVITY, 0, data, + sizeof(data)); + if (ret < 0) + return ret; + + return count; +} + +#define DEFINE_CURVE_RESPONSE_ATTRS(stick_name, stick_num, point_num) \ + static ssize_t stick_##stick_name##_curve_response_##point_num##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return curve_response_show(dev, attr, buf, stick_num, \ + point_num - 1); \ + } \ + \ + static ssize_t stick_##stick_name##_curve_response_##point_num##_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return curve_response_store(dev, attr, buf, count, stick_num, \ + point_num - 1); \ + } \ + static DEVICE_ATTR_RW_NAMED( \ + stick_##stick_name##_curve_response_##point_num, \ + "curve_response_pct_" #point_num); + +DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 1) +DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 2) +DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 3) +DEFINE_CURVE_RESPONSE_ATTRS(xy_left, STICK_LEFT, 4) + +DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 1) +DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 2) +DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 3) +DEFINE_CURVE_RESPONSE_ATTRS(xy_right, STICK_RIGHT, 4) + +static ssize_t axis_xyz_deadzone_index_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "inner outer\n"); +} +static DEVICE_ATTR_RO_NAMED(axis_xyz_deadzone_index, "deadzone_index"); + +static ssize_t axis_xyz_deadzone_show(struct device *dev, + struct device_attribute *attr, char *buf, + struct deadzone *dz) +{ + return sprintf(buf, "%d %d\n", dz->inner, dz->outer); +} + +static int zotac_apply_deadzones(struct zotac_device *zotac, + struct deadzone *left_dz, + struct deadzone *right_dz, u8 cmd_code) +{ + u8 data[DZ_RESPONSE_SIZE]; + int ret; + + data[DZ_LEFT_INNER_IDX] = left_dz->inner; + data[DZ_LEFT_OUTER_IDX] = left_dz->outer; + data[DZ_RIGHT_INNER_IDX] = right_dz->inner; + data[DZ_RIGHT_OUTER_IDX] = right_dz->outer; + + ret = zotac_send_set_command(zotac, cmd_code, 0, data, sizeof(data)); + + return ret; +} + +static ssize_t axis_xyz_deadzone_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count, + struct deadzone *dz) +{ + struct zotac_cfg_data *cfg = zotac.cfg_data; + int inner, outer; + u8 cmd_code; + int ret; + + ret = sscanf(buf, "%d %d", &inner, &outer); + if (ret != 2) + return -EINVAL; + + if (inner < 0 || inner > 100 || outer < 0 || outer > 100) + return -EINVAL; + + dz->inner = inner; + dz->outer = outer; + + /* Determine which command to use based on which deadzone is being modified */ + if (dz == &cfg->ls_dz || dz == &cfg->rs_dz) { + cmd_code = CMD_SET_STICK_DEADZONES; + ret = zotac_apply_deadzones(&zotac, &cfg->ls_dz, &cfg->rs_dz, + cmd_code); + } else { + cmd_code = CMD_SET_TRIGGER_DEADZONES; + ret = zotac_apply_deadzones(&zotac, &cfg->lt_dz, &cfg->rt_dz, + cmd_code); + } + + if (ret < 0) + return ret; + + return count; +} + +#define DEFINE_DEADZONE_HANDLERS(axis_name, dz_field) \ + static ssize_t axis_##axis_name##_deadzone_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return axis_xyz_deadzone_show(dev, attr, buf, \ + &zotac.cfg_data->dz_field); \ + } \ + \ + static ssize_t axis_##axis_name##_deadzone_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return axis_xyz_deadzone_store(dev, attr, buf, count, \ + &zotac.cfg_data->dz_field); \ + } \ + static DEVICE_ATTR_RW_NAMED(axis_##axis_name##_deadzone, "deadzone"); + +#define DEFINE_XY_AXIS_ATTR_GROUP(axis_name) \ + static struct attribute *axis_##axis_name##_attrs[] = { \ + &dev_attr_axis_##axis_name##_deadzone.attr, \ + &dev_attr_stick_##axis_name##_curve_response_1.attr, \ + &dev_attr_stick_##axis_name##_curve_response_2.attr, \ + &dev_attr_stick_##axis_name##_curve_response_3.attr, \ + &dev_attr_stick_##axis_name##_curve_response_4.attr, \ + &dev_attr_axis_xyz_deadzone_index.attr, \ + NULL \ + }; \ + \ + static const struct attribute_group axis_##axis_name##_attr_group = { \ + .name = "axis_" #axis_name, \ + .attrs = axis_##axis_name##_attrs, \ + }; + +#define DEFINE_Z_AXIS_ATTR_GROUP(axis_name) \ + static struct attribute *axis_##axis_name##_attrs[] = { \ + &dev_attr_axis_##axis_name##_deadzone.attr, \ + &dev_attr_axis_xyz_deadzone_index.attr, NULL \ + }; \ + \ + static const struct attribute_group axis_##axis_name##_attr_group = { \ + .name = "axis_" #axis_name, \ + .attrs = axis_##axis_name##_attrs, \ + }; + +DEFINE_DEADZONE_HANDLERS(xy_left, ls_dz); +DEFINE_DEADZONE_HANDLERS(xy_right, rs_dz); +DEFINE_DEADZONE_HANDLERS(z_left, lt_dz); +DEFINE_DEADZONE_HANDLERS(z_right, rt_dz); + +DEFINE_XY_AXIS_ATTR_GROUP(xy_left); +DEFINE_XY_AXIS_ATTR_GROUP(xy_right); +DEFINE_Z_AXIS_ATTR_GROUP(z_left); +DEFINE_Z_AXIS_ATTR_GROUP(z_right); + +static ssize_t vibration_intensity_index_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, + "trigger_left trigger_right rumble_left rumble_right\n"); +} +static DEVICE_ATTR_RO_NAMED(vibration_intensity_index, + "vibration_intensity_index"); + +static ssize_t vibration_intensity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 data[VIB_RESPONSE_SIZE]; + size_t data_len = sizeof(data); + int ret; + + ret = zotac_send_get_command(&zotac, CMD_GET_VIBRATION_STRENGTH, 0, NULL, + 0, data, &data_len); + if (ret < 0) + return ret; + + if (data_len < VIB_RESPONSE_SIZE) { + dev_err(&zotac.hdev->dev, + "Incomplete vibration data received: %zu bytes\n", + data_len); + return -EIO; + } + + return sprintf(buf, "%d %d %d %d\n", data[VIB_LEFT_TRIGGER_IDX], + data[VIB_RIGHT_TRIGGER_IDX], data[VIB_LEFT_RUMBLE_IDX], + data[VIB_RIGHT_RUMBLE_IDX]); +} + +static ssize_t vibration_intensity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u8 data[VIB_RESPONSE_SIZE]; + int lt, rt, lr, rr; + int ret; + + ret = sscanf(buf, "%d %d %d %d", <, &rt, &lr, &rr); + if (ret != 4) + return -EINVAL; + + data[VIB_LEFT_TRIGGER_IDX] = clamp_val(lt, 0, 100); + data[VIB_RIGHT_TRIGGER_IDX] = clamp_val(rt, 0, 100); + data[VIB_LEFT_RUMBLE_IDX] = clamp_val(lr, 0, 100); + data[VIB_RIGHT_RUMBLE_IDX] = clamp_val(rr, 0, 100); + + ret = zotac_send_set_command(&zotac, CMD_SET_VIBRATION_STRENGTH, 0, data, + sizeof(data)); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(vibration_intensity); + +static ssize_t mouse_speed_max_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", MOUSE_SPEED_MAX); +} +static DEVICE_ATTR_RO_NAMED(mouse_speed_max, "mouse_speed_max"); + +static ssize_t mouse_speed_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int speed; + + speed = zotac_send_get_byte(&zotac, CMD_GET_MOUSE_SPEED, 0, NULL, 0); + if (speed < 0) + return speed; + + return sprintf(buf, "%d\n", speed); +} + +static ssize_t mouse_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int speed_val; + u8 speed; + int ret; + + ret = kstrtoint(buf, 10, &speed_val); + if (ret) + return ret; + + if (speed_val < MOUSE_SPEED_MIN || speed_val > MOUSE_SPEED_MAX) + return -EINVAL; + + speed = (u8)speed_val; + ret = zotac_send_set_command(&zotac, CMD_SET_MOUSE_SPEED, 0, &speed, 1); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_RW(mouse_speed); + +static ssize_t motor_test_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + u8 data[MOTOR_TEST_SIZE] = { 0 }; + + int left_trigger, right_trigger, left_rumble, right_rumble; + int ret; + + ret = sscanf(buf, "%d %d %d %d", &left_trigger, &right_trigger, + &left_rumble, &right_rumble); + if (ret != 4) + return -EINVAL; + + left_trigger = clamp_val(left_trigger, 0, 100); + right_trigger = clamp_val(right_trigger, 0, 100); + left_rumble = clamp_val(left_rumble, 0, 100); + right_rumble = clamp_val(right_rumble, 0, 100); + + data[0] = (u8)left_trigger; /* Left Trigger Motor */ + data[1] = (u8)right_trigger; /* Right Trigger Motor */ + data[2] = (u8)left_rumble; /* Left Rumble Motor */ + data[3] = (u8)right_rumble; /* Right Rumble Motor */ + + ret = zotac_send_set_command(&zotac, CMD_MOTOR_TEST, 0, data, + MOTOR_TEST_SIZE); + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(motor_test); + +static ssize_t motor_test_index_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, + "left_trigger right_trigger left_rumble right_rumble\n"); +} +static DEVICE_ATTR_RO_NAMED(motor_test_index, "motor_test_index"); + +static ssize_t profile_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int profile; + + profile = zotac_send_get_byte(&zotac, CMD_GET_PROFILE, 0, NULL, 0); + if (profile < 0) + return profile; + + return sprintf(buf, "%d\n", profile); +} + +static ssize_t profile_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int profile_id, ret; + u8 profile; + + ret = kstrtoint(buf, 10, &profile_id); + if (ret) + return ret; + + if (profile_id > PROFILE_SECONDARY) + return -EINVAL; + + profile = (u8)profile_id; + ret = zotac_send_set_command(&zotac, CMD_SET_PROFILE, 0, &profile, 1); + if (ret) + return ret; + + ret = zotac_cfg_refresh(&zotac); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW_NAMED(profile, "current"); + +static ssize_t profile_count_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + return sprintf(buf, "%d\n", + zotac_send_get_byte(&zotac, CMD_GET_PROFILE_NUM, 0, NULL, + 0)); +} +static DEVICE_ATTR_RO_NAMED(profile_count, "count"); + +/* The device resets and reconnects */ +static ssize_t restore_profile_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val != 1) + return -EINVAL; + + dev_warn(dev, "Restoring profile, the device will reset and reconnect"); + ret = zotac_send_set_command(&zotac, CMD_RESTORE_PROFILE, 0, NULL, 0); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO_NAMED(restore_profile, "restore"); + +static struct attribute *zotac_profile_attrs[] = { + &dev_attr_profile.attr, &dev_attr_profile_count.attr, + &dev_attr_restore_profile.attr, NULL +}; + +static const struct attribute_group zotac_profile_attr_group = { + .name = "profile", + .attrs = zotac_profile_attrs, +}; + +static ssize_t save_config_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int val, ret; + + ret = kstrtoint(buf, 10, &val); + if (ret) + return ret; + + if (val != 1) + return -EINVAL; + + ret = zotac_send_set_command(&zotac, CMD_SAVE_CONFIG, 0, NULL, 0); + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_WO(save_config); + +static ssize_t qam_mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + if (!zotac.gamepad) + return -ENODEV; + + return sprintf(buf, "%i\n", zotac.gamepad->qam_mode); +} + +static ssize_t qam_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + u8 value; + int ret; + + if (!zotac.gamepad) + return -ENODEV; + + ret = kstrtou8(buf, 10, &value); + if (ret) + return ret; + + if (value >= QAM_MODE_LENGTH) + return -EINVAL; + + zotac.gamepad->qam_mode = value; + + return count; +} +DEVICE_ATTR_RW(qam_mode); + +static struct attribute *zotac_root_attrs[] = { + &dev_attr_save_config.attr, + &dev_attr_qam_mode.attr, + &dev_attr_vibration_intensity.attr, + &dev_attr_vibration_intensity_index.attr, + &dev_attr_mouse_speed.attr, + &dev_attr_mouse_speed_max.attr, + &dev_attr_motor_test.attr, + &dev_attr_motor_test_index.attr, + NULL +}; + +static const struct attribute_group zotac_root_attr_group = { + .attrs = zotac_root_attrs, +}; + +static const struct attribute_group *zotac_top_level_attr_groups[] = { + &zotac_profile_attr_group, + &zotac_root_attr_group, + &axis_xy_left_attr_group, + &axis_xy_right_attr_group, + &axis_z_left_attr_group, + &axis_z_right_attr_group, + NULL +}; + +int zotac_register_sysfs(struct zotac_device *zotac) +{ + struct device *dev; + int ret, i; + + if (!zotac || !zotac->hdev) + return -ENODEV; + + dev = &zotac->hdev->dev; + + ret = sysfs_create_groups(&dev->kobj, zotac_top_level_attr_groups); + if (ret) { + dev_err(dev, "Failed to create top-level sysfs groups: %d\n", + ret); + return ret; + } + + for (i = 0; button_dirs[i].name != NULL; i++) { + struct button_directory *btn_dir = &button_dirs[i]; + + /* Create the button directory kobject */ + btn_dir->kobj = + kobject_create_and_add(btn_dir->name, &dev->kobj); + if (!btn_dir->kobj) { + dev_err(dev, "Failed to create kobject for %s\n", + btn_dir->name); + ret = -ENOMEM; + goto cleanup; + } + + /* Add the main attributes to the button directory */ + ret = sysfs_create_group(btn_dir->kobj, btn_dir->main_group); + if (ret) { + dev_err(dev, "Failed to create main group for %s: %d\n", + btn_dir->name, ret); + goto cleanup; + } + + /* Add the remap subgroup to the button directory */ + ret = sysfs_create_group(btn_dir->kobj, btn_dir->remap_group); + if (ret) { + dev_err(dev, + "Failed to create remap group for %s: %d\n", + btn_dir->name, ret); + goto cleanup; + } + } + + return 0; + +cleanup: + /* Clean up on error */ + for (i = 0; button_dirs[i].name != NULL; i++) { + if (button_dirs[i].kobj) { + kobject_put(button_dirs[i].kobj); + button_dirs[i].kobj = NULL; + } + } + sysfs_remove_groups(&dev->kobj, zotac_top_level_attr_groups); + return ret; +} + +void zotac_unregister_sysfs(struct zotac_device *zotac) +{ + int i; + struct device *dev; + + if (!zotac || !zotac->hdev) { + pr_err("Invalid zotac device in unregister_sysfs\n"); + return; + } + + dev = &zotac->hdev->dev; + + /* Remove button directories and their attributes */ + for (i = 0; button_dirs[i].name != NULL; i++) { + if (button_dirs[i].kobj) { + kobject_put(button_dirs[i].kobj); + button_dirs[i].kobj = NULL; + } + } + + sysfs_remove_groups(&dev->kobj, zotac_top_level_attr_groups); +} + +/** + * zotac_cfg_refresh - Refresh all configuration data from the device + * @zotac: The zotac device to refresh + * + * This function queries the device for all current configuration and + * updates the driver's cached values. It should be called during + * initialization and after profile changes or restores. + */ +int zotac_cfg_refresh(struct zotac_device *zotac) +{ + struct zotac_cfg_data *cfg; + u8 data[DZ_RESPONSE_SIZE]; + size_t data_len = sizeof(data); + int ret, i; + + if (!zotac->cfg_data) + return -EINVAL; + + cfg = zotac->cfg_data; + + ret = zotac_send_get_command(zotac, CMD_GET_STICK_DEADZONES, 0, NULL, 0, + data, &data_len); + if (ret == 0 && data_len >= DZ_RESPONSE_SIZE) { + cfg->ls_dz.inner = data[DZ_LEFT_INNER_IDX]; + cfg->ls_dz.outer = data[DZ_LEFT_OUTER_IDX]; + cfg->rs_dz.inner = data[DZ_RIGHT_INNER_IDX]; + cfg->rs_dz.outer = data[DZ_RIGHT_OUTER_IDX]; + } else { + dev_info( + &zotac->hdev->dev, + "Could not retrieve stick deadzone settings, using defaults\n"); + cfg->ls_dz.inner = 0; + cfg->ls_dz.outer = 100; + cfg->rs_dz.inner = 0; + cfg->rs_dz.outer = 100; + } + + ret = zotac_send_get_command(zotac, CMD_GET_TRIGGER_DEADZONES, 0, NULL, + 0, data, &data_len); + if (ret == 0 && data_len >= DZ_RESPONSE_SIZE) { + cfg->lt_dz.inner = data[DZ_LEFT_INNER_IDX]; + cfg->lt_dz.outer = data[DZ_LEFT_OUTER_IDX]; + cfg->rt_dz.inner = data[DZ_RIGHT_INNER_IDX]; + cfg->rt_dz.outer = data[DZ_RIGHT_OUTER_IDX]; + } else { + dev_info( + &zotac->hdev->dev, + "Could not retrieve trigger deadzone settings, using defaults\n"); + cfg->lt_dz.inner = 0; + cfg->lt_dz.outer = 100; + cfg->rt_dz.inner = 0; + cfg->rt_dz.outer = 100; + } + + ret = zotac_get_stick_sensitivity(zotac, STICK_LEFT, + cfg->left_stick_sensitivity.values); + if (ret < 0) { + dev_info( + &zotac->hdev->dev, + "Could not retrieve left stick sensitivity, using defaults\n"); + /* Initialize with linear response: 25%, 50%, 75%, 100% */ + cfg->left_stick_sensitivity.values[0] = 64; /* X1 = 25% */ + cfg->left_stick_sensitivity.values[1] = 64; /* Y1 = 25% */ + cfg->left_stick_sensitivity.values[2] = 128; /* X2 = 50% */ + cfg->left_stick_sensitivity.values[3] = 128; /* Y2 = 50% */ + cfg->left_stick_sensitivity.values[4] = 192; /* X3 = 75% */ + cfg->left_stick_sensitivity.values[5] = 192; /* Y3 = 75% */ + cfg->left_stick_sensitivity.values[6] = 255; /* X4 = 100% */ + cfg->left_stick_sensitivity.values[7] = 255; /* Y4 = 100% */ + } + + ret = zotac_get_stick_sensitivity(zotac, STICK_RIGHT, + cfg->right_stick_sensitivity.values); + if (ret < 0) { + dev_info( + &zotac->hdev->dev, + "Could not retrieve right stick sensitivity, using defaults\n"); + /* Copy the left stick values which may be initialized or linear defaults */ + memcpy(cfg->right_stick_sensitivity.values, + cfg->left_stick_sensitivity.values, + sizeof(cfg->right_stick_sensitivity.values)); + } + + ret = zotac_get_button_turbo(zotac); + if (ret < 0) { + dev_info( + &zotac->hdev->dev, + "Could not retrieve button turbo settings, using defaults\n"); + cfg->button_turbo = 0; /* Default: no turbo buttons */ + } + + for (i = 1; i <= BUTTON_MAX; i++) { + u8 request_data = i; + u8 response_data[BTN_MAP_RESPONSE_MIN_SIZE]; + size_t response_len = sizeof(response_data); + + /* Set default values first */ + cfg->button_mappings[i].target_gamepad_buttons = 0; + cfg->button_mappings[i].target_modifier_keys = 0; + memset(cfg->button_mappings[i].target_keyboard_keys, 0, + MAX_KEYBOARD_KEYS); + cfg->button_mappings[i].target_mouse_buttons = 0; + + ret = zotac_send_get_command(zotac, CMD_GET_BUTTON_MAPPING, 0, + &request_data, 1, response_data, + &response_len); + if (ret == 0 && response_len >= BTN_MAP_RESPONSE_MIN_SIZE) { + memcpy(&cfg->button_mappings[i].target_gamepad_buttons, + &response_data[BTN_MAP_GAMEPAD_START_IDX], + BTN_MAP_GAMEPAD_SIZE); + + cfg->button_mappings[i].target_modifier_keys = + response_data[BTN_MAP_MODIFIER_IDX]; + + memcpy(cfg->button_mappings[i].target_keyboard_keys, + &response_data[BTN_MAP_KEYBOARD_START_IDX], + BTN_MAP_KEYBOARD_SIZE); + + cfg->button_mappings[i].target_mouse_buttons = + response_data[BTN_MAP_MOUSE_IDX]; + } else { + dev_info( + &zotac->hdev->dev, + "Could not retrieve button %d mapping, using defaults\n", + i); + } + } + + return 0; +} + +/** + * zotac_cfg_setup - Allocate and initialize config data structure + * @zotac: The zotac device to set up + * + * This function allocates the config data structure and initializes + * the mutex and sequence number. It should be called once during + * driver initialization. + */ +static int zotac_cfg_setup(struct zotac_device *zotac) +{ + struct zotac_cfg_data *cfg; + + if (!zotac) + return -EINVAL; + + cfg = kzalloc(sizeof(*cfg), GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + mutex_init(&cfg->command_mutex); + cfg->sequence_num = 0; + zotac->cfg_data = cfg; + + zotac_log_device_info(zotac); + + return 0; +} + +/** + * zotac_cfg_init - Initialize the device configuration system + * @zotac: The zotac device to initialize + * + * This function sets up the configuration system and loads the initial + * configuration from the device. + */ +int zotac_cfg_init(struct zotac_device *zotac) +{ + int ret; + + ret = zotac_cfg_setup(zotac); + if (ret < 0) + return ret; + + ret = zotac_cfg_refresh(zotac); + if (ret < 0) { + /* If refresh fails, still keep the structure but log an error */ + dev_err(&zotac->hdev->dev, + "Failed to load initial configuration: %d\n", ret); + } + + return 0; +} + +void zotac_cfg_cleanup(struct zotac_device *zotac) +{ + if (!zotac || !zotac->cfg_data) + return; + + kfree(zotac->cfg_data); + zotac->cfg_data = NULL; +} diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c new file mode 100644 index 000000000000..6f80ea2f9aad --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-core.c @@ -0,0 +1,597 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for ZOTAC Gaming Zone Controller - RGB LED control + * + * Copyright (c) 2025 Luke D. Jones + */ + +#include +#include +#include +#include +#include + +#include "zotac-zone.h" + +#define ZOTAC_VENDOR_ID 0x1ee9 +#define ZOTAC_ALT_VENDOR_ID 0x1e19 +#define ZOTAC_PRODUCT_ID 0x1590 + +#define ZOTAC_DIAL_INTERFACE 1 +#define ZOTAC_REPORT_INTERFACE 2 +#define ZOTAC_COMMAND_INTERFACE 3 + +#define ZOTAC_DIAL_REPORT_ID 0x03 +#define ZOTAC_KBD_REPORT_ID 0x02 +#define ZOTAC_MOUSE_REPORT_ID 0x04 +#define ZOTAC_STATUS_REPORT_ID 0x07 +#define ZOTAC_STATUS2_REPORT_ID 0x08 +#define ZOTAC_STATUS3_REPORT_ID 0x09 + +#define ZOTAC_RIGHT_DIAL_CW_BIT 0 +#define ZOTAC_RIGHT_DIAL_CCW_BIT 1 +#define ZOTAC_LEFT_DIAL_CW_BIT 3 +#define ZOTAC_LEFT_DIAL_CCW_BIT 4 + +#define HID_USAGE_F16 0x6B +#define HID_USAGE_F17 0x6C +#define HID_USAGE_F18 0x6D +#define HID_USAGE_F19 0x6E +#define HID_USAGE_F20 0x6F + +struct zotac_device zotac; + +typedef void (*zotac_report_handler)(struct zotac_device *zotac, u8 *data, + int size); + +struct zotac_report_handler { + u8 report_id; + zotac_report_handler handler; + const char *name; +}; + +/** + * zotac_get_usb_interface - Get the USB interface from a HID device + * @hdev: The HID device + * + * Returns the USB interface if the device is a USB device, NULL otherwise + */ +struct usb_interface *zotac_get_usb_interface(struct hid_device *hdev) +{ + struct usb_interface *intf = NULL; + + if (hid_is_usb(hdev)) + intf = to_usb_interface(hdev->dev.parent); + + return intf; +} + +static int get_interface_num(struct hid_device *hdev) +{ + struct usb_interface *intf = zotac_get_usb_interface(hdev); + + if (intf && intf->cur_altsetting) + return intf->cur_altsetting->desc.bInterfaceNumber; + + return -1; +} + +static void process_dial_wheel_report(struct zotac_device *zotac, u8 *data, + int size) +{ + u8 value; + + if (size < 4 || !zotac->wheel_input) + return; + + value = data[3]; + if (value == 0) + return; + + if (value & BIT(ZOTAC_RIGHT_DIAL_CW_BIT)) + input_report_rel(zotac->wheel_input, REL_WHEEL, 1); + if (value & BIT(ZOTAC_RIGHT_DIAL_CCW_BIT)) + input_report_rel(zotac->wheel_input, REL_WHEEL, -1); + + if (value & BIT(ZOTAC_LEFT_DIAL_CW_BIT)) + input_report_rel(zotac->wheel_input, REL_HWHEEL, 1); + if (value & BIT(ZOTAC_LEFT_DIAL_CCW_BIT)) + input_report_rel(zotac->wheel_input, REL_HWHEEL, -1); + + input_sync(zotac->wheel_input); +} + +static int process_keyboard_report(struct zotac_device *zotac, u8 *data, + int size) +{ + u32 pattern; + enum qam_mode qam_mode; + + if (zotac->gamepad) + qam_mode = zotac->gamepad->qam_mode; + + if (size < 5) + return 0; + + pattern = (data[1] << 16) | (data[2] << 8) | data[3]; + + if (pattern == 0x09006c || pattern == 0x09006d || pattern == 0x080007 || pattern == 0x050063) { + switch (data[3]) { + case 0x63: + switch (qam_mode) { + case QAM_MODE_KEYBOARD: + data[3] = HID_USAGE_F19; + break; + case QAM_MODE_CUSTOM: + zotac_gamepad_send_button(zotac, (int[]){ BTN_TRIGGER_HAPPY6 }, 1); + break; + default: + zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_X }, 2); + break; + } + break; + case 0x6C: + switch (qam_mode) { + case QAM_MODE_KEYBOARD: + data[3] = HID_USAGE_F16; + break; + default: + zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE }, 1); + break; + } + break; + case 0x6D: + switch (qam_mode) { + case QAM_MODE_KEYBOARD: + data[3] = HID_USAGE_F17; + break; + default: + zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_A }, 2); + break; + } + break; + case 0x07: + switch (qam_mode) { + case QAM_MODE_KEYBOARD: + data[3] = HID_USAGE_F18; + break; + case QAM_MODE_CUSTOM: + zotac_gamepad_send_button(zotac, (int[]){ BTN_TRIGGER_HAPPY5 }, 1); + break; + default: + zotac_gamepad_send_button(zotac, (int[]){ BTN_MODE, BTN_B }, 2); + break; + } + break; + } + if (qam_mode) { + memset(data, 0, size); + return 1; + } else { + data[1] = 0; + return 0; + } + } + return 0; +} + +static void process_mouse_report(struct zotac_device *zotac, u8 *data, int size) +{ + s8 x_movement = 0, y_movement = 0, wheel_movement = 0; + int bit, i; + + if (size < 5 || !zotac->mouse_input) + return; + + x_movement = (s8)data[2]; + y_movement = (s8)data[3]; + + if (size >= 5) + wheel_movement = (s8)data[4]; + + for (i = 0; i < 8; i++) { + bit = 1 << i; + input_report_key(zotac->mouse_input, BTN_LEFT + i, + (data[1] & bit) ? 1 : 0); + } + + input_report_rel(zotac->mouse_input, REL_X, x_movement); + input_report_rel(zotac->mouse_input, REL_Y, y_movement); + + if (wheel_movement) + input_report_rel(zotac->mouse_input, REL_WHEEL, wheel_movement); + + input_sync(zotac->mouse_input); +} + +static const struct zotac_report_handler dial_interface_handlers[] = { + { .report_id = ZOTAC_DIAL_REPORT_ID, + .handler = process_dial_wheel_report, + .name = "dial wheel" }, + { .report_id = ZOTAC_MOUSE_REPORT_ID, + .handler = process_mouse_report, + .name = "mouse" }, + { 0 } +}; + +static int zotac_process_report(struct zotac_device *zotac, + const struct zotac_report_handler *handlers, + u8 *data, int size) +{ + const struct zotac_report_handler *handler; + u8 report_id; + + if (size < 1) + return 0; + + report_id = data[0]; + + for (handler = handlers; handler->handler; handler++) { + if (handler->report_id == report_id) { + handler->handler(zotac, data, size); + return 1; + } + } + + return 0; +} + +static int zotac_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + int intf_num = get_interface_num(hdev); + int handled = 0; + + if (size < 2) + return 0; + + switch (intf_num) { + case ZOTAC_GAMEPAD_INTERFACE: + if (zotac.gamepad) { + zotac_process_gamepad_report(&zotac, data, size); + handled = 1; + } + break; + case ZOTAC_DIAL_INTERFACE: + if (data[0] == ZOTAC_KBD_REPORT_ID) + return process_keyboard_report(&zotac, data, size); + + handled = zotac_process_report(&zotac, dial_interface_handlers, + data, size); + break; + case ZOTAC_REPORT_INTERFACE: + if (data[0] >= ZOTAC_STATUS_REPORT_ID && + data[0] <= ZOTAC_STATUS3_REPORT_ID) + handled = 1; + break; + } + + return handled; +} + +static int zotac_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + int intf_num = get_interface_num(hdev); + + if (intf_num == ZOTAC_REPORT_INTERFACE || + intf_num == ZOTAC_COMMAND_INTERFACE) + return -1; + + if (intf_num == ZOTAC_DIAL_INTERFACE) { + if (field->report && + (field->report->id == ZOTAC_MOUSE_REPORT_ID || + field->report->id == ZOTAC_DIAL_REPORT_ID)) + return -1; + + if (field->report && field->report->id == ZOTAC_KBD_REPORT_ID) + return 0; + } + + return 0; +} + +/** + * zotac_init_input_device - Initialize common input device properties + * @input_dev: The input device to initialize + * @hdev: The HID device associated with this input device + * @name: The name to assign to the input device + * + * Sets up common properties for an input device based on the HID device + */ +void zotac_init_input_device(struct input_dev *input_dev, + struct hid_device *hdev, const char *name) +{ + input_dev->name = name; + input_dev->phys = hdev->phys; + input_dev->uniq = hdev->uniq; + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_dev->dev.parent = &hdev->dev; +} + +static int setup_wheel_input_device(struct zotac_device *zotac) +{ + int ret; + + zotac->wheel_input = devm_input_allocate_device(&zotac->hdev->dev); + if (!zotac->wheel_input) { + hid_err(zotac->hdev, "Failed to allocate wheel input device\n"); + return -ENOMEM; + } + + zotac_init_input_device(zotac->wheel_input, zotac->hdev, + "ZOTAC Gaming Zone Dials"); + + __set_bit(EV_REL, zotac->wheel_input->evbit); + __set_bit(REL_WHEEL, zotac->wheel_input->relbit); + __set_bit(REL_HWHEEL, zotac->wheel_input->relbit); + + ret = input_register_device(zotac->wheel_input); + if (ret) { + hid_err(zotac->hdev, "Failed to register wheel input device\n"); + return ret; + } + + return 0; +} + +static int setup_mouse_input_device(struct zotac_device *zotac) +{ + int ret, i; + + zotac->mouse_input = devm_input_allocate_device(&zotac->hdev->dev); + if (!zotac->mouse_input) { + hid_err(zotac->hdev, "Failed to allocate mouse input device\n"); + return -ENOMEM; + } + + zotac_init_input_device(zotac->mouse_input, zotac->hdev, + "ZOTAC Gaming Zone Mouse"); + + __set_bit(EV_KEY, zotac->mouse_input->evbit); + __set_bit(EV_REL, zotac->mouse_input->evbit); + + for (i = 0; i < 8; i++) + __set_bit(BTN_LEFT + i, zotac->mouse_input->keybit); + + __set_bit(REL_X, zotac->mouse_input->relbit); + __set_bit(REL_Y, zotac->mouse_input->relbit); + __set_bit(REL_WHEEL, zotac->mouse_input->relbit); + + ret = input_register_device(zotac->mouse_input); + if (ret) { + hid_err(zotac->hdev, "Failed to register mouse input device\n"); + return ret; + } + + return 0; +} + +static int zotac_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + int intf_num, ret; + bool gamepad_initialized = false; + bool cfg_initialized = false; + + intf_num = get_interface_num(hdev); + + zotac.hdev = hdev; + hid_set_drvdata(hdev, &zotac); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + switch (intf_num) { + case ZOTAC_DIAL_INTERFACE: + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + break; + case ZOTAC_REPORT_INTERFACE: + case ZOTAC_COMMAND_INTERFACE: + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW); + break; + default: + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + break; + } + + if (ret) { + hid_err(hdev, "HID hw start failed\n"); + goto err; + } + + if (intf_num == ZOTAC_DIAL_INTERFACE) { + ret = setup_wheel_input_device(&zotac); + if (ret) { + hid_err(hdev, "Wheel input setup failed\n"); + goto err_stop_hw; + } + + ret = setup_mouse_input_device(&zotac); + if (ret) { + hid_err(hdev, "Mouse input setup failed\n"); + /* Unregister wheel input before jumping to err_stop_hw */ + input_unregister_device(zotac.wheel_input); + zotac.wheel_input = NULL; + goto err_stop_hw; + } + + ret = zotac_init_gamepad(&zotac, zotac_get_usb_interface(hdev)); + if (ret) { + hid_warn(hdev, "Gamepad initialization failed: %d\n", + ret); + } else { + gamepad_initialized = true; + } + } + + if (intf_num == ZOTAC_COMMAND_INTERFACE) { + ret = zotac_cfg_init(&zotac); + if (ret) { + hid_warn(hdev, "Cfg data initialization failed: %d\n", + ret); + goto err_stop_hw; + } else { + cfg_initialized = true; + zotac_register_sysfs(&zotac); + } + ret = zotac_rgb_init(&zotac); + if (ret) + hid_warn(hdev, "RGB initialization failed: %d\n", ret); + } + + hid_info(hdev, "Loaded version %s\n", ZOTAC_VERSION); + + return 0; + +err_stop_hw: + if (gamepad_initialized) + zotac_cleanup_gamepad(&zotac); + if (cfg_initialized) + zotac_cfg_cleanup(&zotac); + + hid_hw_stop(hdev); +err: + return ret; +} + +static int zotac_input_configured(struct hid_device *hdev, struct hid_input *hi) +{ + int intf_num = get_interface_num(hdev); + + if (intf_num == ZOTAC_DIAL_INTERFACE) + hi->input->name = "ZOTAC Gaming Zone Keyboard"; + + return 0; +} + +static int zotac_resubmit_urbs(struct hid_device *hdev) +{ + int intf_num = get_interface_num(hdev); + int ret = 0; + + if (zotac.gamepad && zotac.gamepad->urbs[0] && + (intf_num == ZOTAC_GAMEPAD_INTERFACE || + intf_num == ZOTAC_DIAL_INTERFACE)) { + ret = usb_submit_urb(zotac.gamepad->urbs[0], GFP_NOIO); + if (ret) { + hid_err(hdev, "Failed to resubmit gamepad URB: %d\n", + ret); + return ret; + } + hid_dbg(hdev, "Gamepad URB resubmitted successfully\n"); + } + + return 0; +} + +static int zotac_resume(struct hid_device *hdev) +{ + hid_dbg(hdev, "resume called for interface %d\n", + get_interface_num(hdev)); + return zotac_resubmit_urbs(hdev); +} + +static int zotac_reset_resume(struct hid_device *hdev) +{ + int intf_num = get_interface_num(hdev); + + hid_info(hdev, "reset_resume called for interface %d\n", intf_num); + + if (zotac.led_rgb_dev && intf_num == ZOTAC_COMMAND_INTERFACE) + zotac_rgb_resume(&zotac); + + return zotac_resubmit_urbs(hdev); +} + +static int zotac_suspend(struct hid_device *hdev, pm_message_t message) +{ + int intf_num = get_interface_num(hdev); + int i; + + hid_dbg(hdev, "suspend called for interface %d\n", intf_num); + + if (zotac.gamepad && (intf_num == ZOTAC_GAMEPAD_INTERFACE || + intf_num == ZOTAC_DIAL_INTERFACE)) { + /* Kill all input URBs */ + for (i = 0; i < ZOTAC_NUM_URBS; i++) { + if (zotac.gamepad->urbs[i]) { + usb_kill_urb(zotac.gamepad->urbs[i]); + hid_dbg(hdev, + "Gamepad URB %d killed for suspend\n", + i); + } + } + + /* Kill all force feedback URBs */ + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (zotac.gamepad->ff_urbs[i]) { + usb_kill_urb(zotac.gamepad->ff_urbs[i]); + hid_dbg(hdev, + "Force feedback URB %d killed for suspend\n", + i); + } + } + } + + if (zotac.led_rgb_dev && intf_num == ZOTAC_COMMAND_INTERFACE) + zotac_rgb_suspend(&zotac); + + return 0; +} + +static void zotac_remove(struct hid_device *hdev) +{ + int intf_num = get_interface_num(hdev); + + dev_info(&hdev->dev, "Removing driver for interface %d", intf_num); + + if (intf_num == ZOTAC_COMMAND_INTERFACE) { + dev_info(&hdev->dev, "Unregistering sysfs entries"); + zotac_unregister_sysfs(&zotac); + } + + if (zotac.gamepad) + zotac_cleanup_gamepad(&zotac); + + if (zotac.cfg_data) + zotac_cfg_cleanup(&zotac); + + if (zotac.led_rgb_dev) + zotac_rgb_cleanup(&zotac); + + hid_hw_stop(hdev); +} + +static const struct hid_device_id zotac_devices[] = { + { HID_USB_DEVICE(ZOTAC_VENDOR_ID, ZOTAC_PRODUCT_ID) }, + { HID_USB_DEVICE(ZOTAC_ALT_VENDOR_ID, ZOTAC_PRODUCT_ID) }, + {} +}; + +MODULE_DEVICE_TABLE(hid, zotac_devices); + +static struct hid_driver zotac_driver = { + .name = "zotac_zone_hid", + .id_table = zotac_devices, + .probe = zotac_probe, + .remove = zotac_remove, + .raw_event = zotac_raw_event, + .input_mapping = zotac_input_mapping, + .input_configured = zotac_input_configured, + .reset_resume = zotac_reset_resume, + .suspend = zotac_suspend, + .resume = zotac_resume, +}; + +module_hid_driver(zotac_driver); + +MODULE_AUTHOR("Luke D. Jones"); +MODULE_DESCRIPTION("HID driver for ZOTAC Gaming Zone Controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c new file mode 100644 index 000000000000..571eec2df446 --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-input.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for ZOTAC Gaming Zone Controller - RGB LED control + * + * Copyright (c) 2025 Luke D. Jones + */ + +#include "linux/input-event-codes.h" +#include +#include +#include +#include +#include +#include +#include + +#include "zotac-zone.h" + +#define ZOTAC_GAMEPAD_REPORT_SIZE 64 +#define ZOTAC_GAMEPAD_URB_INTERVAL 1 + +static void zotac_gamepad_urb_irq(struct urb *urb) +{ + struct zotac_device *zotac = urb->context; + struct zotac_gamepad *gamepad; + unsigned char *data = urb->transfer_buffer; + int retval, status = urb->status; + + if (!zotac || !zotac->gamepad) + return; + + gamepad = zotac->gamepad; + + /* Use memory barrier before reading disconnect flag to ensure latest value */ + smp_rmb(); + if (READ_ONCE(gamepad->disconnect)) { + return; + } + + switch (status) { + case 0: + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto exit; + } + + zotac_process_gamepad_report(zotac, data, urb->actual_length); + +exit: + /* Use memory barrier before reading disconnect flag to ensure latest value */ + smp_rmb(); + if (!READ_ONCE(gamepad->disconnect)) { + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&urb->dev->dev, + "usb_submit_urb failed with result %d\n", + retval); + } +} + +static void zotac_gamepad_ff_urb_complete(struct urb *urb) +{ + struct zotac_device *zotac = urb->context; + struct zotac_gamepad *gamepad; + int i; + + if (!zotac || !zotac->gamepad) + return; + + gamepad = zotac->gamepad; + + if (urb->status) + dev_dbg(&urb->dev->dev, "FF urb status %d\n", urb->status); + + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (gamepad->ff_urbs[i] == urb) { + gamepad->ff_urbs[i]->transfer_flags &= + ~URB_NO_TRANSFER_DMA_MAP; + atomic_set(&gamepad->ff_active[i], 0); + break; + } + } +} + +static int zotac_gamepad_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct zotac_device *zotac = input_get_drvdata(dev); + struct zotac_gamepad *gamepad = zotac->gamepad; + int retval = -EBUSY, i; + u16 strong, weak; + + if (!gamepad || READ_ONCE(gamepad->disconnect)) + return -ENODEV; + if (effect->type != FF_RUMBLE) + return 0; + + strong = effect->u.rumble.strong_magnitude; + weak = effect->u.rumble.weak_magnitude; + + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (atomic_read(&gamepad->ff_active[i]) == 0) { + gamepad->ff_data[i][0] = ZOTAC_FF_REPORT_ID; + gamepad->ff_data[i][1] = 0x08; + gamepad->ff_data[i][2] = 0x00; + gamepad->ff_data[i][3] = strong / 256; + gamepad->ff_data[i][4] = weak / 256; + gamepad->ff_data[i][5] = 0x00; + gamepad->ff_data[i][6] = 0x00; + gamepad->ff_data[i][7] = 0x00; + + /* Use atomic compare-and-swap to claim this URB */ + if (atomic_cmpxchg(&gamepad->ff_active[i], 0, 1) == 0) { + retval = usb_submit_urb(gamepad->ff_urbs[i], + GFP_ATOMIC); + if (retval) { + dev_err(&zotac->hdev->dev, + "usb_submit_urb(ff) failed: %d\n", + retval); + atomic_set(&gamepad->ff_active[i], 0); + } + break; + } + } + } + return retval; +} + +void zotac_process_gamepad_report(struct zotac_device *zotac, u8 *data, + int size) +{ + struct zotac_gamepad *gamepad = zotac->gamepad; + struct input_dev *input_dev; + + if (!gamepad || size < 14 || !(input_dev = gamepad->dev) || + data[0] != 0x00) + return; + + input_report_abs(input_dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(input_dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); + input_report_key(input_dev, BTN_START, data[2] & BIT(4)); + input_report_key(input_dev, BTN_SELECT, data[2] & BIT(5)); + input_report_key(input_dev, BTN_THUMBL, data[2] & BIT(6)); + input_report_key(input_dev, BTN_THUMBR, data[2] & BIT(7)); + input_report_key(input_dev, BTN_A, data[3] & BIT(4)); + input_report_key(input_dev, BTN_B, data[3] & BIT(5)); + input_report_key(input_dev, BTN_X, data[3] & BIT(6)); + input_report_key(input_dev, BTN_Y, data[3] & BIT(7)); + input_report_key(input_dev, BTN_TL, data[3] & BIT(0)); + input_report_key(input_dev, BTN_TR, data[3] & BIT(1)); + input_report_key(input_dev, BTN_MODE, data[3] & BIT(2)); + input_report_abs(input_dev, ABS_X, + (__s16)le16_to_cpup((__le16 *)(data + 6))); + input_report_abs(input_dev, ABS_Y, + ~(__s16)le16_to_cpup((__le16 *)(data + 8))); + input_report_abs(input_dev, ABS_RX, + (__s16)le16_to_cpup((__le16 *)(data + 10))); + input_report_abs(input_dev, ABS_RY, + ~(__s16)le16_to_cpup((__le16 *)(data + 12))); + input_report_abs(input_dev, ABS_Z, data[4]); + input_report_abs(input_dev, ABS_RZ, data[5]); + input_sync(input_dev); +} + +static void zotac_button_work_func(struct work_struct *work) +{ + struct zotac_gamepad *gamepad = container_of( + to_delayed_work(work), struct zotac_gamepad, button_work); + unsigned int button2, button; + bool qam_update; + + if (READ_ONCE(gamepad->disconnect) || !gamepad->dev) + return; + + /* Access these values atomically without a spinlock */ + /* We copy them locally to avoid races */ + button = READ_ONCE(gamepad->button_to_press); + button2 = READ_ONCE(gamepad->button_to_press2); + qam_update = READ_ONCE(gamepad->update_qam); + + /* Update the state variables */ + WRITE_ONCE(gamepad->update_qam, false); + WRITE_ONCE(gamepad->button_to_press, 0); + WRITE_ONCE(gamepad->button_to_press2, 0); + + /* Memory barrier to ensure these writes complete before proceeding */ + smp_wmb(); + + if (qam_update) { + input_report_key(gamepad->dev, button, 1); + input_sync(gamepad->dev); + msleep(150); + input_report_key(gamepad->dev, button2, 1); + input_sync(gamepad->dev); + input_report_key(gamepad->dev, button2, 0); + input_sync(gamepad->dev); + input_report_key(gamepad->dev, button, 0); + input_sync(gamepad->dev); + } else if (button) { + input_report_key(gamepad->dev, button, 1); + input_sync(gamepad->dev); + input_report_key(gamepad->dev, button, 0); + input_sync(gamepad->dev); + } + + /* Release the button press lock so others can schedule button presses */ + atomic_set(&gamepad->button_press_in_progress, 0); +} + +void zotac_gamepad_send_button(struct zotac_device *zotac, int buttons[], + int num_buttons) +{ + struct zotac_gamepad *gamepad; + + if (!zotac || !zotac->gamepad || !zotac->gamepad->dev || + READ_ONCE(zotac->gamepad->disconnect)) + return; + + gamepad = zotac->gamepad; + + /* Try to atomically take the button press lock */ + if (atomic_cmpxchg(&gamepad->button_press_in_progress, 0, 1) == 0) { + /* We got the lock, update button values */ + + /* Reset button state first */ + WRITE_ONCE(gamepad->button_to_press, 0); + WRITE_ONCE(gamepad->button_to_press2, 0); + WRITE_ONCE(gamepad->update_qam, false); + + /* Set new button values */ + if (num_buttons == 1) { + WRITE_ONCE(gamepad->button_to_press, buttons[0]); + } else if (num_buttons == 2) { + WRITE_ONCE(gamepad->update_qam, true); + WRITE_ONCE(gamepad->button_to_press, buttons[0]); + WRITE_ONCE(gamepad->button_to_press2, buttons[1]); + } else { + /* No buttons to press, release the lock */ + atomic_set(&gamepad->button_press_in_progress, 0); + return; + } + + /* Memory barrier to ensure writes complete before scheduling work */ + smp_wmb(); + + /* Schedule the work */ + schedule_delayed_work(&gamepad->button_work, + msecs_to_jiffies(5)); + } +} + +static void zotac_find_endpoints(struct usb_interface *intf, + struct usb_endpoint_descriptor **ep_in, + struct usb_endpoint_descriptor **ep_out) +{ + struct usb_host_interface *host_interface = intf->cur_altsetting; + int i; + + *ep_in = *ep_out = NULL; + for (i = 0; i < host_interface->desc.bNumEndpoints; i++) { + struct usb_endpoint_descriptor *ep = + &host_interface->endpoint[i].desc; + if (usb_endpoint_is_int_in(ep)) + *ep_in = ep; + else if (usb_endpoint_is_int_out(ep)) + *ep_out = ep; + } +} + +int zotac_init_gamepad(struct zotac_device *zotac, struct usb_interface *intf) +{ + struct usb_endpoint_descriptor *ep_in = NULL, *ep_out = NULL; + struct usb_device *udev = interface_to_usbdev(intf); + struct hid_device *hdev = zotac->hdev; + int pipe, maxp, interval, ret = 0, i; + + struct usb_interface *gamepad_intf; + struct zotac_gamepad *gamepad; + struct input_dev *input_dev; + + if (!(gamepad = kzalloc(sizeof(*gamepad), GFP_KERNEL))) + return -ENOMEM; + + WRITE_ONCE(gamepad->disconnect, false); + gamepad->zotac = zotac; + zotac->gamepad = gamepad; + zotac->udev = udev; + + if (!(gamepad_intf = usb_ifnum_to_if(udev, ZOTAC_GAMEPAD_INTERFACE))) { + ret = -ENODEV; + goto err_free_gamepad; + } + + zotac_find_endpoints(gamepad_intf, &ep_in, &ep_out); + if (!ep_in) { + ret = -ENODEV; + goto err_free_gamepad; + } + + gamepad->qam_mode = QAM_MODE_STEAM; + gamepad->ep_in = ep_in; + gamepad->ep_out = ep_out; + + if (!(input_dev = input_allocate_device())) { + ret = -ENOMEM; + goto err_free_gamepad; + } + + gamepad->dev = input_dev; + zotac_init_input_device(input_dev, hdev, "ZOTAC Gaming Zone Gamepad"); + input_set_drvdata(input_dev, zotac); + + input_set_abs_params(input_dev, ABS_X, -32768, 32767, 16, 128); + input_set_abs_params(input_dev, ABS_Y, -32768, 32767, 16, 128); + input_set_abs_params(input_dev, ABS_RX, -32768, 32767, 16, 128); + input_set_abs_params(input_dev, ABS_RY, -32768, 32767, 16, 128); + input_set_abs_params(input_dev, ABS_Z, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, 0, 255, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0X, -1, 1, 0, 0); + input_set_abs_params(input_dev, ABS_HAT0Y, -1, 1, 0, 0); + + input_set_capability(input_dev, EV_KEY, BTN_A); + input_set_capability(input_dev, EV_KEY, BTN_B); + input_set_capability(input_dev, EV_KEY, BTN_X); + input_set_capability(input_dev, EV_KEY, BTN_Y); + input_set_capability(input_dev, EV_KEY, BTN_TL); + input_set_capability(input_dev, EV_KEY, BTN_TR); + input_set_capability(input_dev, EV_KEY, BTN_MODE); + input_set_capability(input_dev, EV_KEY, BTN_START); + input_set_capability(input_dev, EV_KEY, BTN_SELECT); + input_set_capability(input_dev, EV_KEY, BTN_THUMBL); + input_set_capability(input_dev, EV_KEY, BTN_THUMBR); + + /* Allow the gamepad to emit these events for screenface buttons */ + input_set_capability(input_dev, EV_KEY, KEY_F14); + input_set_capability(input_dev, EV_KEY, KEY_F15); + input_set_capability(input_dev, EV_KEY, KEY_F16); + input_set_capability(input_dev, EV_KEY, KEY_F17); + input_set_capability(input_dev, EV_KEY, KEY_F18); + input_set_capability(input_dev, EV_KEY, KEY_F19); + + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY1); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY2); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY3); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY4); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY5); + input_set_capability(input_dev, EV_KEY, BTN_TRIGGER_HAPPY6); + + pipe = usb_rcvintpipe(udev, gamepad->ep_in->bEndpointAddress); + if (!(maxp = usb_maxpacket(udev, pipe))) { + ret = -EINVAL; + goto err_free_input; + } + + interval = gamepad->ep_in->bInterval ? gamepad->ep_in->bInterval : + ZOTAC_GAMEPAD_URB_INTERVAL; + + for (i = 0; i < ZOTAC_NUM_URBS; i++) { + if (!(gamepad->urb_buf[i] = + kzalloc(ZOTAC_GAMEPAD_REPORT_SIZE, GFP_KERNEL))) { + ret = -ENOMEM; + goto err_free_urbs; + } + if (!(gamepad->urbs[i] = usb_alloc_urb(0, GFP_KERNEL))) { + ret = -ENOMEM; + goto err_free_urbs; + } + usb_fill_int_urb(gamepad->urbs[i], udev, pipe, + gamepad->urb_buf[i], ZOTAC_GAMEPAD_REPORT_SIZE, + zotac_gamepad_urb_irq, zotac, interval); + } + + INIT_DELAYED_WORK(&gamepad->button_work, zotac_button_work_func); + + /* Initialize atomic variables */ + atomic_set(&gamepad->button_press_in_progress, 0); + + if (gamepad->ep_out) { + input_set_capability(input_dev, EV_FF, FF_RUMBLE); + + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + /* Initialize atomic ff_active */ + atomic_set(&gamepad->ff_active[i], 0); + + if (!(gamepad->ff_data[i] = usb_alloc_coherent( + udev, ZOTAC_FF_REPORT_LEN, GFP_KERNEL, + &gamepad->ff_dma[i]))) { + ret = -ENOMEM; + goto err_free_ff_data; + } + if (!(gamepad->ff_urbs[i] = + usb_alloc_urb(0, GFP_KERNEL))) { + ret = -ENOMEM; + goto err_free_ff_urbs; + } + usb_fill_int_urb( + gamepad->ff_urbs[i], udev, + usb_sndintpipe( + udev, + gamepad->ep_out->bEndpointAddress), + gamepad->ff_data[i], ZOTAC_FF_REPORT_LEN, + zotac_gamepad_ff_urb_complete, zotac, + gamepad->ep_out->bInterval); + gamepad->ff_urbs[i]->transfer_dma = gamepad->ff_dma[i]; + gamepad->ff_urbs[i]->transfer_flags |= + URB_NO_TRANSFER_DMA_MAP; + } + + if ((ret = input_ff_create_memless( + input_dev, NULL, zotac_gamepad_play_effect))) { + dev_err(&zotac->hdev->dev, + "Failed to create FF device: %d\n", ret); + goto err_free_ff_urbs; + } + } + + if ((ret = input_register_device(input_dev))) { + dev_err(&zotac->hdev->dev, + "Failed to register input device: %d\n", ret); + goto err_free_ff_urbs; + } + + for (i = 0; i < ZOTAC_NUM_URBS; i++) { + if ((ret = usb_submit_urb(gamepad->urbs[i], GFP_KERNEL))) { + dev_err(&zotac->hdev->dev, + "Failed to submit URB %d: %d\n", i, ret); + while (--i >= 0) + usb_kill_urb(gamepad->urbs[i]); + input_unregister_device(input_dev); + gamepad->dev = NULL; + goto err_free_ff_urbs; + } + } + return 0; + +err_free_ff_urbs: + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (gamepad->ff_urbs[i]) { + usb_free_urb(gamepad->ff_urbs[i]); + gamepad->ff_urbs[i] = NULL; + } + } + +err_free_ff_data: + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (gamepad->ff_data[i]) { + usb_free_coherent(udev, ZOTAC_FF_REPORT_LEN, + gamepad->ff_data[i], + gamepad->ff_dma[i]); + gamepad->ff_data[i] = NULL; + } + } + +err_free_urbs: + for (i = 0; i < ZOTAC_NUM_URBS; i++) { + if (gamepad->urbs[i]) { + usb_free_urb(gamepad->urbs[i]); + gamepad->urbs[i] = NULL; + } + if (gamepad->urb_buf[i]) { + kfree(gamepad->urb_buf[i]); + gamepad->urb_buf[i] = NULL; + } + } + +err_free_input: + if (gamepad->dev) { + input_free_device(gamepad->dev); + gamepad->dev = NULL; + } + +err_free_gamepad: + zotac->gamepad = NULL; + kfree(gamepad); + return ret; +} + +void zotac_cleanup_gamepad(struct zotac_device *zotac) +{ + struct zotac_gamepad *gamepad; + int i; + + if (!zotac || !zotac->gamepad) + return; + + gamepad = zotac->gamepad; + + /* Set disconnect first, use WRITE_ONCE and memory barrier to ensure visibility */ + WRITE_ONCE(gamepad->disconnect, true); + smp_wmb(); + + cancel_delayed_work_sync(&gamepad->button_work); + + for (i = 0; i < ZOTAC_NUM_URBS; i++) { + if (gamepad->urbs[i]) { + usb_kill_urb(gamepad->urbs[i]); + usb_free_urb(gamepad->urbs[i]); + gamepad->urbs[i] = NULL; + } + } + + for (i = 0; i < ZOTAC_NUM_FF_URBS; i++) { + if (gamepad->ff_urbs[i]) { + usb_kill_urb(gamepad->ff_urbs[i]); + usb_free_urb(gamepad->ff_urbs[i]); + gamepad->ff_urbs[i] = NULL; + } + } + + if (gamepad->dev) { + input_unregister_device(gamepad->dev); + gamepad->dev = NULL; + } + + zotac->gamepad = NULL; +} diff --git a/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c b/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c new file mode 100644 index 000000000000..0305de2bd549 --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone-hid-rgb.c @@ -0,0 +1,717 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for ZOTAC Gaming Zone Controller - RGB LED control + * + * Copyright (c) 2025 Luke D. Jones + */ + +#include +#include +#include +#include +#include +#include + +#include "zotac-zone.h" + +#define SETTING_COLOR 0x00 +#define SETTING_SPEED 0x01 +#define SETTING_EFFECT 0x02 +#define SETTING_BRIGHTNESS 0x03 +#define SETTING_REAL_TIME 0x04 + +#define EFFECT_RAINBOW 0x00 +#define EFFECT_BREATHE 0x01 +#define EFFECT_STARS 0x02 +#define EFFECT_FADE 0x03 +#define EFFECT_DANCE 0x04 +#define EFFECT_OFF 0xF0 + +#define SPEED_SLOW 0x00 +#define SPEED_NORMAL 0x01 +#define SPEED_FAST 0x02 + +#define BRIGHTNESS_OFF 0x00 /* 0% */ +#define BRIGHTNESS_LOW 0x19 /* 25% */ +#define BRIGHTNESS_MED 0x32 /* 50% */ +#define BRIGHTNESS_HIGH 0x4B /* 75% */ +#define BRIGHTNESS_MAX 0x64 /* 100% */ + +static void zotac_rgb_set_default_colors(struct zotac_device *zotac, + struct zotac_rgb_dev *led_rgb, + int zone_idx) +{ + int j, led_index; + + for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) { + led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j; + + led_rgb->red[led_index] = 128; + led_rgb->green[led_index] = 128; + led_rgb->blue[led_index] = 128; + + zotac->led_rgb_data.zone[zone_idx].red[j] = 128; + zotac->led_rgb_data.zone[zone_idx].green[j] = 128; + zotac->led_rgb_data.zone[zone_idx].blue[j] = 128; + } +} + +static int zotac_rgb_read_zone_colors(struct zotac_device *zotac, + struct zotac_rgb_dev *led_rgb, + u8 zone_idx) +{ + size_t expected_len = 1 + ZOTAC_RGB_LEDS_PER_ZONE * 3; + u8 zone_rgb_data[1 + ZOTAC_RGB_LEDS_PER_ZONE * 3]; + size_t data_len = sizeof(zone_rgb_data); + int ret, j, led_index, red_idx, green_idx, blue_idx; + u8 red, green, blue; + + zone_rgb_data[0] = zone_idx; + + ret = zotac_send_get_command(zotac, CMD_GET_RGB, SETTING_COLOR, + &zone_idx, 1, zone_rgb_data, &data_len); + + if (ret < 0) { + hid_err(zotac->hdev, "Failed to read RGB data for zone %d\n", + zone_idx); + zotac_rgb_set_default_colors(zotac, led_rgb, zone_idx); + return ret; + } + + if (data_len < expected_len) { + hid_warn(zotac->hdev, + "Incomplete RGB data for zone %d: %zu bytes\n", + zone_idx, data_len); + zotac_rgb_set_default_colors(zotac, led_rgb, zone_idx); + return 0; + } + + for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) { + led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j; + red_idx = 1 + (j * 3); + green_idx = red_idx + 1; + blue_idx = red_idx + 2; + + if (red_idx >= data_len || green_idx >= data_len || + blue_idx >= data_len) { + hid_warn(zotac->hdev, + "Index out of bounds for zone %d, LED %d\n", + zone_idx, j); + break; + } + + red = zone_rgb_data[red_idx]; + green = zone_rgb_data[green_idx]; + blue = zone_rgb_data[blue_idx]; + + if (led_index < + ZOTAC_RGB_ZONE_COUNT * ZOTAC_RGB_LEDS_PER_ZONE) { + led_rgb->red[led_index] = red; + led_rgb->green[led_index] = green; + led_rgb->blue[led_index] = blue; + + zotac->led_rgb_data.zone[zone_idx].red[j] = red; + zotac->led_rgb_data.zone[zone_idx].green[j] = green; + zotac->led_rgb_data.zone[zone_idx].blue[j] = blue; + } else { + hid_warn( + zotac->hdev, + "Output index out of bounds for zone %d, LED %d\n", + zone_idx, j); + break; + } + } + + return 0; +} + +static int zotac_rgb_set_globals(struct zotac_device *zotac) +{ + int effect, speed, brightness; + + effect = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_EFFECT, NULL, + 0); + if (effect < 0) { + hid_warn( + zotac->hdev, + "Could not read effect from device, using default: %d\n", + EFFECT_RAINBOW); + effect = EFFECT_RAINBOW; + } + + speed = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_SPEED, NULL, 0); + if (speed < 0) { + hid_warn( + zotac->hdev, + "Could not read speed from device, using default: %d\n", + SPEED_NORMAL); + speed = SPEED_NORMAL; + } + + /* This brightness is firmware level, not LED class level */ + brightness = zotac_send_get_byte(zotac, CMD_GET_RGB, SETTING_BRIGHTNESS, + NULL, 0); + if (brightness < 0) { + hid_warn( + zotac->hdev, + "Could not read brightness from device, using default: %d\n", + BRIGHTNESS_MED); + brightness = BRIGHTNESS_MED; + } + + zotac->led_rgb_data.effect = effect; + zotac->led_rgb_data.speed = speed; + zotac->led_rgb_data.brightness = brightness; + + return 0; +} + +static void zotac_rgb_schedule_work(struct zotac_rgb_dev *led) +{ + unsigned long flags; + + spin_lock_irqsave(&led->lock, flags); + if (!led->removed) + schedule_work(&led->work); + spin_unlock_irqrestore(&led->lock, flags); +} + +static void zotac_rgb_do_work(struct work_struct *work) +{ + struct zotac_rgb_dev *led = + container_of(work, struct zotac_rgb_dev, work); + struct zotac_device *zotac = led->zotac; + u8 zone_idx = led - zotac->led_rgb_dev; + u8 zone_data[3 + ZOTAC_RGB_LEDS_PER_ZONE * 3]; + unsigned long flags; + int j, led_index; + + spin_lock_irqsave(&led->lock, flags); + if (!led->update_rgb) { + spin_unlock_irqrestore(&led->lock, flags); + return; + } + led->update_rgb = false; + + zone_data[0] = zone_idx; + + // [0] = zone number, [1..2] = blank, [3..] = data + for (j = 0; j < ZOTAC_RGB_LEDS_PER_ZONE; j++) { + led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + j; + zone_data[3 + (j * 3)] = led->red[led_index]; + zone_data[4 + (j * 3)] = led->green[led_index]; + zone_data[5 + (j * 3)] = led->blue[led_index]; + } + spin_unlock_irqrestore(&led->lock, flags); + + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_COLOR, zone_data, + sizeof(zone_data)); + + if (zone_idx == 0) { + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_EFFECT, + &zotac->led_rgb_data.effect, 1); + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_SPEED, + &zotac->led_rgb_data.speed, 1); + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_BRIGHTNESS, + &led->brightness, 1); + } + + zotac_send_set_command(zotac, CMD_SAVE_CONFIG, 0, NULL, 0); +} + +static void zotac_rgb_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct zotac_rgb_dev *led = + container_of(mc_cdev, struct zotac_rgb_dev, led_rgb_dev); + struct zotac_device *zotac = led->zotac; + u8 zone_idx = led - zotac->led_rgb_dev; + unsigned long flags; + int i, led_index, intensity, bright; + + led_mc_calc_color_components(mc_cdev, brightness); + + spin_lock_irqsave(&led->lock, flags); + led->update_rgb = true; + bright = mc_cdev->led_cdev.brightness; + + for (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) { + led_index = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE + i; + intensity = mc_cdev->subled_info[i].intensity; + led->red[led_index] = (((intensity >> 16) & 0xFF) * bright) / 255; + led->green[led_index] = (((intensity >> 8) & 0xFF) * bright) / 255; + led->blue[led_index] = ((intensity & 0xFF) * bright) / 255; + + zotac->led_rgb_data.zone[zone_idx].red[i] = led->red[led_index]; + zotac->led_rgb_data.zone[zone_idx].green[i] = led->green[led_index]; + zotac->led_rgb_data.zone[zone_idx].blue[i] = led->blue[led_index]; + } + + zotac->led_rgb_data.zone[zone_idx].brightness = bright; + zotac->led_rgb_data.initialized = true; + spin_unlock_irqrestore(&led->lock, flags); + + zotac_rgb_schedule_work(led); +} + +static void zotac_rgb_store_settings(struct zotac_device *zotac) +{ + struct zotac_rgb_dev *led_rgb; + int i, arr_size = ZOTAC_RGB_LEDS_PER_ZONE; + + for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) { + led_rgb = &zotac->led_rgb_dev[i]; + + zotac->led_rgb_data.zone[i].brightness = + led_rgb->led_rgb_dev.led_cdev.brightness; + + memcpy(zotac->led_rgb_data.zone[i].red, + led_rgb->red + (i * ZOTAC_RGB_LEDS_PER_ZONE), arr_size); + memcpy(zotac->led_rgb_data.zone[i].green, + led_rgb->green + (i * ZOTAC_RGB_LEDS_PER_ZONE), + arr_size); + memcpy(zotac->led_rgb_data.zone[i].blue, + led_rgb->blue + (i * ZOTAC_RGB_LEDS_PER_ZONE), arr_size); + } +} + +static void zotac_rgb_restore_settings(struct zotac_rgb_dev *led_rgb, + struct led_classdev *led_cdev, + struct mc_subled *mc_led_info) +{ + struct zotac_device *zotac = led_rgb->zotac; + u8 zone_idx = led_rgb - zotac->led_rgb_dev; + int i, offset = zone_idx * ZOTAC_RGB_LEDS_PER_ZONE; + int arr_size = ZOTAC_RGB_LEDS_PER_ZONE; + + memcpy(led_rgb->red + offset, zotac->led_rgb_data.zone[zone_idx].red, + arr_size); + memcpy(led_rgb->green + offset, + zotac->led_rgb_data.zone[zone_idx].green, arr_size); + memcpy(led_rgb->blue + offset, zotac->led_rgb_data.zone[zone_idx].blue, + arr_size); + + for (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) { + mc_led_info[i].intensity = + (zotac->led_rgb_data.zone[zone_idx].red[i] << 16) | + (zotac->led_rgb_data.zone[zone_idx].green[i] << 8) | + zotac->led_rgb_data.zone[zone_idx].blue[i]; + } + + led_cdev->brightness = zotac->led_rgb_data.zone[zone_idx].brightness; +} + +static ssize_t rgb_effect_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int effect; + + if (!zotac.cfg_data) + return -ENODEV; + effect = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_EFFECT, NULL, + 0); + if (effect < 0) + return effect; + + return sysfs_emit(buf, "%d\n", effect); +} + +static ssize_t rgb_effect_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int effect, ret; + u8 effect_val; + + if (!zotac.cfg_data) + return -ENODEV; + + ret = kstrtoint(buf, 10, &effect); + if (ret) + return ret; + + switch (effect) { + case EFFECT_RAINBOW: + case EFFECT_BREATHE: + case EFFECT_STARS: + case EFFECT_FADE: + case EFFECT_DANCE: + case EFFECT_OFF: + break; + default: + return -EINVAL; + } + + effect_val = (u8)effect; + ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_EFFECT, + &effect_val, 1); + if (ret < 0) + return ret; + + zotac.led_rgb_data.effect = effect_val; + + return count; +} +static DEVICE_ATTR_RW_NAMED(rgb_effect, "effect"); + +static ssize_t rgb_speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + int speed; + + if (!zotac.cfg_data) + return -ENODEV; + + speed = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_SPEED, NULL, 0); + if (speed < 0) + return speed; + + return sysfs_emit(buf, "%d\n", speed); +} + +static ssize_t rgb_speed_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int speed, ret; + u8 speed_val; + + if (!zotac.cfg_data) + return -ENODEV; + + ret = kstrtoint(buf, 10, &speed); + if (ret) { + dev_err(dev, "Invalid speed value format\n"); + return ret; + } + + switch (speed) { + case SPEED_SLOW: + case SPEED_NORMAL: + case SPEED_FAST: + break; + default: + dev_err(dev, "Invalid speed value: %d (valid: 0-2)\n", speed); + return -EINVAL; + } + + speed_val = (u8)speed; + ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_SPEED, + &speed_val, 1); + if (ret < 0) { + dev_err(dev, "Failed to set RGB speed: %d\n", ret); + return ret; + } + + zotac.led_rgb_data.speed = speed_val; + + return count; +} +static DEVICE_ATTR_RW_NAMED(rgb_speed, "speed"); + +static u8 brightness_level_to_value(unsigned int level) +{ + switch (level) { + case 0: + return BRIGHTNESS_OFF; + case 1: + return BRIGHTNESS_LOW; + case 2: + return BRIGHTNESS_MED; + case 3: + return BRIGHTNESS_HIGH; + case 4: + return BRIGHTNESS_MAX; + default: + return BRIGHTNESS_MED; + } +} + +static unsigned int brightness_value_to_level(u8 value) +{ + if (value <= (BRIGHTNESS_OFF + BRIGHTNESS_LOW) / 2) + return 0; + else if (value <= (BRIGHTNESS_LOW + BRIGHTNESS_MED) / 2) + return 1; + else if (value <= (BRIGHTNESS_MED + BRIGHTNESS_HIGH) / 2) + return 2; + else if (value <= (BRIGHTNESS_HIGH + BRIGHTNESS_MAX) / 2) + return 3; + else + return 4; +} + +static ssize_t rgb_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int brightness = 0; + unsigned int level; + + if (!zotac.cfg_data) + return -ENODEV; + + brightness = zotac_send_get_byte(&zotac, CMD_GET_RGB, SETTING_BRIGHTNESS, + NULL, 0); + if (brightness < 0) + return brightness; + + level = brightness_value_to_level(brightness); + + return sysfs_emit(buf, "%u\n", level); +} + +static ssize_t rgb_brightness_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int level, ret; + u8 brightness; + + if (!zotac.cfg_data) + return -ENODEV; + + ret = kstrtoint(buf, 10, &level); + if (ret) + return ret; + + if (level > 4) + return -EINVAL; + + brightness = brightness_level_to_value(level); + + ret = zotac_send_set_command(&zotac, CMD_SET_RGB, SETTING_BRIGHTNESS, + &brightness, 1); + if (ret < 0) + return ret; + + ret = zotac_send_set_command(&zotac, CMD_SAVE_CONFIG, 0, NULL, 0); + if (ret < 0) + return ret; + + zotac.led_rgb_data.brightness = brightness; + + return count; +} +static DEVICE_ATTR_RW_NAMED(rgb_brightness, "brightness"); + +static struct attribute *zotac_rgb_attrs[] = { &dev_attr_rgb_effect.attr, + &dev_attr_rgb_speed.attr, + &dev_attr_rgb_brightness.attr, + NULL }; + +static const struct attribute_group zotac_rgb_attr_group = { + .name = "rgb", + .attrs = zotac_rgb_attrs, +}; + +/** +* zotac_rgb_resume - Restore RGB LED settings after system resume +* @zotac: Pointer to the zotac device structure +* +* Restores previously saved RGB settings after the system resumes from +* suspend. This includes effect, speed, and color settings for all zones. +*/ +void zotac_rgb_resume(struct zotac_device *zotac) +{ + struct zotac_rgb_dev *led_rgb; + struct led_classdev *led_cdev; + struct mc_subled *mc_led_info; + int i; + + if (!zotac->led_rgb_dev) + return; + + if (!zotac->led_rgb_data.initialized) { + hid_warn( + zotac->hdev, + "RGB data not initialized, skipping resume restoration\n"); + return; + } + + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_EFFECT, + &zotac->led_rgb_data.effect, 1); + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_SPEED, + &zotac->led_rgb_data.speed, 1); + zotac_send_set_command(zotac, CMD_SET_RGB, SETTING_BRIGHTNESS, + &zotac->led_rgb_data.brightness, 1); + + for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) { + led_rgb = &zotac->led_rgb_dev[i]; + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + mc_led_info = led_rgb->led_rgb_dev.subled_info; + + zotac_rgb_restore_settings(led_rgb, led_cdev, mc_led_info); + led_rgb->update_rgb = true; + zotac_rgb_schedule_work(led_rgb); + } +} + +/** +* zotac_rgb_suspend - Save RGB LED settings before system suspend +* @zotac: Pointer to the zotac device structure +* +* Stores current RGB settings before the system suspends so they +* can be restored when the system resumes. +*/ +void zotac_rgb_suspend(struct zotac_device *zotac) +{ + if (!zotac->led_rgb_dev) + return; + + zotac_rgb_store_settings(zotac); +} + +static int zotac_rgb_register_zone(struct hid_device *hdev, + struct zotac_rgb_dev *led_rgb, + int zone_index) +{ + struct mc_subled *mc_led_info; + struct led_classdev *led_cdev; + char name[32]; + int i, err; + + snprintf(name, sizeof(name), "zotac:rgb:spectra_zone_%d", zone_index); + + mc_led_info = devm_kmalloc_array(&hdev->dev, ZOTAC_RGB_LEDS_PER_ZONE, + sizeof(*mc_led_info), + GFP_KERNEL | __GFP_ZERO); + if (!mc_led_info) + return -ENOMEM; + + for (i = 0; i < ZOTAC_RGB_LEDS_PER_ZONE; i++) { + mc_led_info[i].color_index = LED_COLOR_ID_RGB; + } + + led_rgb->led_rgb_dev.subled_info = mc_led_info; + led_rgb->led_rgb_dev.num_colors = ZOTAC_RGB_LEDS_PER_ZONE; + + led_cdev = &led_rgb->led_rgb_dev.led_cdev; + led_cdev->brightness = 128; + led_cdev->name = kstrdup(name, GFP_KERNEL); + if (!led_cdev->name){ + devm_kfree(&hdev->dev, mc_led_info); + return -ENOMEM; + } + + led_cdev->max_brightness = 255; + led_cdev->brightness_set = zotac_rgb_set_brightness; + + err = devm_led_classdev_multicolor_register(&hdev->dev, + &led_rgb->led_rgb_dev); + if (err) { + kfree(led_cdev->name); + return err; + } + + err = sysfs_create_group(&led_cdev->dev->kobj, &zotac_rgb_attr_group); + if (err) { + return err; + } + + return 0; +} + +static int zotac_rgb_init_zone(struct zotac_device *zotac, int zone_idx) +{ + struct zotac_rgb_dev *led_rgb = &zotac->led_rgb_dev[zone_idx]; + int ret; + + led_rgb->hdev = zotac->hdev; + led_rgb->zotac = zotac; + led_rgb->removed = false; + led_rgb->brightness = 128; + + zotac->led_rgb_data.zone[zone_idx].brightness = + zotac->led_rgb_data.brightness; + + zotac_rgb_read_zone_colors(zotac, led_rgb, zone_idx); + + INIT_WORK(&led_rgb->work, zotac_rgb_do_work); + led_rgb->output_worker_initialized = true; + spin_lock_init(&led_rgb->lock); + + ret = zotac_rgb_register_zone(zotac->hdev, led_rgb, zone_idx); + if (ret < 0) + return ret; + + return 0; +} + +/** + * zotac_rgb_cleanup - Clean up resources used by RGB LED subsystem + * @zotac: Pointer to the zotac device structure + * + * Releases resources allocated for RGB LED control, including sysfs + * attributes, scheduled work, and allocated memory. + */ +void zotac_rgb_cleanup(struct zotac_device *zotac) +{ + struct zotac_rgb_dev *led_rgb; + unsigned long flags; + int i; + + if (!zotac->led_rgb_dev) + return; + + sysfs_remove_group(&zotac->led_rgb_dev->led_rgb_dev.led_cdev.dev->kobj, &zotac_rgb_attr_group); + + for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) { + led_rgb = &zotac->led_rgb_dev[i]; + + if (led_rgb->removed) + continue; + + spin_lock_irqsave(&led_rgb->lock, flags); + led_rgb->removed = true; + led_rgb->output_worker_initialized = false; + spin_unlock_irqrestore(&led_rgb->lock, flags); + + cancel_work_sync(&led_rgb->work); + } + + zotac->led_rgb_dev = NULL; +} + +/** +* zotac_rgb_init - Initialize RGB LED subsystem +* @zotac: Pointer to the zotac device structure +* +* Initializes the RGB LED subsystem by allocating memory for LEDs, +* fetching current settings from the device, registering LED zones, +* and creating sysfs attributes for RGB control. +* +* Return: 0 on success, negative error code on failure +*/ +int zotac_rgb_init(struct zotac_device *zotac) +{ + struct zotac_rgb_dev *led_rgb; + int ret, i; + + led_rgb = devm_kcalloc(&zotac->hdev->dev, ZOTAC_RGB_ZONE_COUNT, + sizeof(*led_rgb), GFP_KERNEL); + if (!led_rgb) + return -ENOMEM; + + zotac->led_rgb_dev = led_rgb; + + zotac_rgb_set_globals(zotac); + + for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) { + ret = zotac_rgb_init_zone(zotac, i); + if (ret < 0) { + zotac_rgb_cleanup(zotac); + return ret; + } + } + + zotac->led_rgb_data.initialized = true; + + for (i = 0; i < ZOTAC_RGB_ZONE_COUNT; i++) { + led_rgb = &zotac->led_rgb_dev[i]; + led_rgb->update_rgb = true; + zotac_rgb_schedule_work(led_rgb); + } + + return 0; +} diff --git a/drivers/hid/zotac-zone-hid/zotac-zone.h b/drivers/hid/zotac-zone-hid/zotac-zone.h new file mode 100644 index 000000000000..07ecd9886098 --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone.h @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for ZOTAC Gaming Zone Controller - RGB LED control + * + * Copyright (c) 2025 Luke D. Jones + */ + +#ifndef __HID_ZOTAC_ZONE_H +#define __HID_ZOTAC_ZONE_H + +#include +#include +#include +#include +#include + +#define ZOTAC_VERSION "0.1.4" + +#define ZOTAC_VENDOR_ID 0x1ee9 +#define ZOTAC_ALT_VENDOR_ID 0x1e19 +#define ZOTAC_PRODUCT_ID 0x1590 + +#define ZOTAC_GAMEPAD_INTERFACE 0 +#define ZOTAC_DIAL_INTERFACE 1 +#define ZOTAC_REPORT_INTERFACE 2 +#define ZOTAC_COMMAND_INTERFACE 3 + +#define ZOTAC_FF_REPORT_ID 0x00 +#define ZOTAC_FF_REPORT_LEN 8 +#define ZOTAC_NUM_URBS 3 +#define ZOTAC_NUM_FF_URBS 2 + +#define CMD_SAVE_CONFIG 0xFB +#define CMD_SET_RGB 0xAD +#define CMD_GET_RGB 0xAE + +#define ZOTAC_RGB_ZONE_COUNT 2 /* Number of physical zones (0 and 1) */ +#define ZOTAC_RGB_LEDS_PER_ZONE 10 /* Number of LEDs in each zone */ + +#define SENSITIVITY_POINT_COUNT 4 + +/* Command codes for button mapping */ +#define CMD_SET_BUTTON_MAPPING 0xA1 +#define CMD_GET_BUTTON_MAPPING 0xA2 + +#define BUTTON_MAX 0x18 +#define MAX_GAMEPAD_BUTTONS 14 +#define MAX_KEYBOARD_KEYS 6 +#define MAX_MOUSE_BUTTONS 3 + +enum qam_mode { + QAM_MODE_KEYBOARD = 0, + QAM_MODE_STEAM, + QAM_MODE_CUSTOM, + QAM_MODE_LENGTH, +}; + +struct zotac_gamepad { + struct input_dev *dev; /* input device interface */ + struct zotac_device *zotac; /* back-pointer to parent zotac device */ + bool disconnect; /* set when device disconnected */ + + struct usb_endpoint_descriptor *ep_in; + struct usb_endpoint_descriptor *ep_out; + + struct urb *urbs[ZOTAC_NUM_URBS]; + unsigned char *urb_buf[ZOTAC_NUM_URBS]; + + struct urb *ff_urbs[ZOTAC_NUM_FF_URBS]; + unsigned char *ff_data[ZOTAC_NUM_FF_URBS]; + dma_addr_t ff_dma[ZOTAC_NUM_FF_URBS]; + atomic_t ff_active[ZOTAC_NUM_FF_URBS]; + + atomic_t button_press_in_progress; + + struct delayed_work button_work; + unsigned int button_to_press; + unsigned int button_to_press2; + bool update_qam; + enum qam_mode qam_mode; +}; + +struct zotac_rgb_dev { + struct zotac_device *zotac; + struct hid_device *hdev; + struct led_classdev_mc led_rgb_dev; + struct work_struct work; + bool output_worker_initialized; + spinlock_t lock; + + bool removed; + bool update_rgb; + uint8_t red[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t green[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t blue[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t brightness; +}; + +struct zotac_rgb_data { + struct { + uint8_t red[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t green[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t blue[ZOTAC_RGB_LEDS_PER_ZONE]; + uint8_t brightness; // MC LED class level brightness + } zone[ZOTAC_RGB_ZONE_COUNT]; + uint8_t effect; // Global effect + uint8_t speed; // Global speed + uint8_t brightness; // Global brightness + bool initialized; +}; + +struct stick_sensitivity { + /* X1, Y1, X2, Y2, X3, Y3, X4, Y4 */ + u8 values[SENSITIVITY_POINT_COUNT * 2]; +}; + +struct deadzone { + u8 inner; + u8 outer; +}; + +struct button_mapping { + u32 target_gamepad_buttons; /* Bit field for controller buttons */ + u8 target_modifier_keys; /* Bit field for modifier keys */ + u8 target_keyboard_keys[MAX_KEYBOARD_KEYS]; /* Array of keyboard key codes */ + u8 target_mouse_buttons; /* Bit field for mouse buttons */ +}; + +struct zotac_cfg_data { + struct mutex command_mutex; + u8 sequence_num; + /* deadzones */ + struct deadzone ls_dz; // left stick + struct deadzone rs_dz; // right stick + struct deadzone lt_dz; // left trigger + struct deadzone rt_dz; // right trigger + struct stick_sensitivity left_stick_sensitivity; + struct stick_sensitivity right_stick_sensitivity; + u8 button_turbo; + /* Indexed by the button number */ + struct button_mapping button_mappings[BUTTON_MAX+1]; +}; + +struct zotac_device { + struct hid_device *hdev; + struct input_dev *wheel_input; + struct input_dev *mouse_input; + struct zotac_gamepad *gamepad; + struct usb_device *udev; + struct zotac_cfg_data *cfg_data; + struct zotac_rgb_dev *led_rgb_dev; + struct zotac_rgb_data led_rgb_data; +}; +extern struct zotac_device zotac; + +void zotac_init_input_device(struct input_dev *input_dev, + struct hid_device *hdev, const char *name); + +struct usb_interface *zotac_get_usb_interface(struct hid_device *hdev); + +int zotac_init_gamepad(struct zotac_device *zotac, struct usb_interface *intf); + +void zotac_process_gamepad_report(struct zotac_device *zotac, u8 *data, + int size); + +void zotac_cleanup_gamepad(struct zotac_device *zotac); + +void zotac_gamepad_send_button(struct zotac_device *zotac, int buttons[], + int num_buttons); + +int zotac_cfg_init(struct zotac_device *zotac); + +void zotac_cfg_cleanup(struct zotac_device *zotac); + +int zotac_register_sysfs(struct zotac_device *zotac); + +void zotac_unregister_sysfs(struct zotac_device *zotac); + +int zotac_send_get_command(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *req_data, size_t req_data_len, + u8 *output_data, size_t *output_len); + +int zotac_send_set_command(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *data, size_t data_len); + +int zotac_send_get_byte(struct zotac_device *zotac, u8 cmd_code, u8 setting, + const u8 *req_data, size_t req_data_len); + +/* RGB LED functions */ +int zotac_rgb_init(struct zotac_device *zotac); +void zotac_rgb_cleanup(struct zotac_device *zotac); +void zotac_rgb_resume(struct zotac_device *zotac); +void zotac_rgb_suspend(struct zotac_device *zotac); + +#endif /* __HID_ZOTAC_ZONE_H */ diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 0dc60c793cf1..fefabe42f8ec 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2218,6 +2218,17 @@ config SENSORS_SCH5636 This driver can also be built as a module. If so, the module will be called sch5636. +config SENSORS_STEAMDECK + tristate "Steam Deck EC sensors" + depends on MFD_STEAMDECK + help + If you say yes here you get support for the hardware + monitoring features exposed by EC firmware on Steam Deck + devices + + This driver can also be built as a module. If so, the module + will be called steamdeck-hwmon. + config SENSORS_STTS751 tristate "ST Microelectronics STTS751" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 1f886b38ff89..e7d19340be84 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -224,6 +224,7 @@ obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o +obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o obj-$(CONFIG_SENSORS_SURFACE_TEMP)+= surface_temp.o diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index ed212615b2fe..b78a3aaa70cc 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -155,6 +155,14 @@ static const struct dmi_system_id dmi_table[] = { }, .driver_data = &gpd_win4_drvdata, }, + { + // GPD Win 5 with AMD AI MAX 395 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-05"), + }, + .driver_data = &gpd_duo_drvdata, + }, { // GPD Win 4 with Ryzen 7840U .matches = { diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c new file mode 100644 index 000000000000..9d0a5471b181 --- /dev/null +++ b/drivers/hwmon/steamdeck-hwmon.c @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Steam Deck EC sensors driver + * + * Copyright (C) 2021-2022 Valve Corporation + */ + +#include +#include +#include + +#define STEAMDECK_HWMON_NAME "steamdeck-hwmon" + +struct steamdeck_hwmon { + struct acpi_device *adev; +}; + +static long +steamdeck_hwmon_get(struct steamdeck_hwmon *sd, const char *method) +{ + unsigned long long val; + if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle, + (char *)method, NULL, &val))) + return -EIO; + + return val; +} + +static int +steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *out) +{ + struct steamdeck_hwmon *sd = dev_get_drvdata(dev); + + switch (type) { + case hwmon_curr: + if (attr != hwmon_curr_input) + return -EOPNOTSUPP; + + *out = steamdeck_hwmon_get(sd, "PDAM"); + if (*out < 0) + return *out; + break; + case hwmon_in: + if (attr != hwmon_in_input) + return -EOPNOTSUPP; + + *out = steamdeck_hwmon_get(sd, "PDVL"); + if (*out < 0) + return *out; + break; + case hwmon_temp: + if (attr != hwmon_temp_input) + return -EOPNOTSUPP; + + *out = steamdeck_hwmon_get(sd, "BATT"); + if (*out < 0) + return *out; + /* + * Assuming BATT returns deg C we need to mutiply it + * by 1000 to convert to mC + */ + *out *= 1000; + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + *out = steamdeck_hwmon_get(sd, "FANR"); + if (*out < 0) + return *out; + break; + case hwmon_fan_target: + *out = steamdeck_hwmon_get(sd, "FSSR"); + if (*out < 0) + return *out; + break; + case hwmon_fan_fault: + *out = steamdeck_hwmon_get(sd, "FANC"); + if (*out < 0) + return *out; + /* + * FANC (Fan check): + * 0: Abnormal + * 1: Normal + */ + *out = !*out; + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int +steamdeck_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + switch (type) { + /* + * These two aren't, strictly speaking, measured. EC + * firmware just reports what PD negotiation resulted + * in. + */ + case hwmon_curr: + *str = "PD Contract Current"; + break; + case hwmon_in: + *str = "PD Contract Voltage"; + break; + case hwmon_temp: + *str = "Battery Temp"; + break; + case hwmon_fan: + *str = "System Fan"; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int +steamdeck_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + struct steamdeck_hwmon *sd = dev_get_drvdata(dev); + + if (type != hwmon_fan || + attr != hwmon_fan_target) + return -EOPNOTSUPP; + + val = clamp_val(val, 0, 7300); + + if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, + "FANS", val))) + return -EIO; + + return 0; +} + +static umode_t +steamdeck_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_fan && + attr == hwmon_fan_target) + return 0644; + + return 0444; +} + +static const struct hwmon_channel_info *steamdeck_hwmon_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL), + HWMON_CHANNEL_INFO(temp, + HWMON_T_INPUT | HWMON_T_LABEL), + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL | + HWMON_F_TARGET | HWMON_F_FAULT), + NULL +}; + +static const struct hwmon_ops steamdeck_hwmon_ops = { + .is_visible = steamdeck_hwmon_is_visible, + .read = steamdeck_hwmon_read, + .read_string = steamdeck_hwmon_read_string, + .write = steamdeck_hwmon_write, +}; + +static const struct hwmon_chip_info steamdeck_hwmon_chip_info = { + .ops = &steamdeck_hwmon_ops, + .info = steamdeck_hwmon_info, +}; + + +static ssize_t +steamdeck_hwmon_simple_store(struct device *dev, const char *buf, size_t count, + const char *method, + unsigned long upper_limit) +{ + struct steamdeck_hwmon *sd = dev_get_drvdata(dev); + unsigned long value; + + if (kstrtoul(buf, 10, &value) || value >= upper_limit) + return -EINVAL; + + if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, + (char *)method, value))) + return -EIO; + + return count; +} + +static ssize_t +steamdeck_hwmon_simple_show(struct device *dev, char *buf, + const char *method) +{ + struct steamdeck_hwmon *sd = dev_get_drvdata(dev); + unsigned long value; + + value = steamdeck_hwmon_get(sd, method); + if (value < 0) + return value; + + return sprintf(buf, "%ld\n", value); +} + +#define STEAMDECK_HWMON_ATTR_RW(_name, _set_method, _get_method, \ + _upper_limit) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + return steamdeck_hwmon_simple_show(dev, buf, \ + _get_method); \ + } \ + static ssize_t _name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + return steamdeck_hwmon_simple_store(dev, buf, count, \ + _set_method, \ + _upper_limit); \ + } \ + static DEVICE_ATTR_RW(_name) + +STEAMDECK_HWMON_ATTR_RW(max_battery_charge_level, "FCBL", "SFBL", 101); +STEAMDECK_HWMON_ATTR_RW(max_battery_charge_rate, "CHGR", "GCHR", 101); + +static struct attribute *steamdeck_hwmon_attributes[] = { + &dev_attr_max_battery_charge_level.attr, + &dev_attr_max_battery_charge_rate.attr, + NULL +}; + +static const struct attribute_group steamdeck_hwmon_group = { + .attrs = steamdeck_hwmon_attributes, +}; + +static const struct attribute_group *steamdeck_hwmon_groups[] = { + &steamdeck_hwmon_group, + NULL +}; + +static int steamdeck_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct steamdeck_hwmon *sd; + struct device *hwmon; + + sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + + sd->adev = ACPI_COMPANION(dev->parent); + hwmon = devm_hwmon_device_register_with_info(dev, + "steamdeck_hwmon", + sd, + &steamdeck_hwmon_chip_info, + steamdeck_hwmon_groups); + if (IS_ERR(hwmon)) { + dev_err(dev, "Failed to register HWMON device"); + return PTR_ERR(hwmon); + } + + return 0; +} + +static const struct platform_device_id steamdeck_hwmon_id_table[] = { + { .name = STEAMDECK_HWMON_NAME }, + {} +}; +MODULE_DEVICE_TABLE(platform, steamdeck_hwmon_id_table); + +static struct platform_driver steamdeck_hwmon_driver = { + .probe = steamdeck_hwmon_probe, + .driver = { + .name = STEAMDECK_HWMON_NAME, + }, + .id_table = steamdeck_hwmon_id_table, +}; +module_platform_driver(steamdeck_hwmon_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Steam Deck EC sensors driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d35456994280..964a4cbbf478 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -203,7 +203,7 @@ config I2C_ISMT config I2C_PIIX4 tristate "Intel PIIX4 and compatible (ATI/AMD/Serverworks/Broadcom/SMSC)" - depends on PCI && HAS_IOPORT + depends on PCI && HAS_IOPORT && X86 select I2C_SMBUS help If you say yes to this option, support will be included for the Intel diff --git a/drivers/input/input.c b/drivers/input/input.c index cf6fecea79b8..f4fe3d93e535 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "input-compat.h" #include "input-core-private.h" #include "input-poller.h" @@ -362,6 +363,17 @@ void input_handle_event(struct input_dev *dev, lockdep_assert_held(&dev->event_lock); + /* + * During hibernation/suspend, consume KEY_POWER events in the kernel + * to cancel the sleep transition. Don't forward to userspace to prevent + * userspace (e.g., systemd-logind) from triggering another suspend or + * poweroff action after the system wakes up. + */ + if (code == KEY_POWER && pm_sleep_transition_in_progress()) { + pm_wakeup_dev_event(&dev->dev, 0, true); + return; + } + disposition = input_get_disposition(dev, type, code, &value); if (disposition != INPUT_IGNORE_EVENT) { if (type != EV_SYN) diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 2da0b7f1722a..ac47326b5b70 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -331,7 +331,9 @@ static const struct xpad_device { { 0x1bad, 0xfa01, "MadCatz GamePad", 0, XTYPE_XBOX360 }, { 0x1bad, 0xfd00, "Razer Onza TE", 0, XTYPE_XBOX360 }, { 0x1bad, 0xfd01, "Razer Onza", 0, XTYPE_XBOX360 }, + #if !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) { 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 }, + #endif /* !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) */ { 0x20bc, 0x5134, "BETOP BTP-KP50B Xinput Dongle", 0, XTYPE_XBOX360 }, { 0x20bc, 0x514a, "BETOP BTP-KP50C Xinput Dongle", 0, XTYPE_XBOX360 }, { 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE }, @@ -538,7 +540,9 @@ static const struct usb_device_id xpad_table[] = { XPAD_XBOX360_VENDOR(0x1949), /* Amazon controllers */ XPAD_XBOX360_VENDOR(0x1a86), /* Nanjing Qinheng Microelectronics (WCH) */ XPAD_XBOX360_VENDOR(0x1bad), /* Harmonix Rock Band guitar and drums */ + #if !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */ + #endif /* !IS_REACHABLE(CONFIG_ZOTAC_ZONE_HID) */ XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */ XPAD_XBOX360_VENDOR(0x20d6), /* PowerA controllers */ XPAD_XBOXONE_VENDOR(0x20d6), /* PowerA controllers */ diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c index d32fa4b508fc..a193c191df12 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c @@ -194,7 +194,11 @@ static int uinput_request_submit(struct uinput_device *udev, if (retval) goto out; - if (!wait_for_completion_timeout(&request->done, 30 * HZ)) { + retval = wait_for_completion_interruptible_timeout(&request->done, 30 * HZ); + if (retval == -ERESTARTSYS) + goto out; + + if (!retval) { retval = -ETIMEDOUT; goto out; } diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c870..c4bbc6175303 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1028,6 +1028,13 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_STEAMDECK + tristate "LED support for Steam Deck" + depends on LEDS_CLASS && MFD_STEAMDECK + help + This option enabled support for the status LED (next to the + power button) on Steam Deck + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7db3768912ca..2b8132b9916a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o +obj-$(CONFIG_LEDS_STEAMDECK) += leds-steamdeck.o obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o diff --git a/drivers/leds/leds-steamdeck.c b/drivers/leds/leds-steamdeck.c new file mode 100644 index 000000000000..ada9fffc0a42 --- /dev/null +++ b/drivers/leds/leds-steamdeck.c @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Steam Deck EC MFD LED cell driver + * + * Copyright (C) 2021-2022 Valve Corporation + * + */ + +#include +#include +#include + +struct steamdeck_led { + struct acpi_device *adev; + struct led_classdev cdev; +}; + +static ssize_t led_brightness_multiplier_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led, + cdev); + unsigned long long led_brightness_multiplier; + + if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle, + "GLDM", + NULL, + &led_brightness_multiplier))) + return -EIO; + + + return sprintf(buf, "%llu", led_brightness_multiplier); +} + +static ssize_t led_brightness_multiplier_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led, + cdev); + unsigned long value; + + if (kstrtoul(buf, 10, &value) || value > 100) + return -EINVAL; + + + if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, + "SLDM", value))) + return -EIO; + + + return count; +} + +static DEVICE_ATTR_RW(led_brightness_multiplier); + +static struct attribute *steamdeck_led_attrs[] = { + &dev_attr_led_brightness_multiplier.attr, + NULL +}; +ATTRIBUTE_GROUPS(steamdeck_led); + +static int steamdeck_leds_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led, + cdev); + + if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, + "CHBV", value))) + return -EIO; + + return 0; +} + +static int steamdeck_leds_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct steamdeck_led *sd; + int ret; + + sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + + sd->adev = ACPI_COMPANION(dev->parent); + + sd->cdev.name = "status:white"; + sd->cdev.brightness_set_blocking = steamdeck_leds_brightness_set; + sd->cdev.max_brightness = 100; + sd->cdev.groups = steamdeck_led_groups; + + ret = devm_led_classdev_register(dev, &sd->cdev); + if (ret) { + dev_err(dev, "Failed to register LEDs device: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct platform_device_id steamdeck_leds_id_table[] = { + { .name = "steamdeck-leds" }, + {} +}; +MODULE_DEVICE_TABLE(platform, steamdeck_leds_id_table); + +static struct platform_driver steamdeck_leds_driver = { + .probe = steamdeck_leds_probe, + .driver = { + .name = "steamdeck-leds", + }, + .id_table = steamdeck_leds_id_table, +}; +module_platform_driver(steamdeck_leds_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Steam Deck LEDs driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 6e9ab5f60714..b91d6be11cc5 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -124,4 +124,13 @@ config LEDS_MT6370_RGB This driver can also be built as a module. If so, the module will be called "leds-mt6370-rgb". +config LEDS_VALVE + tristate "LED Support for Valve Freemont" + help + Say Y here to enable support for Valve Freemont LED strip device. + It is a strip LED controller with 17 RGB LEDs. + + This driver can also be built as a module. If so, the module + will be called "leds-valve". + endif # LEDS_CLASS_MULTICOLOR diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index cc0f2df66286..64cb3db6fb67 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o obj-$(CONFIG_LEDS_S2M_RGB) += leds-s2m-rgb.o obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o +obj-$(CONFIG_LEDS_VALVE) += leds-valve.o diff --git a/drivers/leds/rgb/leds-valve.c b/drivers/leds/rgb/leds-valve.c new file mode 100644 index 000000000000..af5205ce37e2 --- /dev/null +++ b/drivers/leds/rgb/leds-valve.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Valve LEDs driver + * + * Copyright (C) 2025 Valve Corporation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRVNAME "valve-leds" + +#define VALVE_NUM_LEDS 17 +#define VALVE_NUM_COMPONENTS 3 +#define VALVE_BRIGHTNESS_DEFAULT 255 +#define VALVE_BRIGHTNESS_HALF 56 /* Gamma 2.2 */ +#define VALVE_BRIGHTNESS_MAX 255 +#define VALVE_INTENSITY_DEFAULT 255 +#define VALVE_DELAY_RANGE_MIN 0 +#define VALVE_DELAY_RANGE_MAX 20 +#define VALVE_BIOS_CTRL_ENABLE_BIT BIT(0) +#define VALVE_BRIGHTNESS_CTRL_SCALE_BIT BIT(0) +#define VALVE_BRIGHTNESS_CTRL_PWRBTN_BIT BIT(1) + +#define VALVE_PORT_EC_CMD 0x6c +#define VALVE_EC_CMD_COMMIT_SETTINGS 0xc6 + +#define VALVE_PORT_BASE 0xde8 +#define VALVE_PORT_BIOS_LED_CTRL (0xde8 - VALVE_PORT_BASE) +#define VALVE_PORT_INTENSITY_STARTUP (0xde9 - VALVE_PORT_BASE) +#define VALVE_PORT_BRIGHTNESS_STARTUP (0xdec - VALVE_PORT_BASE) +#define VALVE_PORT_STRIP_ENABLE (0xdef - VALVE_PORT_BASE) +#define VALVE_PORT_INTENSITY (0xe39 - VALVE_PORT_BASE) +#define VALVE_PORT_MODE (0xe6c - VALVE_PORT_BASE) +#define VALVE_PORT_DELAY (0xe6e - VALVE_PORT_BASE) +#define VALVE_PORT_BREATH_OFFSET (0xe6f - VALVE_PORT_BASE) +#define VALVE_PORT_BREATH_LEVEL (0xe70 - VALVE_PORT_BASE) +#define VALVE_PORT_PATROL_NUM (0xe71 - VALVE_PORT_BASE) +#define VALVE_PORT_COLOR_SHIFT (0xe75 - VALVE_PORT_BASE) +#define VALVE_PORT_BRIGHTNESS_CTRL (0xe78 - VALVE_PORT_BASE) +#define VALVE_PORT_BRIGHTNESS_SCALE (0xe79 - VALVE_PORT_BASE) +#define VALVE_PORT_BRIGHTNESS_PWRBTN (0xe7a - VALVE_PORT_BASE) +#define VALVE_NR_PORTS (VALVE_PORT_BRIGHTNESS_PWRBTN + 1) + +#define VALVE_PORT_STRIDE 3 +#define VALVE_LED_PORT(n) (VALVE_PORT_INTENSITY + n * VALVE_PORT_STRIDE) + +static bool test_interface; +module_param(test_interface, bool, 0644); +MODULE_PARM_DESC(test_interface, "Just test the interface, don't write LEDs"); + +static int test_reg_write(void *ctx, unsigned int reg, unsigned int val) +{ + pr_info(DRVNAME " %s(): 0x%03x=0x%x\n", __func__, VALVE_PORT_BASE + reg, val); + return 0; +} + +static int test_raw_write(void *ctx, const void *data, size_t count) +{ + int i; + const u8 *bytes = data; + + pr_info(DRVNAME " %s(): 0x%03x: ", __func__, VALVE_PORT_BASE + bytes[0]); + for (i = 1; i < count; i++) + pr_cont("%02x ", bytes[i]); + pr_cont("\n"); + + return 0; +} + +static int test_reg_read(void *ctx, unsigned int reg, unsigned int *val) +{ + *val = 0; + pr_info(DRVNAME " %s(): 0x%03x\n", __func__, VALVE_PORT_BASE + reg); + return 0; +} + +static int test_raw_read(void *ctx, const void *reg_buf, size_t reg_size, void *val_buf, size_t val_size) +{ + int i; + const u8 *regs = reg_buf; + + pr_info(DRVNAME " %s(): ", __func__); + for (i = 0; i < reg_size; i++) + pr_cont("%03x ", VALVE_PORT_BASE + regs[i]); + pr_cont("\n"); + + return 0; +} + +static const struct regmap_bus test_bus = { + .reg_write = test_reg_write, + .write = test_raw_write, + .reg_read = test_reg_read, + .read = test_raw_read, +}; + +struct valve_leds { + struct platform_device *pdev; + struct regmap *regmap; + struct valve_led { + struct led_classdev_mc mcdev; + struct mc_subled rgb[VALVE_NUM_COMPONENTS]; + int index; + } leds[VALVE_NUM_LEDS]; + + int enabled; + int effect_index; + int delay; + int breath_offset; + int breath_level; + int patrol_num; + int color_shift; + int brightness_ctrl; + int brightness_scale; +}; + +static struct platform_device *pdev; + +static const char *const effect_names[] = { + "patrol", + "breath", + "factory", + "normal", + "off", + "rainbow", + "demo", + "manual", +}; + +static void valve_leds_send_ec_cmd(unsigned char cmd) +{ + outb(cmd, VALVE_PORT_EC_CMD); +} + +static int valve_leds_has_persistence(struct valve_leds *leds, bool *out) +{ + unsigned int val; + int ret; + + /* + * Read the LED control register, and determine if persistent BIOS control + * is enabled. + */ + ret = regmap_read(leds->regmap, VALVE_PORT_BIOS_LED_CTRL, &val); + if (ret) + return ret; + + *out = (val & VALVE_BIOS_CTRL_ENABLE_BIT) != 0; + + return 0; +} + +static int valve_leds_ensure_persistence(struct device *dev) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + unsigned int val; + int ret; + + /* Read the LED control register. */ + ret = regmap_read(leds->regmap, VALVE_PORT_BIOS_LED_CTRL, &val); + if (ret) + return ret; + + /* Ensure that persistent BIOS control is enabled. */ + if ((val & VALVE_BIOS_CTRL_ENABLE_BIT) == 0) { + ret = regmap_write(leds->regmap, VALVE_PORT_BIOS_LED_CTRL, val | VALVE_BIOS_CTRL_ENABLE_BIT); + if (ret) { + dev_err(dev, "Failed to enable persistent LED control\n"); + return ret; + } + } + + /* Success. */ + return 0; +} + +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + unsigned int val; + ssize_t len = 0; + int ret; + + ret = regmap_read(leds->regmap, VALVE_PORT_STRIP_ENABLE, &val); + if (ret) + return ret; + + len = sysfs_emit(buf, "%d\n", val); + + return len; +} + +static ssize_t enabled_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + int ret; + bool enabled; + + ret = kstrtobool(buf, &enabled); + if (ret < 0) + return ret; + + ret = regmap_write(leds->regmap, VALVE_PORT_STRIP_ENABLE, (int)enabled); + if (ret) + return ret; + + return count; +} + +static ssize_t effect_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + unsigned int mode; + ssize_t len = 0; + int ret; + + ret = regmap_read(leds->regmap, VALVE_PORT_MODE, &mode); + if (ret) + return ret; + + if (mode >= 0 && mode < ARRAY_SIZE(effect_names)) + len = sysfs_emit(buf, "%s\n", effect_names[mode]); + + return len; +} + +static ssize_t effect_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + u8 rgb[VALVE_NUM_LEDS * VALVE_PORT_STRIDE]; + int led, comp, mode, ret; + + mode = __sysfs_match_string(effect_names, ARRAY_SIZE(effect_names), buf); + if (mode < 0) + return mode; + + ret = regmap_write(leds->regmap, VALVE_PORT_MODE, mode); + if (ret) + return ret; + + /* + * Some effects animate and clobber colors, thus re-write colors after + * setting the mode. + */ + for (led = 0; led < VALVE_NUM_LEDS; led++) + for (comp = 0; comp < VALVE_NUM_COMPONENTS; comp++) + rgb[led * VALVE_NUM_COMPONENTS + comp] = leds->leds[led].rgb[comp].intensity; + + ret = regmap_bulk_write(leds->regmap, VALVE_LED_PORT(0), rgb, ARRAY_SIZE(rgb)); + if (ret) + return ret; + + return count; +} + +static ssize_t effect_index_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(effect_names); i++) + len += sysfs_emit_at(buf, len, "%s ", effect_names[i]); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t delay_range_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const ssize_t len = sysfs_emit(buf, "%d-%d ", VALVE_DELAY_RANGE_MIN, + VALVE_DELAY_RANGE_MAX); + + buf[len - 1] = '\n'; + + return len; +} + +static ssize_t brightness_startup_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + unsigned int val; + bool enabled; + int ret; + + /* + * If persistent BIOS control is enabled return the startup brightness + * register value. + * Otherwise return the default value, which is half brightness. + */ + ret = valve_leds_has_persistence(leds, &enabled); + if (ret) + return ret; + + if (enabled) { + ret = regmap_read(leds->regmap, VALVE_PORT_BRIGHTNESS_STARTUP, &val); + if (ret) + return ret; + } else { + val = VALVE_BRIGHTNESS_HALF; + } + + return sysfs_emit(buf, "0x%02x\n", val); +} + +static ssize_t brightness_startup_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + /* Ensure that persistence is enabled. */ + ret = valve_leds_ensure_persistence(dev); + if (ret) + return ret; + + /* Write the persistent startup brightness value. */ + ret = regmap_write(leds->regmap, VALVE_PORT_BRIGHTNESS_STARTUP, val); + if (ret) + return ret; + + /* Command the EC to commit the new brightness value to non-volatile storage. */ + valve_leds_send_ec_cmd(VALVE_EC_CMD_COMMIT_SETTINGS); + + return count; +} + +static ssize_t multi_intensity_startup_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + bool enabled; + u8 rgb[VALVE_NUM_COMPONENTS] = { 0, 0, 255 }; /* pure blue by default */ + int ret; + + /* + * If persistent BIOS control is enabled return the startup color register + * values. + * Otherwise return the default values. + */ + ret = valve_leds_has_persistence(leds, &enabled); + if (ret) + return ret; + + if (enabled) { + ret = regmap_bulk_read(leds->regmap, VALVE_PORT_INTENSITY_STARTUP, rgb, ARRAY_SIZE(rgb)); + if (ret) + return ret; + } + + return sysfs_emit(buf, "%hhu %hhu %hhu\n", rgb[0], rgb[1], rgb[2]); +} + +static ssize_t multi_intensity_startup_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + u8 rgb[VALVE_NUM_COMPONENTS]; + int ret; + + ret = sscanf(buf, "%hhu %hhu %hhu", &rgb[0], &rgb[1], &rgb[2]); + if (ret != 3) + return -EINVAL; + + /* Ensure that persistence is enabled. */ + ret = valve_leds_ensure_persistence(dev); + if (ret) + return ret; + + /* Write the persistent startup color values. */ + ret = regmap_bulk_write(leds->regmap, VALVE_PORT_INTENSITY_STARTUP, rgb, VALVE_NUM_COMPONENTS); + if (ret) + return ret; + + /* Command the EC to commit the new values to non-volatile storage. */ + valve_leds_send_ec_cmd(VALVE_EC_CMD_COMMIT_SETTINGS); + + return count; +} + +static DEVICE_ATTR_RW(enabled); +static DEVICE_ATTR_RW(effect); +static DEVICE_ATTR_RO(effect_index); +static DEVICE_ATTR_RO(delay_range); +static DEVICE_ATTR_RW(brightness_startup); +static DEVICE_ATTR_RW(multi_intensity_startup); + +static ssize_t byte_reg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + struct dev_ext_attribute *ext = container_of(attr, typeof(*ext), attr); + unsigned int port = (long)ext->var; + unsigned int val; + int ret; + + ret = regmap_read(leds->regmap, port, &val); + if (ret) + return ret; + + return sysfs_emit(buf, "0x%02x\n", val); +} + +static ssize_t byte_reg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + struct valve_leds *leds = dev_get_drvdata(dev->parent); + struct dev_ext_attribute *ext = container_of(attr, typeof(*ext), attr); + unsigned int port = (long)ext->var; + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret) + return ret; + + ret = regmap_write(leds->regmap, port, val); + if (ret) + return ret; + + return size; +} + +#define VALVE_LEDS_BYTE_ATTR(_name, _mode, _port) \ + struct dev_ext_attribute dev_attr_##_name = \ + { __ATTR(_name, _mode, byte_reg_show, byte_reg_store), ((void *)_port) } + +VALVE_LEDS_BYTE_ATTR(delay, 0644, VALVE_PORT_DELAY); +VALVE_LEDS_BYTE_ATTR(breath_offset, 0644, VALVE_PORT_BREATH_OFFSET); +VALVE_LEDS_BYTE_ATTR(breath_level, 0644, VALVE_PORT_BREATH_LEVEL); +VALVE_LEDS_BYTE_ATTR(patrol_num, 0644, VALVE_PORT_PATROL_NUM); +VALVE_LEDS_BYTE_ATTR(color_shift, 0644, VALVE_PORT_COLOR_SHIFT); +VALVE_LEDS_BYTE_ATTR(brightness_scale, 0644, VALVE_PORT_BRIGHTNESS_SCALE); + +static struct attribute *valve_leds_attrs[] = { + &dev_attr_effect.attr, + &dev_attr_effect_index.attr, + &dev_attr_enabled.attr, + &dev_attr_delay.attr.attr, + &dev_attr_delay_range.attr, + &dev_attr_breath_offset.attr.attr, + &dev_attr_breath_level.attr.attr, + &dev_attr_patrol_num.attr.attr, + &dev_attr_color_shift.attr.attr, + &dev_attr_brightness_scale.attr.attr, + &dev_attr_brightness_startup.attr, + &dev_attr_multi_intensity_startup.attr, + NULL, +}; +static const struct attribute_group valve_leds_attr_group = { + .attrs = valve_leds_attrs, +}; + +static int valve_leds_init_brightness_control(struct platform_device *pdev, struct valve_leds *leds) +{ + unsigned int initval = VALVE_BRIGHTNESS_HALF; + unsigned int val; + int ret; + + /* + * Read the default brightness from BIOS settings, if BIOS control is enabled. + * If BIOS control is not enabled, fall back to half brightness. + */ + ret = regmap_read(leds->regmap, VALVE_PORT_BIOS_LED_CTRL, &val); + if (ret) + return ret; + + if ((val & VALVE_BIOS_CTRL_ENABLE_BIT) != 0) { + ret = regmap_read(leds->regmap, VALVE_PORT_BRIGHTNESS_STARTUP, &val); + if (ret) + return ret; + + initval = val; + + dev_info(&pdev->dev, "Init with BIOS set brightness: %u\n", initval); + } else { + dev_info(&pdev->dev, "No BIOS set brightness. Falling back to: %u\n", initval); + } + + /* Set the control bit to enable brightness control. */ + ret = regmap_write(leds->regmap, VALVE_PORT_BRIGHTNESS_CTRL, VALVE_BRIGHTNESS_CTRL_SCALE_BIT); + if (ret) + return ret; + + /* Initialize with the default brightness value. */ + ret = regmap_write(leds->regmap, VALVE_PORT_BRIGHTNESS_SCALE, initval); + + return ret; +} + +static int valve_leds_set_brightness(struct led_classdev *led_cdev, enum led_brightness brightness) +{ + struct valve_leds *leds = dev_get_drvdata(led_cdev->dev->parent); + struct led_classdev_mc *mcdev = lcdev_to_mccdev(led_cdev); + struct valve_led *led = container_of(mcdev, typeof(*led), mcdev); + char rgb[3]; + int ret; + + led_mc_calc_color_components(mcdev, brightness); + + rgb[0] = led->rgb[0].brightness; + rgb[1] = led->rgb[1].brightness; + rgb[2] = led->rgb[2].brightness; + + ret = regmap_bulk_write(leds->regmap, VALVE_LED_PORT(led->index), rgb, 3); + return ret; +} + + +#define VALVE_REG_SYNC_FROM_HW(leds, reg, store) do { \ + int val; \ + int ret = regmap_read(leds->regmap, reg, &val); \ + if (ret) { \ + dev_err(&leds->pdev->dev, "Failed to sync %s from hw: %d\n", __stringify(reg), ret); \ + return ret; \ + } \ + store = val; \ +} while (0) + +static int valve_leds_sync_from_hw(struct valve_leds *leds) +{ + u8 rgb[VALVE_NUM_LEDS * VALVE_PORT_STRIDE]; + u8 *port; + int ret; + int led, comp; + + ret = regmap_bulk_read(leds->regmap, VALVE_LED_PORT(0), rgb, ARRAY_SIZE(rgb)); + if (ret) { + dev_err(&leds->pdev->dev, "Failed to sync from hw: %d\n", ret); + return ret; + } + + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_STRIP_ENABLE, leds->enabled); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_MODE, leds->effect_index); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_DELAY, leds->delay); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_BREATH_OFFSET, leds->breath_offset); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_BREATH_LEVEL, leds->breath_level); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_PATROL_NUM, leds->patrol_num); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_COLOR_SHIFT, leds->color_shift); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_BRIGHTNESS_CTRL, leds->brightness_ctrl); + VALVE_REG_SYNC_FROM_HW(leds, VALVE_PORT_BRIGHTNESS_SCALE, leds->brightness_scale); + + port = rgb; + for (led = 0; led < VALVE_NUM_LEDS; led++) + for (comp = 0; comp < VALVE_NUM_COMPONENTS; comp++) + leds->leds[led].rgb[comp].intensity = *port++; + + return 0; +} + +#define VALVE_REG_SYNC_TO_HW(leds, reg, val) do { \ + int ret = regmap_write(leds->regmap, reg, val); \ + if (ret) { \ + dev_err(&leds->pdev->dev, "Failed to sync %s to hw: %d\n", __stringify(reg), ret); \ + return ret; \ + } \ +} while (0) + +static int valve_leds_sync_to_hw(struct valve_leds *leds) +{ + u8 rgb[VALVE_NUM_LEDS * VALVE_PORT_STRIDE]; + u8 *port; + int ret; + int led, comp; + + port = &rgb[0]; + for (led = 0; led < VALVE_NUM_LEDS; led++) + for (comp = 0; comp < VALVE_NUM_COMPONENTS; comp++) + *port++ = leds->leds[led].rgb[comp].intensity; + + ret = regmap_bulk_write(leds->regmap, VALVE_LED_PORT(0), rgb, ARRAY_SIZE(rgb)); + if (ret) { + dev_err(&leds->pdev->dev, "Failed to sync to hw: %d\n", ret); + return ret; + } + + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_DELAY, leds->delay); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_BREATH_OFFSET, leds->breath_offset); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_BREATH_LEVEL, leds->breath_level); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_PATROL_NUM, leds->patrol_num); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_COLOR_SHIFT, leds->color_shift); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_BRIGHTNESS_CTRL, leds->brightness_ctrl); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_BRIGHTNESS_SCALE, leds->brightness_scale); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_MODE, leds->effect_index); + VALVE_REG_SYNC_TO_HW(leds, VALVE_PORT_STRIP_ENABLE, leds->enabled); + + return 0; +} + +static int valve_leds_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct valve_leds *leds = platform_get_drvdata(pdev); + + return valve_leds_sync_from_hw(leds); +} + +static int valve_leds_resume(struct platform_device *pdev) +{ + struct valve_leds *leds = platform_get_drvdata(pdev); + + return valve_leds_sync_to_hw(leds); +} + +static const struct dmi_system_id valve_leds_dmi_table[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEM"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "F7F"), + }, + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Fremont"), + }, + }, + {} +}; + +static int valve_leds_probe(struct platform_device *pdev) +{ + struct valve_leds *vleds; + int i, c; + int ret; + void __iomem *ports; + + static const struct regmap_config cfg = { + .name = "leds", + .reg_bits = 8, + .val_bits = 8, + .io_port = true, + }; + + if (!test_interface && !dmi_check_system(valve_leds_dmi_table)) + return -ENODEV; + + vleds = devm_kzalloc(&pdev->dev, sizeof(*vleds), GFP_KERNEL); + if (!vleds) + return -ENOMEM; + + platform_set_drvdata(pdev, vleds); + + if (test_interface) { + vleds->regmap = devm_regmap_init(&pdev->dev, &test_bus, NULL, &cfg); + } else { + if (!devm_request_region(&pdev->dev, VALVE_PORT_BASE, VALVE_NR_PORTS, DRVNAME)) + return -EBUSY; + + ports = devm_ioport_map(&pdev->dev, VALVE_PORT_BASE, VALVE_NR_PORTS); + if (!ports) + return -ENOMEM; + + vleds->regmap = devm_regmap_init_mmio(&pdev->dev, ports, &cfg); + + /* Request the EC command I/O port. */ + if (!devm_request_region(&pdev->dev, VALVE_PORT_EC_CMD, 1, DRVNAME)) { + dev_err(&pdev->dev, "Failed to request EC cmd i/o port\n"); + return -EBUSY; + } + } + + if (IS_ERR(vleds->regmap)) { + dev_err(&pdev->dev, "Failed to init regmap\n"); + return PTR_ERR(vleds->regmap); + } + + vleds->pdev = pdev; + + ret = valve_leds_sync_from_hw(vleds); + if (ret) { + dev_err(&pdev->dev, "%s(): Failed to read led state: %d\n", __func__, ret); + return ret; + } + + for (i = 0; i < VALVE_NUM_LEDS; i++) { + for (c = 0; c < VALVE_NUM_COMPONENTS; c++) { + vleds->leds[i].rgb[c].color_index = LED_COLOR_ID_RED + c; + vleds->leds[i].rgb[c].brightness = VALVE_BRIGHTNESS_DEFAULT; + vleds->leds[i].rgb[c].channel = c; + } + + vleds->leds[i].index = i; + vleds->leds[i].mcdev.led_cdev.name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s[%d]", DRVNAME, i); + vleds->leds[i].mcdev.led_cdev.brightness = VALVE_BRIGHTNESS_DEFAULT; + vleds->leds[i].mcdev.led_cdev.max_brightness = VALVE_BRIGHTNESS_MAX; + vleds->leds[i].mcdev.num_colors = VALVE_NUM_COMPONENTS; + vleds->leds[i].mcdev.subled_info = vleds->leds[i].rgb; + vleds->leds[i].mcdev.led_cdev.brightness_set_blocking = valve_leds_set_brightness; + + ret = devm_led_classdev_multicolor_register(&pdev->dev, &vleds->leds[i].mcdev); + if (ret) + return ret; + + ret = devm_device_add_group(vleds->leds[i].mcdev.led_cdev.dev, &valve_leds_attr_group); + if (ret) + return ret; + } + + /* + * The driver takes control of LED brightness. Enable the brightness control + * that affects the entire LED strip, and initialize it with the default + * brightness value set in BIOS. + */ + ret = valve_leds_init_brightness_control(pdev, vleds); + if (ret) { + dev_err(&pdev->dev, "Failed to init brightness control\n"); + return ret; + } + + return 0; +} + +static struct platform_driver valve_leds_driver = { + .probe = valve_leds_probe, + .suspend = valve_leds_suspend, + .resume = valve_leds_resume, + .driver = { + .name = DRVNAME, + }, +}; + +static int __init valve_leds_init(void) +{ + int ret; + + ret = platform_driver_register(&valve_leds_driver); + if (ret < 0) { + pr_err("%s(): Failed to register driver: %d\n", __func__, ret); + return ret; + } + + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("%s(): Failed to register device: %ld\n", __func__, PTR_ERR(pdev)); + platform_driver_unregister(&valve_leds_driver); + return PTR_ERR(pdev); + } + + return 0; +} + +static void __exit valve_leds_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&valve_leds_driver); +} + +module_init(valve_leds_init); +module_exit(valve_leds_exit); + +MODULE_DEVICE_TABLE(dmi, valve_leds_dmi_table); +MODULE_AUTHOR("Robert Beckett "); +MODULE_DESCRIPTION("Valve LEDs driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/cec/platform/cros-ec/cros-ec-cec.c b/drivers/media/cec/platform/cros-ec/cros-ec-cec.c index 1fe9c537671d..874e26b2c14b 100644 --- a/drivers/media/cec/platform/cros-ec/cros-ec-cec.c +++ b/drivers/media/cec/platform/cros-ec/cros-ec-cec.c @@ -101,17 +101,28 @@ static void cros_ec_cec_read_message(struct cros_ec_cec_port *port) .port = port->port_num, }; struct ec_response_cec_read response; - int ret; + int ret = 0; + int count = 0; - ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, ¶ms, - sizeof(params), &response, sizeof(response)); - if (ret < 0) { + /* NOTE read as many messages as we can, return value from cros_ec_cmd + * here should either equal to the bytes transferred or a negative + * error code, so as long as we get positive integers we are + * reading messages! + */ + do { + ret = cros_ec_cmd(cros_ec, 0, EC_CMD_CEC_READ_MSG, ¶ms, + sizeof(params), &response, sizeof(response)); + if (ret > 0) { + ++count; + cros_ec_cec_received_message(port, response.msg, response.msg_len); + } + } + while (ret > 0); + + if (ret < 0 && count == 0) { dev_err(cros_ec->dev, "error reading CEC message on EC: %d\n", ret); - return; } - - cros_ec_cec_received_message(port, response.msg, response.msg_len); } static void handle_cec_event(struct cros_ec_cec *cros_ec_cec) @@ -295,12 +306,18 @@ struct cec_dmi_match { }; static const char *const port_b_conns[] = { "Port B", NULL }; +static const char *const port_c_conns[] = { "Port C", NULL }; static const char *const port_db_conns[] = { "Port D", "Port B", NULL }; static const char *const port_ba_conns[] = { "Port B", "Port A", NULL }; static const char *const port_ab_conns[] = { "Port A", "Port B", NULL }; static const char *const port_d_conns[] = { "Port D", NULL }; static const struct cec_dmi_match cec_dmi_match_table[] = { + /* AMD Lilac */ + { "AMD", "Lilac", "0000:03:00.0", port_c_conns }, + /* Valve Fremont */ + { "OEM", "F7F", "0000:03:00.0", port_c_conns }, + { "Valve", "Fremont", "0000:03:00.0", port_c_conns }, /* Google Fizz */ { "Google", "Fizz", "0000:00:02.0", port_b_conns }, /* Google Brask */ @@ -339,6 +356,8 @@ static const struct cec_dmi_match cec_dmi_match_table[] = { { "Google", "Moxoe", "0000:00:02.0", port_b_conns }, /* Google Dirkson */ { "Google", "Dirkson", "0000:00:02.0", port_ab_conns }, + /* AMD Lilac */ + { "AMD", "Lilac", "0000:06:00.0", port_c_conns }, }; static struct device *cros_ec_cec_find_hdmi_dev(struct device *dev, diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 763ce6a34782..cdd8b39aaee9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2573,5 +2573,16 @@ config MFD_MAX7360 additional drivers must be enabled in order to use the functionality of the device. +config MFD_STEAMDECK + tristate "Valve Steam Deck" + select MFD_CORE + depends on ACPI + depends on X86_64 || COMPILE_TEST + help + This driver registers various MFD cells that expose aspects + of Steam Deck specific ACPI functionality. + + Say N here, unless you are running on Steam Deck hardware. + endmenu endif diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c33..e7eab1183a8a 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -303,3 +303,5 @@ obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o + +obj-$(CONFIG_MFD_STEAMDECK) += steamdeck.o diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c new file mode 100644 index 000000000000..a60fa7db9141 --- /dev/null +++ b/drivers/mfd/steamdeck.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Steam Deck EC MFD core driver + * + * Copyright (C) 2021-2022 Valve Corporation + * + */ + +#include +#include +#include + +#define STEAMDECK_STA_OK \ + (ACPI_STA_DEVICE_ENABLED | \ + ACPI_STA_DEVICE_PRESENT | \ + ACPI_STA_DEVICE_FUNCTIONING) + +struct steamdeck { + struct acpi_device *adev; + struct device *dev; +}; + +#define STEAMDECK_ATTR_RO(_name, _method) \ + static ssize_t _name##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + struct steamdeck *sd = dev_get_drvdata(dev); \ + unsigned long long val; \ + \ + if (ACPI_FAILURE(acpi_evaluate_integer( \ + sd->adev->handle, \ + _method, NULL, &val))) \ + return -EIO; \ + \ + return sysfs_emit(buf, "%llu\n", val); \ + } \ + static DEVICE_ATTR_RO(_name) + +STEAMDECK_ATTR_RO(firmware_version, "PDFW"); +STEAMDECK_ATTR_RO(board_id, "BOID"); + +static ssize_t controller_board_power_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct steamdeck *sd = dev_get_drvdata(dev); + bool enabled; + ssize_t ret = kstrtobool(buf, &enabled); + + if (ret) + return ret; + + if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, + "SCBP", enabled))) + return -EIO; + + return count; +} +static DEVICE_ATTR_WO(controller_board_power); + +static struct attribute *steamdeck_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_board_id.attr, + &dev_attr_controller_board_power.attr, + NULL +}; + +ATTRIBUTE_GROUPS(steamdeck); + +static const struct mfd_cell steamdeck_cells[] = { + { .name = "steamdeck-hwmon" }, + { .name = "steamdeck-leds" }, + { .name = "steamdeck-extcon" }, +}; + +static void steamdeck_remove_sysfs_groups(void *data) +{ + struct steamdeck *sd = data; + + sysfs_remove_groups(&sd->dev->kobj, steamdeck_groups); +} + +static int steamdeck_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned long long sta; + struct steamdeck *sd; + acpi_status status; + int ret; + + sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + sd->adev = ACPI_COMPANION(dev); + sd->dev = dev; + platform_set_drvdata(pdev, sd); + + status = acpi_evaluate_integer(sd->adev->handle, "_STA", + NULL, &sta); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Status check failed (0x%x)\n", status); + return -EINVAL; + } + + if ((sta & STEAMDECK_STA_OK) != STEAMDECK_STA_OK) { + dev_err(dev, "Device is not ready\n"); + return -EINVAL; + } + + ret = sysfs_create_groups(&dev->kobj, steamdeck_groups); + if (ret) { + dev_err(dev, "Failed to create sysfs group\n"); + return ret; + } + + ret = devm_add_action_or_reset(dev, steamdeck_remove_sysfs_groups, + sd); + if (ret) { + dev_err(dev, "Failed to register devres action\n"); + return ret; + } + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + steamdeck_cells, ARRAY_SIZE(steamdeck_cells), + NULL, 0, NULL); +} + +static const struct acpi_device_id steamdeck_device_ids[] = { + { "VLV0100", 0 }, + { "", 0 }, +}; +MODULE_DEVICE_TABLE(acpi, steamdeck_device_ids); + +static struct platform_driver steamdeck_driver = { + .probe = steamdeck_probe, + .driver = { + .name = "steamdeck", + .acpi_match_table = steamdeck_device_ids, + }, +}; +module_platform_driver(steamdeck_driver); + +MODULE_AUTHOR("Andrey Smirnov "); +MODULE_DESCRIPTION("Steam Deck EC MFD core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/cardreader/rtsx_pcr.c b/drivers/misc/cardreader/rtsx_pcr.c index c4d54ca2fa80..0a6b09783843 100644 --- a/drivers/misc/cardreader/rtsx_pcr.c +++ b/drivers/misc/cardreader/rtsx_pcr.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "rtsx_pcr.h" #include "rts5261.h" @@ -843,6 +844,28 @@ int rtsx_pci_switch_output_voltage(struct rtsx_pcr *pcr, u8 voltage) } EXPORT_SYMBOL_GPL(rtsx_pci_switch_output_voltage); +/* Open the gate for an SD-Express handoff so runtime suspend can delink. */ +void rtsx_pci_sd_express_handoff(struct rtsx_pcr *pcr) +{ + mutex_lock(&pcr->pcr_mutex); + pcr->sd_express = true; + mutex_unlock(&pcr->pcr_mutex); +} +EXPORT_SYMBOL_GPL(rtsx_pci_sd_express_handoff); + +/* Set whether runtime suspend must keep a present card powered. The SD/MMC + * host knows the policy but may probe asynchronously after the parent, so it + * (un)sets the gate here; the gate is read live in rtsx_pci_runtime_suspend(), + * so there is no establishment race with a card already present at boot. + */ +void rtsx_pci_set_sd_pm_keepalive(struct rtsx_pcr *pcr, bool keepalive) +{ + mutex_lock(&pcr->pcr_mutex); + pcr->sd_pm_keepalive = keepalive; + mutex_unlock(&pcr->pcr_mutex); +} +EXPORT_SYMBOL_GPL(rtsx_pci_set_sd_pm_keepalive); + unsigned int rtsx_pci_card_exist(struct rtsx_pcr *pcr) { unsigned int val; @@ -914,6 +937,12 @@ static void rtsx_pci_card_detect(struct work_struct *work) pcr->card_exist &= ~card_removed; } + /* A removed SD card cancels any pending SD-Express handoff, so the gate + * protects the next card inserted into the slot. + */ + if (!(pcr->card_exist & SD_EXIST)) + pcr->sd_express = false; + mutex_unlock(&pcr->pcr_mutex); if ((card_detect & SD_EXIST) && pcr->slots[RTSX_SD_CARD].card_event) @@ -1446,6 +1475,48 @@ static int rtsx_pci_init_chip(struct rtsx_pcr *pcr) option->force_clkreq_0 = true; } + /* + * MSI Claw 8 EX AI+ (MS-1T91): the BIOS leaves ASPM L1/L1 substates + * off on the card reader's Root Port and the FADT disables OS ASPM + * management, so L1.2 never gets enabled, CLKREQ# is forced low, and + * the SoC cannot reach S0ix. Enable L1 and the substates on both + * ends ourselves; a companion quirk already made the core program + * the L1.2 timing. + */ + if (dmi_match(DMI_BOARD_NAME, "MS-1T91")) { + struct pci_dev *parent = pci_upstream_bridge(pcr->pci); + u32 l1ss_en = PCI_L1SS_CTL1_ASPM_L1_1 | PCI_L1SS_CTL1_ASPM_L1_2 | + PCI_L1SS_CTL1_PCIPM_L1_1 | PCI_L1SS_CTL1_PCIPM_L1_2; + u32 v; + + option->force_clkreq_0 = false; + option->ltr_enabled = true; + option->ltr_active = true; + rtsx_set_dev_flag(pcr, ASPM_L1_1_EN | ASPM_L1_2_EN | + PM_L1_1_EN | PM_L1_2_EN); + + /* Enable L1 substates while ASPM L1 is still disabled... */ + if (pcr->pci->l1ss) { + u16 off = pcr->pci->l1ss + PCI_L1SS_CTL1; + + pci_read_config_dword(pcr->pci, off, &v); + pci_write_config_dword(pcr->pci, off, v | l1ss_en); + } + if (parent && parent->l1ss) { + u16 off = parent->l1ss + PCI_L1SS_CTL1; + + pci_read_config_dword(parent, off, &v); + pci_write_config_dword(parent, off, v | l1ss_en); + } + /* ...then enable ASPM L1 upstream port first, then downstream. */ + if (parent) + pcie_capability_set_word(parent, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L1); + pcie_capability_set_word(pcr->pci, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L1); + pcr_dbg(pcr, "MSI Claw 8 EX AI+ quirk: enabled ASPM L1.2\n"); + } + if (pcr->ops->fetch_vendor_settings) pcr->ops->fetch_vendor_settings(pcr); @@ -1774,11 +1845,23 @@ static int rtsx_pci_runtime_suspend(struct device *device) dev_dbg(device, "--> %s\n", __func__); + /* On devices that keep the card initialised across runtime suspend + * (aggressive PM disabled), never power-cycle a card the mmc core + * still owns: stay in D0 instead. ASPM L1 was already re-enabled by + * rtsx_pci_runtime_idle(), so idle link power is still saved. + */ + mutex_lock(&pcr->pcr_mutex); + if (pcr->sd_pm_keepalive && (pcr->card_exist & SD_EXIST) && + !pcr->sd_express) { + mutex_unlock(&pcr->pcr_mutex); + return -EBUSY; + } + mutex_unlock(&pcr->pcr_mutex); + cancel_delayed_work_sync(&pcr->carddet_work); mutex_lock(&pcr->pcr_mutex); rtsx_pci_power_off(pcr, HOST_ENTER_S3, true); - mutex_unlock(&pcr->pcr_mutex); return 0; diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c index 54a923ba4f1e..6183758e8ac9 100644 --- a/drivers/mmc/core/block.c +++ b/drivers/mmc/core/block.c @@ -2977,8 +2977,11 @@ static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md) return 0; } -static void mmc_blk_remove_req(struct mmc_blk_data *md) +static void mmc_blk_remove_req(struct mmc_card *card, struct mmc_blk_data *md) { + if (mmc_card_removed(card)) + blk_mark_disk_dead(md->disk); + /* * Flush remaining requests and free queues. It is freeing the queue * that stops new requests from being accepted. @@ -3005,7 +3008,7 @@ static void mmc_blk_remove_parts(struct mmc_card *card, list_for_each_safe(pos, q, &md->part) { part_md = list_entry(pos, struct mmc_blk_data, part); list_del(pos); - mmc_blk_remove_req(part_md); + mmc_blk_remove_req(card, part_md); } } @@ -3251,7 +3254,7 @@ static int mmc_blk_probe(struct mmc_card *card) out: mmc_blk_remove_parts(card, md); - mmc_blk_remove_req(md); + mmc_blk_remove_req(card, md); out_free: destroy_workqueue(card->complete_wq); return ret; @@ -3272,7 +3275,7 @@ static void mmc_blk_remove(struct mmc_card *card) if (!mmc_card_sd_combo(card)) pm_runtime_disable(&card->dev); pm_runtime_put_noidle(&card->dev); - mmc_blk_remove_req(md); + mmc_blk_remove_req(card, md); destroy_workqueue(card->complete_wq); } diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c index b7ce3137d452..2625d918081b 100644 --- a/drivers/mmc/core/host.c +++ b/drivers/mmc/core/host.c @@ -22,6 +22,7 @@ #include #include +#include "card.h" #include "core.h" #include "crypto.h" #include "host.h" @@ -703,3 +704,17 @@ void mmc_free_host(struct mmc_host *host) } EXPORT_SYMBOL(mmc_free_host); + +/** + * mmc_host_set_removed - declare host removed + * @host: mmc host + * + * Declare the host (and any inserted card) removed and inaccessible. + */ +void mmc_host_set_removed(struct mmc_host *host) +{ + if (host->card) + mmc_card_set_removed(host->card); +} + +EXPORT_SYMBOL(mmc_host_set_removed); diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index 05444ecf3909..533f62db4242 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -829,6 +829,7 @@ MMC_DEV_ATTR(rel_sectors, "%#x\n", card->ext_csd.rel_sectors); MMC_DEV_ATTR(ocr, "0x%08x\n", card->ocr); MMC_DEV_ATTR(rca, "0x%04x\n", card->rca); MMC_DEV_ATTR(cmdq_en, "%d\n", card->ext_csd.cmdq_en); +MMC_DEV_ATTR(safe_trim_quirk, "%d\n", card->safe_trim_quirk_version); static ssize_t mmc_fwrev_show(struct device *dev, struct device_attribute *attr, @@ -888,6 +889,7 @@ static struct attribute *mmc_std_attrs[] = { &dev_attr_rca.attr, &dev_attr_dsr.attr, &dev_attr_cmdq_en.attr, + &dev_attr_safe_trim_quirk.attr, NULL, }; ATTRIBUTE_GROUPS(mmc_std); diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h index 940549d3b95d..823e409bf382 100644 --- a/drivers/mmc/core/quirks.h +++ b/drivers/mmc/core/quirks.h @@ -271,4 +271,6 @@ static inline void mmc_fixup_device(struct mmc_card *card, dev_dbg(&card->dev, "calling %ps\n", f->vendor_fixup); f->vendor_fixup(card, f->data); } + + card->safe_trim_quirk_version = 1; } diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c index 948948ca9b4a..5335220f651d 100644 --- a/drivers/mmc/core/sd.c +++ b/drivers/mmc/core/sd.c @@ -727,6 +727,7 @@ MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid); MMC_DEV_ATTR(serial, "0x%08x\n", card->cid.serial); MMC_DEV_ATTR(ocr, "0x%08x\n", card->ocr); MMC_DEV_ATTR(rca, "0x%04x\n", card->rca); +MMC_DEV_ATTR(safe_trim_quirk, "%d\n", card->safe_trim_quirk_version); static ssize_t mmc_dsr_show(struct device *dev, struct device_attribute *attr, @@ -789,6 +790,7 @@ static struct attribute *sd_std_attrs[] = { &dev_attr_ocr.attr, &dev_attr_rca.attr, &dev_attr_dsr.attr, + &dev_attr_safe_trim_quirk.attr, NULL, }; diff --git a/drivers/mmc/host/rtsx_pci_sdmmc.c b/drivers/mmc/host/rtsx_pci_sdmmc.c index 8dfbc62f165b..a1502e7ab8a6 100644 --- a/drivers/mmc/host/rtsx_pci_sdmmc.c +++ b/drivers/mmc/host/rtsx_pci_sdmmc.c @@ -7,6 +7,7 @@ * Wei WANG */ +#include #include #include #include @@ -23,6 +24,121 @@ #include #include +#define QUIRK_NO_AGGRESSIVE_PM BIT(0) + +/* DMI quirk table for systems with known SD card reader issues. */ +static const struct dmi_system_id rtsx_pci_sdmmc_dmi_quirks[] = { + { + /* ASUS ROG Xbox Ally */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_BOARD_NAME, "RC73YA"), + }, + }, + { + /* ASUS ROG Xbox Ally X */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_BOARD_NAME, "RC73XA"), + }, + }, + { + /* Lenovo Legion Go */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83E1"), + }, + }, + { + /* Lenovo Legion Go 2 */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83N0"), + }, + }, + { + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83N1"), + }, + }, + { + /* Lenovo Legion Go S */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83L3"), + }, + }, + { + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83N6"), + }, + }, + { + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83Q2"), + }, + }, + { + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "83Q3"), + }, + }, + { + /* MSI Claw */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"), + }, + }, + { + /* MSI Claw 7 AI+ A2VM */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"), + }, + }, + { + /* MSI Claw 8 AI+ A2VM */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"), + }, + }, + { + /* MSI Claw A8 BZ2EM */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T8K"), + }, + }, + { + /* MSI Claw 8 EX AI+ CG3EM */ + .driver_data = (void *)QUIRK_NO_AGGRESSIVE_PM, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T91"), + }, + }, + {}, +}; + struct realtek_pci_sdmmc { struct platform_device *pdev; struct rtsx_pcr *pcr; @@ -937,7 +1053,7 @@ static int sd_power_on(struct realtek_pci_sdmmc *host, unsigned char power_mode) if (err < 0) return err; - mdelay(1); + mdelay(5); err = rtsx_pci_write_register(pcr, CARD_OE, SD_OUTPUT_EN, SD_OUTPUT_EN); if (err < 0) @@ -1370,6 +1486,10 @@ static int sdmmc_init_sd_express(struct mmc_host *mmc, struct mmc_ios *ios) RTS5261_MCU_BUS_SEL_MASK | RTS5261_MCU_CLOCK_SEL_MASK | RTS5261_DRIVER_ENABLE_FW, RTS5261_MCU_CLOCK_SEL_16M | RTS5261_DRIVER_ENABLE_FW); + + /* Release the D0 hold so the function can delink for the NVMe handoff. */ + rtsx_pci_sd_express_handoff(pcr); + host->eject = true; return 0; } @@ -1414,6 +1534,12 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) { struct mmc_host *mmc = host->mmc; struct rtsx_pcr *pcr = host->pcr; + const struct dmi_system_id *dmi_id; + unsigned long quirks = 0; + + dmi_id = dmi_first_match(rtsx_pci_sdmmc_dmi_quirks); + if (dmi_id) + quirks = (unsigned long)dmi_id->driver_data; mmc->f_min = 250000; mmc->f_max = 208000000; @@ -1421,8 +1547,16 @@ static void realtek_init_host(struct realtek_pci_sdmmc *host) mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED | MMC_CAP_BUS_WIDTH_TEST | MMC_CAP_UHS_SDR12 | MMC_CAP_UHS_SDR25; - if (pcr->rtd3_en) + if (pcr->rtd3_en && !(quirks & QUIRK_NO_AGGRESSIVE_PM)) mmc->caps = mmc->caps | MMC_CAP_AGGRESSIVE_PM; + + /* These devices skip aggressive PM, so the function must stay in D0 + * rather than power-cycle a present card on runtime suspend. Arm the + * gate here; it is cleared in rtsx_pci_sdmmc_drv_remove() so it never + * outlives this host. + */ + rtsx_pci_set_sd_pm_keepalive(pcr, !!(quirks & QUIRK_NO_AGGRESSIVE_PM)); + mmc->caps2 = MMC_CAP2_NO_PRESCAN_POWERUP | MMC_CAP2_FULL_PWR_CYCLE | MMC_CAP2_NO_SDIO; mmc->max_current_330 = 400; @@ -1492,6 +1626,7 @@ static int rtsx_pci_sdmmc_drv_probe(struct platform_device *pdev) ret = mmc_add_host(mmc); if (ret) { + rtsx_pci_set_sd_pm_keepalive(pcr, false); pm_runtime_dont_use_autosuspend(&pdev->dev); pm_runtime_disable(&pdev->dev); return ret; @@ -1507,10 +1642,15 @@ static void rtsx_pci_sdmmc_drv_remove(struct platform_device *pdev) struct mmc_host *mmc; pcr = host->pcr; + /* Drop the runtime-suspend gate so it never outlives this host. */ + rtsx_pci_set_sd_pm_keepalive(pcr, false); pcr->slots[RTSX_SD_CARD].p_dev = NULL; pcr->slots[RTSX_SD_CARD].card_event = NULL; mmc = host->mmc; + if (pci_dev_is_disconnected(pcr->pci)) + mmc_host_set_removed(mmc); + cancel_work_sync(&host->work); mutex_lock(&host->host_mutex); diff --git a/drivers/mmc/host/sdhci-pci-gli.c b/drivers/mmc/host/sdhci-pci-gli.c index 6e4084407662..70ba2c85cfea 100644 --- a/drivers/mmc/host/sdhci-pci-gli.c +++ b/drivers/mmc/host/sdhci-pci-gli.c @@ -26,6 +26,9 @@ #define GLI_9750_WT_EN_ON 0x1 #define GLI_9750_WT_EN_OFF 0x0 +#define SDHCI_GLI_9750_GM_BURST_SIZE 0x510 +#define SDHCI_GLI_9750_GM_BURST_SIZE_MASK GENMASK(17, 16) + #define SDHCI_GLI_9750_CFG2 0x848 #define SDHCI_GLI_9750_CFG2_L1DLY GENMASK(28, 24) #define GLI_9750_CFG2_L1DLY_VALUE 0x1F @@ -638,6 +641,11 @@ static void gl9750_hw_setting(struct sdhci_host *host) gl9750_wt_on(host); + /* clear GM_BURST bits to avoid corruption with DMA writes */ + value = sdhci_readl(host, SDHCI_GLI_9750_GM_BURST_SIZE); + value &= ~SDHCI_GLI_9750_GM_BURST_SIZE_MASK; + sdhci_writel(host, value, SDHCI_GLI_9750_GM_BURST_SIZE); + value = sdhci_readl(host, SDHCI_GLI_9750_CFG2); value &= ~SDHCI_GLI_9750_CFG2_L1DLY; /* set ASPM L1 entry delay to 7.9us */ diff --git a/drivers/net/wireless/ath/ath11k/Makefile b/drivers/net/wireless/ath/ath11k/Makefile index b1435fcf3e1b..ecf04d400417 100644 --- a/drivers/net/wireless/ath/ath11k/Makefile +++ b/drivers/net/wireless/ath/ath11k/Makefile @@ -19,7 +19,8 @@ ath11k-y += core.o \ hw.o \ pcic.o \ fw.o \ - p2p.o + p2p.o\ + unitest.o ath11k-$(CONFIG_ATH11K_DEBUGFS) += debugfs.o debugfs_htt_stats.o debugfs_sta.o ath11k-$(CONFIG_NL80211_TESTMODE) += testmode.o diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h index a0d725923ef2..44b02ae1e85b 100644 --- a/drivers/net/wireless/ath/ath11k/core.h +++ b/drivers/net/wireless/ath/ath11k/core.h @@ -70,6 +70,7 @@ extern bool ath11k_ftm_mode; #define ATH11K_RESET_FAIL_TIMEOUT_HZ (20 * HZ) #define ATH11K_RECONFIGURE_TIMEOUT_HZ (10 * HZ) #define ATH11K_RECOVER_START_TIMEOUT_HZ (20 * HZ) +#define ATH11K_LOCKUP_DESC_ERR_RANGE_HZ (60 * HZ) enum ath11k_supported_bw { ATH11K_BW_20 = 0, @@ -1039,6 +1040,8 @@ struct ath11k_base { struct ath11k_dbring_cap *db_caps; u32 num_db_cap; + u64 last_frame_tx_error_jiffies; + bool simulate_lockup; /* To synchronize 11d scan vdev id */ struct mutex vdev_id_11d_lock; diff --git a/drivers/net/wireless/ath/ath11k/debugfs.c b/drivers/net/wireless/ath/ath11k/debugfs.c index 70922612d3fb..4303e3620096 100644 --- a/drivers/net/wireless/ath/ath11k/debugfs.c +++ b/drivers/net/wireless/ath/ath11k/debugfs.c @@ -355,7 +355,8 @@ static ssize_t ath11k_read_simulate_fw_crash(struct file *file, const char buf[] = "To simulate firmware crash write one of the keywords to this file:\n" "`assert` - this will send WMI_FORCE_FW_HANG_CMDID to firmware to cause assert.\n" - "`hw-restart` - this will simply queue hw restart without fw/hw actually crashing.\n"; + "`hw-restart` - this will simply queue hw restart without fw/hw actually crashing.\n" + "`lockup` - simulate a firmware lockup without the h/w actually hanging.\n"; return simple_read_from_buffer(user_buf, count, ppos, buf, strlen(buf)); } @@ -412,6 +413,14 @@ static ssize_t ath11k_write_simulate_fw_crash(struct file *file, ath11k_info(ab, "user requested hw restart\n"); queue_work(ab->workqueue_aux, &ab->reset_work); ret = 0; + } else if (!strcmp(buf, "mhi-rddm")) { + ath11k_info(ab, "force target rddm\n"); + ath11k_hif_force_rddm(ab); + ret = 0; + } else if (!strcmp(buf, "lockup")) { + ath11k_info(ab, "simulating lockup\n"); + ab->simulate_lockup = true; + ret = 0; } else { ret = -EINVAL; goto exit; diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 8e2abc7b8383..ca8e667ec020 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2560,7 +2560,7 @@ static int ath11k_dp_rx_process_msdu(struct ath11k *ar, lrx_desc = (struct hal_rx_desc *)last_buf->data; rx_attention = ath11k_dp_rx_get_attention(ab, lrx_desc); if (!ath11k_dp_rx_h_attn_msdu_done(rx_attention)) { - ath11k_warn(ab, "msdu_done bit in attention is not set\n"); + ath11k_dbg(ab, ATH11K_DBG_DATA, "msdu_done bit in attention is not set\n"); ret = -EIO; goto free_out; } diff --git a/drivers/net/wireless/ath/ath11k/hal.c b/drivers/net/wireless/ath/ath11k/hal.c index 98bd9e3f0aae..e4becfa885a9 100644 --- a/drivers/net/wireless/ath/ath11k/hal.c +++ b/drivers/net/wireless/ath/ath11k/hal.c @@ -691,7 +691,7 @@ int ath11k_hal_srng_dst_num_free(struct ath11k_base *ab, struct hal_srng *srng, tp = srng->u.dst_ring.tp; - if (sync_hw_ptr) { + if (sync_hw_ptr && !ab->simulate_lockup) { hp = *srng->u.dst_ring.hp_addr; srng->u.dst_ring.cached_hp = hp; } else { @@ -743,7 +743,7 @@ u32 *ath11k_hal_srng_src_get_next_entry(struct ath11k_base *ab, */ next_hp = (srng->u.src_ring.hp + srng->entry_size) % srng->ring_size; - if (next_hp == srng->u.src_ring.cached_tp) + if (next_hp == srng->u.src_ring.cached_tp || ab->simulate_lockup) return NULL; desc = srng->ring_base_vaddr + srng->u.src_ring.hp; @@ -828,6 +828,9 @@ void ath11k_hal_srng_access_begin(struct ath11k_base *ab, struct hal_srng *srng) lockdep_assert_held(&srng->lock); + if (ab->simulate_lockup) + return; + if (srng->ring_dir == HAL_SRNG_DIR_SRC) { srng->u.src_ring.cached_tp = *(volatile u32 *)srng->u.src_ring.tp_addr; diff --git a/drivers/net/wireless/ath/ath11k/hif.h b/drivers/net/wireless/ath/ath11k/hif.h index 017fed1b4bd1..5c41a832e64c 100644 --- a/drivers/net/wireless/ath/ath11k/hif.h +++ b/drivers/net/wireless/ath/ath11k/hif.h @@ -32,6 +32,7 @@ struct ath11k_hif_ops { void (*ce_irq_disable)(struct ath11k_base *ab); void (*get_ce_msi_idx)(struct ath11k_base *ab, u32 ce_id, u32 *msi_idx); void (*coredump_download)(struct ath11k_base *ab); + int (*target_crash)(struct ath11k_base *ab); }; static inline void ath11k_hif_ce_irq_enable(struct ath11k_base *ab) @@ -98,6 +99,13 @@ static inline int ath11k_hif_resume(struct ath11k_base *ab) return 0; } +static inline int ath11k_hif_force_rddm(struct ath11k_base *ab) +{ + if (ab->hif.ops->target_crash) + return ab->hif.ops->target_crash(ab); + return 0; +} + static inline u32 ath11k_hif_read32(struct ath11k_base *ab, u32 address) { return ab->hif.ops->read32(ab, address); diff --git a/drivers/net/wireless/ath/ath11k/htc.c b/drivers/net/wireless/ath/ath11k/htc.c index 5f9ee5e633da..8a62007b4ce5 100644 --- a/drivers/net/wireless/ath/ath11k/htc.c +++ b/drivers/net/wireless/ath/ath11k/htc.c @@ -208,7 +208,7 @@ static int ath11k_htc_process_trailer(struct ath11k_htc *htc, break; } - if (ab->hw_params.credit_flow) { + if (ab->hw_params.credit_flow && !ab->simulate_lockup) { switch (record->hdr.id) { case ATH11K_HTC_RECORD_CREDITS: len = sizeof(struct ath11k_htc_credit_report); diff --git a/drivers/net/wireless/ath/ath11k/hw.c b/drivers/net/wireless/ath/ath11k/hw.c index 93f9a03c48dc..e799f24754bc 100644 --- a/drivers/net/wireless/ath/ath11k/hw.c +++ b/drivers/net/wireless/ath/ath11k/hw.c @@ -100,6 +100,7 @@ static void ath11k_init_wmi_config_qca6390(struct ath11k_base *ab, config->num_wow_filters = 0x16; config->num_keep_alive_pattern = 0; config->flag1 |= WMI_RSRC_CFG_FLAG1_BSS_CHANNEL_INFO_64; + config->host_service_flags |= WMI_RSRC_CFG_HOST_SERVICE_FLAG_NAN_IFACE_SUPPORT; } static void ath11k_hw_ipq8074_reo_setup(struct ath11k_base *ab) diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c index 2d55cdc4d165..325ced7055a5 100644 --- a/drivers/net/wireless/ath/ath11k/mac.c +++ b/drivers/net/wireless/ath/ath11k/mac.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,7 @@ #include "debugfs_sta.h" #include "hif.h" #include "wow.h" +#include "unitest.h" #define CHAN2G(_channel, _freq, _flags) { \ .band = NL80211_BAND_2GHZ, \ @@ -255,6 +257,10 @@ static const u32 ath11k_smps_map[] = { [WLAN_HT_CAP_SM_PS_DISABLED] = WMI_PEER_SMPS_PS_NONE, }; +static struct wiphy_vendor_command ath11k_vendor_cmds[] = { + ath11k_unit_test_command, +}; + enum nl80211_he_ru_alloc ath11k_mac_phy_he_ru_to_nl80211_he_ru_alloc(u16 ru_phy) { enum nl80211_he_ru_alloc ret; @@ -3080,6 +3086,27 @@ static bool ath11k_mac_vif_recalc_sta_he_txbf(struct ath11k *ar, hemode |= FIELD_PREP(HE_MODE_SU_TX_BFER, HE_SU_BFER_ENABLE); } + ath11k_info(ar->ab, "mac0-5 cap %x-%x-%x-%x-%x-%x\n", + he_cap_elem.mac_cap_info[0], + he_cap_elem.mac_cap_info[1], + he_cap_elem.mac_cap_info[2], + he_cap_elem.mac_cap_info[3], + he_cap_elem.mac_cap_info[4], + he_cap_elem.mac_cap_info[5]); + ath11k_info(ar->ab, "phy0-5 cap %x-%x-%x-%x-%x-%x\n", + he_cap_elem.phy_cap_info[0], + he_cap_elem.phy_cap_info[1], + he_cap_elem.phy_cap_info[2], + he_cap_elem.phy_cap_info[3], + he_cap_elem.phy_cap_info[4], + he_cap_elem.phy_cap_info[5]); + ath11k_info(ar->ab, "phy6-10 cap %x-%x-%x-%x-%x\n", + he_cap_elem.phy_cap_info[6], + he_cap_elem.phy_cap_info[7], + he_cap_elem.phy_cap_info[8], + he_cap_elem.phy_cap_info[9], + he_cap_elem.phy_cap_info[10]); + ath11k_info(ar->ab, "WMI_VDEV_PARAM_SET_HEMU_MODE 3 0x%x\n", hemode); ret = ath11k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, param, hemode); if (ret) { ath11k_warn(ar->ab, "failed to submit vdev param txbf 0x%x: %d\n", @@ -6541,6 +6568,10 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw, ret = ath11k_dp_tx(ar, arvif, arsta, skb); if (unlikely(ret)) { + scoped_guard(spinlock_bh, &ar->ab->base_lock) { + ar->ab->last_frame_tx_error_jiffies = jiffies_64; + } + ath11k_warn(ar->ab, "failed to transmit frame %d\n", ret); ieee80211_free_txskb(ar->hw, skb); } @@ -9269,9 +9300,14 @@ ath11k_mac_op_reconfig_complete(struct ieee80211_hw *hw, * the recovery has to be done for each radio */ if (recovery_count == ab->num_radios) { + /* Cancel any pending work, preventing a second redudant + * reset. + */ + cancel_work(&ab->reset_work); atomic_dec(&ab->reset_count); complete(&ab->reset_complete); ab->is_reset = false; + ab->last_frame_tx_error_jiffies = 0; atomic_set(&ab->fail_cont_count, 0); ath11k_dbg(ab, ATH11K_DBG_BOOT, "reset success\n"); } @@ -10606,6 +10642,8 @@ static int __ath11k_mac_register(struct ath11k *ar) ar->hw->wiphy->iftype_ext_capab = ath11k_iftypes_ext_capa; ar->hw->wiphy->num_iftype_ext_capab = ARRAY_SIZE(ath11k_iftypes_ext_capa); + ar->hw->wiphy->vendor_commands = ath11k_vendor_cmds; + ar->hw->wiphy->n_vendor_commands = ARRAY_SIZE(ath11k_vendor_cmds); if (ar->supports_6ghz) { wiphy_ext_feature_set(ar->hw->wiphy, diff --git a/drivers/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index a6c9ff112c68..be3236675327 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -458,12 +458,12 @@ void ath11k_mhi_stop(struct ath11k_pci *ab_pci, bool is_suspend) * workaround, otherwise ath11k_core_resume() will timeout * during resume. */ - if (is_suspend) + if (is_suspend) { mhi_power_down_keep_dev(ab_pci->mhi_ctrl, true); - else + } else { mhi_power_down(ab_pci->mhi_ctrl, true); - - mhi_unprepare_after_power_down(ab_pci->mhi_ctrl); + mhi_unprepare_after_power_down(ab_pci->mhi_ctrl); + } } int ath11k_mhi_suspend(struct ath11k_pci *ab_pci) @@ -502,3 +502,17 @@ void ath11k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic) { mhi_download_rddm_image(mhi_ctrl, in_panic); } + +int ath11k_mhi_force_rddm(struct ath11k_pci *ab_pci) +{ + struct ath11k_base *ab = ab_pci->ab; + int ret; + + ret = mhi_force_rddm_mode(ab_pci->mhi_ctrl); + if (ret) { + ath11k_warn(ab, "failed to resume mhi: %d", ret); + return ret; + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath11k/mhi.h b/drivers/net/wireless/ath/ath11k/mhi.h index 7e7561ce883c..8f7677652ffe 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.h +++ b/drivers/net/wireless/ath/ath11k/mhi.h @@ -27,5 +27,6 @@ void ath11k_mhi_clear_vector(struct ath11k_base *ab); int ath11k_mhi_suspend(struct ath11k_pci *ar_pci); int ath11k_mhi_resume(struct ath11k_pci *ar_pci); void ath11k_mhi_coredump(struct mhi_controller *mhi_ctrl, bool in_panic); +int ath11k_mhi_force_rddm(struct ath11k_pci *ab_pci); #endif diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c index a163168f3617..3f7c69c0152d 100644 --- a/drivers/net/wireless/ath/ath11k/pci.c +++ b/drivers/net/wireless/ath/ath11k/pci.c @@ -887,19 +887,20 @@ static int ath11k_pci_start(struct ath11k_base *ab) { struct ath11k_pci *ab_pci = ath11k_pci_priv(ab); - /* TODO: for now don't restore ASPM in case of single MSI - * vector as MHI register reading in M2 causes system hang. - */ - if (test_bit(ATH11K_FLAG_MULTI_MSI_VECTORS, &ab->dev_flags)) - ath11k_pci_aspm_restore(ab_pci); - else - ath11k_info(ab, "leaving PCI ASPM disabled to avoid MHI M2 problems\n"); - + ath11k_pci_aspm_restore(ab_pci); ath11k_pcic_start(ab); return 0; } +static int ath11k_pci_force_rddm(struct ath11k_base *ab) +{ + struct ath11k_pci *ar_pci; + + ar_pci = ath11k_pci_priv(ab); + return ath11k_mhi_force_rddm(ar_pci); +} + static const struct ath11k_hif_ops ath11k_pci_hif_ops = { .start = ath11k_pci_start, .stop = ath11k_pcic_stop, @@ -921,6 +922,7 @@ static const struct ath11k_hif_ops ath11k_pci_hif_ops = { #ifdef CONFIG_DEV_COREDUMP .coredump_download = ath11k_pci_coredump_download, #endif + .target_crash = ath11k_pci_force_rddm, }; static void ath11k_pci_read_hw_version(struct ath11k_base *ab, u32 *major, u32 *minor) diff --git a/drivers/net/wireless/ath/ath11k/unitest.c b/drivers/net/wireless/ath/ath11k/unitest.c new file mode 100644 index 000000000000..541925af0fd0 --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/unitest.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/** + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#include +#include "debug.h" +#include "unitest.h" + +const struct nla_policy ath11k_unitest_policy[UNITEST_MAX + 1] = { + [UNITEST_MODULE_ID] = {.type = NLA_U32}, + [UNITEST_ARGS_NUM] = {.type = NLA_U32}, + [UNITEST_ARGS] = {.type = NLA_BINARY, + .len = MAX_UNITEST_MEMORY_LEN}, +}; + +/** + * ath11k_unit_test() - send unit test command + * @wiphy: wiphy structure pointer + * @wdev: Wireless device structure pointer + * @data: Pointer to the data received + * @data_len: Length of @data + * + * Return: 0 on success; errno on failure + */ + +int ath11k_unit_test(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct ath11k *ar = hw->priv; + struct ath11k_base *ab = ar->ab; + struct nlattr *tb[UNITEST_MAX + 1]; + int status = 0; + u32 module_id, num_args, temp_num_args, len; + bool sta_found = false; + struct ath11k_vif *arvif; + struct unit_test_cmd utest_cmd = {0}; + + list_for_each_entry(arvif, &ar->arvifs, list) { + if (arvif->vdev_type != WMI_VDEV_TYPE_STA || + arvif->vdev_subtype != WMI_VDEV_SUBTYPE_NONE) + continue; + sta_found = true; + break; + } + if (!sta_found) { + ath11k_warn(ar->ab, "no sta found."); + return -EINVAL; + } + + utest_cmd.vdev_id = arvif->vdev_id; + + if (nla_parse(tb, UNITEST_MAX, data, data_len, ath11k_unitest_policy, NULL)) { + ath11k_warn(ab, "Invalid ATTR"); + return -EINVAL; + } + + if (!tb[UNITEST_MODULE_ID]) { + ath11k_warn(ab, "attr unitest module id failed"); + return -EINVAL; + } + module_id = nla_get_u32(tb[UNITEST_MODULE_ID]); + utest_cmd.module_id = module_id; + + if (!tb[UNITEST_ARGS_NUM]) { + ath11k_warn(ab, "attr unitest args num failed"); + return -EINVAL; + } + num_args = nla_get_u32(tb[UNITEST_ARGS_NUM]); + utest_cmd.num_args = num_args; + if (num_args > UNIT_TEST_MAX_NUM_ARGS) { + ath11k_warn(ab, "num args exceed"); + return -EINVAL; + } + + if (!tb[UNITEST_ARGS]) { + ath11k_warn(ab, "attr unitest args failed"); + return -EINVAL; + } + len = nla_len(tb[UNITEST_ARGS]); + temp_num_args = len / sizeof(u32); + if (num_args != temp_num_args) { + ath11k_warn(ab, "num args mismatch"); + return -EINVAL; + } + nla_memcpy(utest_cmd.args, tb[UNITEST_ARGS], len); + + status = ath11k_wmi_set_unit_test(ar, &utest_cmd); + if (status) { + ath11k_warn(ab, "Unable to post unit test message (status-%d)", status); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/net/wireless/ath/ath11k/unitest.h b/drivers/net/wireless/ath/ath11k/unitest.h new file mode 100644 index 000000000000..9fee1cd6873b --- /dev/null +++ b/drivers/net/wireless/ath/ath11k/unitest.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/** + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef _UNIT_TEST_H_ +#define _UNIT_TEST_H_ + +#define VENDOR_ID 0x001374 +#define QCA_NL80211_VENDOR_UNITEST_SUBCMD 84 +#define MAX_UNITEST_MEMORY_LEN 128 + +enum qca_wlan_vendor_attr_unit_test { + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_INVALID = 0, + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_MODULE_ID, + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_ARGS_NUM, + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_ARGS, + /* keep last */ + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_AFTER_LAST, + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_MAX = + QCA_WLAN_VENDOR_ATTR_UNIT_TEST_AFTER_LAST - 1, +}; + +#define UNITEST_INVALID QCA_WLAN_VENDOR_ATTR_UNIT_TEST_INVALID +#define UNITEST_MODULE_ID QCA_WLAN_VENDOR_ATTR_UNIT_TEST_MODULE_ID +#define UNITEST_ARGS QCA_WLAN_VENDOR_ATTR_UNIT_TEST_ARGS +#define UNITEST_ARGS_NUM QCA_WLAN_VENDOR_ATTR_UNIT_TEST_ARGS_NUM +#define UNITEST_MAX QCA_WLAN_VENDOR_ATTR_UNIT_TEST_MAX + +#define ath11k_unit_test_command \ +{\ + .info.vendor_id = VENDOR_ID,\ + .info.subcmd = QCA_NL80211_VENDOR_UNITEST_SUBCMD,\ + .flags = WIPHY_VENDOR_CMD_NEED_WDEV |\ + WIPHY_VENDOR_CMD_NEED_NETDEV |\ + WIPHY_VENDOR_CMD_NEED_RUNNING,\ + .doit = ath11k_unit_test,\ + .policy = VENDOR_CMD_RAW_DATA,\ + .maxattr = QCA_WLAN_VENDOR_ATTR_UNIT_TEST_MAX,\ +} + +int ath11k_unit_test(struct wiphy *wiphy, struct wireless_dev *wdev, + const void *data, int data_len); +#endif diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c index dca6e011cc40..43e7afa0a8f4 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.c +++ b/drivers/net/wireless/ath/ath11k/wmi.c @@ -7,8 +7,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #include @@ -325,9 +328,32 @@ int ath11k_wmi_cmd_send(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, }), WMI_SEND_TIMEOUT_HZ); } - if (ret == -EAGAIN) + if (ret == -EAGAIN) { + u64 range_start; + ath11k_warn(wmi_ab->ab, "wmi command %d timeout\n", cmd_id); + guard(spinlock_bh)(&ab->base_lock); + + if (ab->last_frame_tx_error_jiffies == 0) + return ret; + + range_start = + (jiffies_64 > ATH11K_LOCKUP_DESC_ERR_RANGE_HZ) ? + jiffies_64 - ATH11K_LOCKUP_DESC_ERR_RANGE_HZ : + 0; + + if (time_in_range64(ab->last_frame_tx_error_jiffies, + range_start, jiffies_64) && + queue_work(ab->workqueue_aux, &ab->reset_work)) { + ath11k_err(wmi_ab->ab, + "Firmware lockup detected. Resetting."); + + /* Assume that reset gets us out of lockup. */ + ab->simulate_lockup = false; + } + } + if (ret == -ENOBUFS) ath11k_warn(wmi_ab->ab, "ce desc not available for wmi command %d\n", cmd_id); @@ -4237,6 +4263,7 @@ ath11k_wmi_copy_resource_config(struct wmi_resource_config *wmi_cfg, wmi_cfg->sched_params = tg_cfg->sched_params; wmi_cfg->twt_ap_pdev_count = tg_cfg->twt_ap_pdev_count; wmi_cfg->twt_ap_sta_count = tg_cfg->twt_ap_sta_count; + wmi_cfg->host_service_flags = tg_cfg->host_service_flags; wmi_cfg->host_service_flags &= ~(1 << WMI_CFG_HOST_SERVICE_FLAG_REG_CC_EXT); wmi_cfg->host_service_flags |= (tg_cfg->is_reg_cc_ext_event_supported << @@ -10141,3 +10168,46 @@ bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar) return test_bit(WMI_TLV_SERVICE_REG_CC_EXT_EVENT_SUPPORT, ar->ab->wmi_ab.svc_map) && ar->supports_6ghz; } + +int ath11k_wmi_set_unit_test(struct ath11k *ar, struct unit_test_cmd *unit_test) +{ + struct ath11k_pdev_wmi *wmi = ar->wmi; + struct sk_buff *skb; + struct wmi_unit_test_cmd_fixed_param *cmd; + u32 len, args_tlv_len; + u8 *buf_ptr; + u32 *args; + struct wmi_tlv *tlv; + u32 i; + + args_tlv_len = TLV_HDR_SIZE + unit_test->num_args * sizeof(u32); + + len = sizeof(struct wmi_unit_test_cmd_fixed_param) + args_tlv_len; + skb = ath11k_wmi_alloc_skb(wmi->wmi_ab, len); + if (!skb) + return -ENOMEM; + + cmd = (struct wmi_unit_test_cmd_fixed_param *)skb->data; + buf_ptr = (u8 *)cmd; + cmd->tlv_header = FIELD_PREP(WMI_TLV_TAG, + WMI_TAG_UNIT_TEST_CMD) | + FIELD_PREP(WMI_TLV_LEN, sizeof(*cmd) - TLV_HDR_SIZE); + cmd->vdev_id = unit_test->vdev_id; + cmd->module_id = unit_test->module_id; + cmd->num_args = unit_test->num_args; + + buf_ptr += sizeof(*cmd); + + tlv = (struct wmi_tlv *)buf_ptr; + tlv->header = FIELD_PREP(WMI_TLV_TAG, WMI_TAG_ARRAY_UINT32) | + FIELD_PREP(WMI_TLV_LEN, unit_test->num_args * sizeof(u32)); + args = (u32 *)(buf_ptr + TLV_HDR_SIZE); + ath11k_info(ar->ab, "module id = 0x%x, num args = %u", + unit_test->module_id, unit_test->num_args); + for (i = 0; (i < unit_test->num_args && i < UNIT_TEST_MAX_NUM_ARGS); i++) { + args[i] = unit_test->args[i]; + ath11k_info(ar->ab, "0x%x,", unit_test->args[i]); + } + + return ath11k_wmi_cmd_send(wmi, skb, WMI_UNIT_TEST_CMDID); +} diff --git a/drivers/net/wireless/ath/ath11k/wmi.h b/drivers/net/wireless/ath/ath11k/wmi.h index b2dade0516ac..759f0b92d02e 100644 --- a/drivers/net/wireless/ath/ath11k/wmi.h +++ b/drivers/net/wireless/ath/ath11k/wmi.h @@ -2352,6 +2352,7 @@ struct wmi_init_cmd { u32 num_host_mem_chunks; } __packed; +#define WMI_RSRC_CFG_HOST_SERVICE_FLAG_NAN_IFACE_SUPPORT BIT(0) #define WMI_RSRC_CFG_FLAG1_BSS_CHANNEL_INFO_64 BIT(5) #define WMI_RSRC_CFG_FLAG2_CALC_NEXT_DTIM_COUNT_SET BIT(9) #define WMI_RSRC_CFG_FLAG1_ACK_RSSI BIT(18) @@ -5807,6 +5808,12 @@ struct target_resource_config { u8 is_reg_cc_ext_event_supported; u32 ema_max_vap_cnt; u32 ema_max_profile_period; + u32 max_bssid_indicator; + u32 ul_resp_config; + u32 msdu_flow_override_config0; + u32 msdu_flow_override_config1; + u32 flags2; + u32 host_service_flags; }; enum wmi_debug_log_param { @@ -6431,6 +6438,29 @@ enum wmi_wmm_params_type { const void **ath11k_wmi_tlv_parse_alloc(struct ath11k_base *ab, struct sk_buff *skb, gfp_t gfp); + +#define UNIT_TEST_MAX_NUM_ARGS 8 + +struct unit_test_cmd { + u32 vdev_id; + u32 module_id; + u32 num_args; + u32 args[UNIT_TEST_MAX_NUM_ARGS]; +}; + +struct wmi_unit_test_cmd_fixed_param { + u32 tlv_header; + u32 vdev_id; + u32 module_id; + u32 num_args; + u32 diag_token; + /** + * TLV (tag length value) parameters follow the wmi_unit_test_cmd_fixed_param + * structure. The TLV's are: + * u32 args[]; + */ +} __packed; + int ath11k_wmi_cmd_send(struct ath11k_pdev_wmi *wmi, struct sk_buff *skb, u32 cmd_id); struct sk_buff *ath11k_wmi_alloc_skb(struct ath11k_wmi_base *wmi_sc, u32 len); @@ -6623,4 +6653,6 @@ int ath11k_wmi_send_vdev_set_tpc_power(struct ath11k *ar, int ath11k_wmi_peer_set_cfr_capture_conf(struct ath11k *ar, u32 vdev_id, const u8 *mac, struct wmi_peer_cfr_capture_conf_arg *arg); +int ath11k_wmi_set_unit_test(struct ath11k *ar, struct unit_test_cmd *unit_test); + #endif diff --git a/drivers/net/wireless/ath/ath12k/mhi.c b/drivers/net/wireless/ath/ath12k/mhi.c index ee87f00bc5de..680adecd702c 100644 --- a/drivers/net/wireless/ath/ath12k/mhi.c +++ b/drivers/net/wireless/ath/ath12k/mhi.c @@ -471,6 +471,12 @@ static int ath12k_mhi_set_state(struct ath12k_pci *ab_pci, ath12k_mhi_set_state_bit(ab_pci, mhi_state); + /* mhi_power_down_keep_dev() has been updated to DEINIT without + * freeing bhie tables + */ + if (mhi_state == ATH12K_MHI_POWER_OFF_KEEP_DEV) + ath12k_mhi_set_state_bit(ab_pci, ATH12K_MHI_DEINIT); + return 0; out: @@ -505,12 +511,12 @@ void ath12k_mhi_stop(struct ath12k_pci *ab_pci, bool is_suspend) * workaround, otherwise ath12k_core_resume() will timeout * during resume. */ - if (is_suspend) + if (is_suspend) { ath12k_mhi_set_state(ab_pci, ATH12K_MHI_POWER_OFF_KEEP_DEV); - else + } else { ath12k_mhi_set_state(ab_pci, ATH12K_MHI_POWER_OFF); - - ath12k_mhi_set_state(ab_pci, ATH12K_MHI_DEINIT); + ath12k_mhi_set_state(ab_pci, ATH12K_MHI_DEINIT); + } } void ath12k_mhi_suspend(struct ath12k_pci *ab_pci) diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h index 5547888d7e67..5ccfa255e1c5 100644 --- a/drivers/net/wireless/realtek/rtw89/core.h +++ b/drivers/net/wireless/realtek/rtw89/core.h @@ -8181,6 +8181,7 @@ void rtw89_chip_cfg_txpwr_ul_tb_offset(struct rtw89_dev *rtwdev, bool rtw89_legacy_rate_to_bitrate(struct rtw89_dev *rtwdev, u8 legacy_rate, u16 *bitrate); int rtw89_regd_setup(struct rtw89_dev *rtwdev); int rtw89_regd_init_hint(struct rtw89_dev *rtwdev); +const struct rtw89_regd *rtw89_regd_static_ww_entry(void); const char *rtw89_regd_get_string(enum rtw89_regulation_type regd); void rtw89_traffic_stats_init(struct rtw89_dev *rtwdev, struct rtw89_traffic_stats *stats); diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index c6f175041680..404f36c60e83 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -1353,10 +1353,11 @@ int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev, { const struct __rtw89_fw_regd_element *regd_elm = &elm->u.regd; struct rtw89_fw_elm_info *elm_info = &rtwdev->fw.elm_info; - u32 num_ents = le32_to_cpu(regd_elm->num_ents); struct rtw89_regd_data *p; struct rtw89_regd regd; - u32 i = 0; + /* +1 because we reserve index 0 for WW */ + u32 num_ents = le32_to_cpu(regd_elm->num_ents) + 1; + u32 i = 1; if (num_ents > RTW89_REGD_MAX_COUNTRY_NUM) { rtw89_warn(rtwdev, @@ -1379,6 +1380,7 @@ int rtw89_recognize_regd_from_elm(struct rtw89_dev *rtwdev, return -ENOMEM; p->nr = num_ents; + p->map[0] = *rtw89_regd_static_ww_entry(); rtw89_for_each_in_regd_element(®d, regd_elm) p->map[i++] = regd; diff --git a/drivers/net/wireless/realtek/rtw89/regd.c b/drivers/net/wireless/realtek/rtw89/regd.c index 14fab6b1a74a..d4e3d786396b 100644 --- a/drivers/net/wireless/realtek/rtw89/regd.c +++ b/drivers/net/wireless/realtek/rtw89/regd.c @@ -21,10 +21,8 @@ void rtw89_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request static_assert(BITS_PER_TYPE(unsigned long) >= NUM_OF_RTW89_REGD_FUNC); -static const struct rtw89_regd rtw89_ww_regd = - COUNTRY_REGD("00", RTW89_WW, RTW89_WW, RTW89_WW, 0x0); - static const struct rtw89_regd rtw89_regd_map[] = { + COUNTRY_REGD("00", RTW89_WW, RTW89_WW, RTW89_WW, 0x0), COUNTRY_REGD("AR", RTW89_MEXICO, RTW89_MEXICO, RTW89_FCC, 0x0), COUNTRY_REGD("BO", RTW89_FCC, RTW89_FCC, RTW89_NA, 0x0), COUNTRY_REGD("BR", RTW89_FCC, RTW89_FCC, RTW89_FCC, 0x0), @@ -316,12 +314,12 @@ static const struct rtw89_regd *rtw89_regd_find_reg_by_name(struct rtw89_dev *rt return ®d_ctrl->map[i]; } - return &rtw89_ww_regd; + return NULL; } static bool rtw89_regd_is_ww(const struct rtw89_regd *regd) { - return regd == &rtw89_ww_regd; + return !memcmp(regd->alpha2, "00", 2); } static u8 rtw89_regd_get_index(struct rtw89_dev *rtwdev, const struct rtw89_regd *regd) @@ -331,9 +329,6 @@ static u8 rtw89_regd_get_index(struct rtw89_dev *rtwdev, const struct rtw89_regd BUILD_BUG_ON(ARRAY_SIZE(rtw89_regd_map) > RTW89_REGD_MAX_COUNTRY_NUM); - if (rtw89_regd_is_ww(regd)) - return RTW89_REGD_MAX_COUNTRY_NUM; - return regd - regd_ctrl->map; } @@ -342,6 +337,10 @@ static u8 rtw89_regd_get_index_by_name(struct rtw89_dev *rtwdev, const char *alp const struct rtw89_regd *regd; regd = rtw89_regd_find_reg_by_name(rtwdev, alpha2); + + if (!regd) + return RTW89_REGD_MAX_COUNTRY_NUM; + return rtw89_regd_get_index(rtwdev, regd); } @@ -745,7 +744,7 @@ int rtw89_regd_init_hint(struct rtw89_dev *rtwdev) return -EINVAL; chip_regd = rtw89_regd_find_reg_by_name(rtwdev, rtwdev->efuse.country_code); - if (!rtw89_regd_is_ww(chip_regd)) { + if (chip_regd && !rtw89_regd_is_ww(chip_regd)) { rtwdev->regulatory.regd = chip_regd; rtwdev->regulatory.programmed = true; @@ -767,6 +766,11 @@ int rtw89_regd_init_hint(struct rtw89_dev *rtwdev) return 0; } +const struct rtw89_regd *rtw89_regd_static_ww_entry(void) +{ + return &rtw89_regd_map[0]; +} + static void rtw89_regd_apply_policy_unii4(struct rtw89_dev *rtwdev, struct wiphy *wiphy) { @@ -883,7 +887,15 @@ static void rtw89_regd_notifier_apply(struct rtw89_dev *rtwdev, struct wiphy *wiphy, struct regulatory_request *request) { - rtwdev->regulatory.regd = rtw89_regd_find_reg_by_name(rtwdev, request->alpha2); + const struct rtw89_regd *regd = rtw89_regd_find_reg_by_name(rtwdev, request->alpha2); + + if (!regd) { + /* Fallback to WW domain if name not found. */ + regd = &rtwdev->regulatory.ctrl.map[0]; + } + + rtwdev->regulatory.regd = regd; + /* This notification might be set from the system of distros, * and it does not expect the regulatory will be modified by * connecting to an AP (i.e. country ie). diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 172783e7f519..f4ed7d6ef09f 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c @@ -1261,8 +1261,17 @@ void pci_configure_ltr(struct pci_dev *pdev) } bridge = pci_upstream_bridge(pdev); - if (bridge && bridge->ltr_path) + if (bridge && bridge->ltr_path) { + /* + * A hot-added device may arrive with LTR still + * enabled from before its removal. Re-enable LTR + * in the bridge, or the device's LTR messages are + * handled as Unsupported Requests (PCIe r6.0, sec + * 6.18). + */ + pci_bridge_reconfigure_ltr(pdev); pdev->ltr_path = 1; + } return; } diff --git a/drivers/pci/pcie/portdrv.c b/drivers/pci/pcie/portdrv.c index 2d6aa488fe7b..92bd24da0fe2 100644 --- a/drivers/pci/pcie/portdrv.c +++ b/drivers/pci/pcie/portdrv.c @@ -637,7 +637,9 @@ __setup("pcie_ports=", pcie_port_setup); #ifdef CONFIG_PM static int pcie_port_runtime_suspend(struct device *dev) { - if (!to_pci_dev(dev)->bridge_d3) + struct pci_dev *pdev = to_pci_dev(dev); + + if (!pdev->bridge_d3 || pdev->no_runtime_d3) return -EBUSY; return pcie_port_device_runtime_suspend(dev); @@ -645,12 +647,14 @@ static int pcie_port_runtime_suspend(struct device *dev) static int pcie_port_runtime_idle(struct device *dev) { + struct pci_dev *pdev = to_pci_dev(dev); + /* * Assume the PCI core has set bridge_d3 whenever it thinks the port * should be good to go to D3. Everything else, including moving * the port to D3, is handled by the PCI core. */ - return to_pci_dev(dev)->bridge_d3 ? 0 : -EBUSY; + return pdev->bridge_d3 && !pdev->no_runtime_d3 ? 0 : -EBUSY; } static const struct dev_pm_ops pcie_portdrv_pm_ops = { diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 8c1559b57f82..22e88a0ad1c9 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -6501,22 +6501,224 @@ static void pci_fixup_d3cold_delay_1sec(struct pci_dev *pdev) } DECLARE_PCI_FIXUP_FINAL(0x5555, 0x0004, pci_fixup_d3cold_delay_1sec); +/* + * The MSI Claw 8 EX AI+ muxes its microSD Express slot onto this Root + * Port through an RTS5264 card reader, with in-band presence only, so + * a runtime suspended Port can never see a card change and the slot is + * dead until a manual rescan. Block only runtime D3: keeping the Port + * in D0 across system sleep hangs s2idle entry here, and pciehp + * re-checks presence on resume anyway. + */ +static void quirk_sdexpress_no_runtime_d3(struct pci_dev *pdev) +{ + if (pdev->devfn != PCI_DEVFN(0x1c, 0)) + return; + + if (!dmi_match(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd.") || + !dmi_match(DMI_BOARD_NAME, "MS-1T91")) + return; + + pci_info(pdev, "disabling runtime D3 for SD Express slot Port (in-band presence only)\n"); + pdev->no_runtime_d3 = 1; +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xe43a, + quirk_sdexpress_no_runtime_d3); + #ifdef CONFIG_PCIEAER -static void pci_mask_replay_timer_timeout(struct pci_dev *pdev) +static void pci_mask_replay_timer_timeout(struct pci_dev *pdev, struct pci_dev *reporter) { - struct pci_dev *parent = pci_upstream_bridge(pdev); + u16 aer; u32 val; - if (!parent || !parent->aer_cap) + if (!reporter) return; - pci_info(parent, "mask Replay Timer Timeout Correctable Errors due to %s hardware defect", + /* A header fixup runs before pci_aer_init() caches the offset */ + aer = reporter->aer_cap; + if (!aer) + aer = pci_find_ext_capability(reporter, PCI_EXT_CAP_ID_ERR); + if (!aer) + return; + + pci_info(reporter, "mask Replay Timer Timeout Correctable Errors due to %s hardware defect", pci_name(pdev)); - pci_read_config_dword(parent, parent->aer_cap + PCI_ERR_COR_MASK, &val); + pci_read_config_dword(reporter, aer + PCI_ERR_COR_MASK, &val); val |= PCI_ERR_COR_REP_TIMER; - pci_write_config_dword(parent, parent->aer_cap + PCI_ERR_COR_MASK, val); + pci_write_config_dword(reporter, aer + PCI_ERR_COR_MASK, val); +} + +/* The Port leading to the GL975x reports the errors. */ +static void quirk_gl975x_mask_replay_timer_timeout(struct pci_dev *pdev) +{ + pci_mask_replay_timer_timeout(pdev, pci_upstream_bridge(pdev)); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, quirk_gl975x_mask_replay_timer_timeout); +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, quirk_gl975x_mask_replay_timer_timeout); + +/* + * The RTS5264 reports the errors itself. Mask them in a header fixup: + * the reader is hot-added whenever an SD Express card is removed, and + * the storm can hang the machine once pci_aer_init() enables error + * reporting, before a final fixup would run. + */ +static void quirk_rts5264_mask_replay_timer_timeout(struct pci_dev *pdev) +{ + pci_mask_replay_timer_timeout(pdev, pdev); } -DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9750, pci_mask_replay_timer_timeout); -DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_GLI, 0x9755, pci_mask_replay_timer_timeout); +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_REALTEK, 0x5264, + quirk_rts5264_mask_replay_timer_timeout); +#endif + +/* + * When an SD Express card is removed, the mux in front of the RTS5264 + * hands the link back to the card reader, and the link regularly comes + * back degraded: stuck at 2.5GT/s, streaming corrected errors from both + * ends. Only a Secondary Bus Reset recovers it. The degradation often + * develops only after the reader has enumerated, so check the link a + * few seconds after every runtime hot-add, from a workqueue where the + * reader can be removed, the bus reset, and everything rescanned. + */ +static void rts5264_link_reset_work_fn(struct work_struct *work) +{ + static unsigned long last_reset; + struct pci_dev *pdev, *bridge; + bool degraded = false; + u16 lnksta, aer; + u32 corsts; + + pdev = pci_get_device(PCI_VENDOR_ID_REALTEK, 0x5264, NULL); + if (!pdev) + return; + + bridge = pci_upstream_bridge(pdev); + if (!bridge) + goto out; + + /* Stuck below the supported speed? */ + pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lnksta); + if ((lnksta & PCI_EXP_LNKSTA_CLS) == PCI_EXP_LNKSTA_CLS_2_5GB && + pcie_get_speed_cap(pdev) > PCIE_SPEED_2_5GT && + pcie_get_speed_cap(bridge) > PCIE_SPEED_2_5GT) + degraded = true; + + /* Errors received on the reader side since it was enumerated? */ + aer = pci_find_ext_capability(pdev, PCI_EXT_CAP_ID_ERR); + if (!degraded && aer) { + pci_read_config_dword(pdev, aer + PCI_ERR_COR_STATUS, &corsts); + if (corsts & (PCI_ERR_COR_RCVR | PCI_ERR_COR_BAD_TLP | + PCI_ERR_COR_BAD_DLLP)) + degraded = true; + } + + /* Replay churn on the Port side? */ + aer = pci_find_ext_capability(bridge, PCI_EXT_CAP_ID_ERR); + if (!degraded && aer) { + pci_read_config_dword(bridge, aer + PCI_ERR_COR_STATUS, + &corsts); + if (corsts & (PCI_ERR_COR_REP_TIMER | PCI_ERR_COR_REP_ROLL)) + degraded = true; + } + + if (!degraded) + goto out; + + /* Guard against a link that stays degraded despite the reset */ + if (last_reset && time_before(jiffies, last_reset + 30 * HZ)) + goto out; + last_reset = jiffies; + + pci_lock_rescan_remove(); + pci_info(pdev, "resetting degraded link\n"); + pci_stop_and_remove_bus_device(pdev); + pci_bridge_secondary_bus_reset(bridge); + /* The bus is childless now, so the reset could not wait for it */ + pcie_wait_for_link(bridge, true); + pci_rescan_bus_bridge_resize(bridge); + pci_unlock_rescan_remove(); +out: + pci_dev_put(pdev); +} + +static DECLARE_DELAYED_WORK(rts5264_link_reset_work, + rts5264_link_reset_work_fn); + +static void quirk_rts5264_reset_degraded_link(struct pci_dev *pdev) +{ + /* Links degraded already at boot are not reset-recoverable */ + if (system_state < SYSTEM_RUNNING) + return; + + schedule_delayed_work(&rts5264_link_reset_work, 3 * HZ); +} +DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REALTEK, 0x5264, + quirk_rts5264_reset_degraded_link); + +#ifdef CONFIG_PCIEASPM +/* + * MSI Claw 8 EX AI+ (MS-1T91): the BIOS hides ASPM L1 from the Link + * Capabilities of the Root Port hosting the RTS5264 reader, so the + * ASPM core never programs L1/L1SS, the PMC power-gate domain never + * gates, and the SoC cannot reach S0ix in s2idle. Mark the Port + * L1-capable. + */ +static void quirk_msi_claw8_rp_aspm_l1(struct pci_dev *dev) +{ + if (dev->devfn != PCI_DEVFN(0x1c, 0)) + return; + + if (!dmi_match(DMI_BOARD_NAME, "MS-1T91")) + return; + + if (dev->aspm_l1_support) + return; + + dev->aspm_l1_support = 1; + pci_info(dev, "ASPM: forcing L1 support (MSI Claw 8 EX AI+ hides L1 in LnkCap)\n"); +} +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xe43a, quirk_msi_claw8_rp_aspm_l1); + +/* + * SD Express cards put their own NVMe controller on the reader's link + * (see the RTS5264 quirks above), where the rtsx driver cannot apply + * the ASPM L1.2 the MSI Claw 8 EX AI+ needs for S0ix. Do the + * equivalent here when the card's NVMe controller is enumerated. + */ +static void quirk_msi_claw8_sdexpress_aspm_l1(struct pci_dev *pdev) +{ + struct pci_dev *parent; + u32 l1ss_en = PCI_L1SS_CTL1_ASPM_L1_1 | PCI_L1SS_CTL1_ASPM_L1_2 | + PCI_L1SS_CTL1_PCIPM_L1_1 | PCI_L1SS_CTL1_PCIPM_L1_2; + u32 v; + + if (!dmi_match(DMI_BOARD_NAME, "MS-1T91")) + return; + + parent = pci_upstream_bridge(pdev); + if (!parent || parent->vendor != PCI_VENDOR_ID_INTEL || + parent->device != 0xe43a) + return; + + /* Enable L1 substates while ASPM L1 is still disabled... */ + if (pdev->l1ss) { + pci_read_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, &v); + pci_write_config_dword(pdev, pdev->l1ss + PCI_L1SS_CTL1, + v | l1ss_en); + } + if (parent->l1ss) { + pci_read_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, &v); + pci_write_config_dword(parent, parent->l1ss + PCI_L1SS_CTL1, + v | l1ss_en); + } + /* ...then enable ASPM L1 upstream port first, then downstream */ + pcie_capability_set_word(parent, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L1); + pcie_capability_set_word(pdev, PCI_EXP_LNKCTL, + PCI_EXP_LNKCTL_ASPM_L1); + + pci_info(pdev, "enabled ASPM L1.2 for S0ix (SD Express card link)\n"); +} +DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID, + PCI_CLASS_STORAGE_EXPRESS, 0, + quirk_msi_claw8_sdexpress_aspm_l1); #endif diff --git a/drivers/pci/remove.c b/drivers/pci/remove.c index d8bffa21498a..4e3e11a4caa1 100644 --- a/drivers/pci/remove.c +++ b/drivers/pci/remove.c @@ -45,6 +45,13 @@ static void pci_destroy_dev(struct pci_dev *dev) device_del(&dev->dev); + /* + * pci_stop_dev() already left the PME poll list, but a runtime + * suspend racing with the driver release can put the device back + * on it. Runtime PM is permanently off now, so leave it for good. + */ + pci_pme_active(dev, false); + down_write(&pci_bus_sem); list_del(&dev->bus_list); up_write(&pci_bus_sem); diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c index 15a398bb3be2..5cc3fb7ce46d 100644 --- a/drivers/pinctrl/pinctrl-amd.c +++ b/drivers/pinctrl/pinctrl-amd.c @@ -30,6 +30,7 @@ #include #include #include +#include #include "core.h" #include "pinctrl-utils.h" @@ -499,6 +500,7 @@ static int amd_gpio_irq_set_type(struct irq_data *d, unsigned int type) struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct amd_gpio *gpio_dev = gpiochip_get_data(gc); irq_hw_number_t hwirq = irqd_to_hwirq(d); + int timeout = 100; raw_spin_lock_irqsave(&gpio_dev->lock, flags); pin_reg = readl(gpio_dev->base + hwirq * 4); @@ -568,11 +570,15 @@ static int amd_gpio_irq_set_type(struct irq_data *d, unsigned int type) pin_reg_irq_en |= mask; pin_reg_irq_en &= ~BIT(INTERRUPT_MASK_OFF); writel(pin_reg_irq_en, gpio_dev->base + hwirq * 4); - while ((readl(gpio_dev->base + hwirq * 4) & mask) != mask) - continue; + while (((readl(gpio_dev->base + hwirq * 4) & mask) != mask) && timeout--) + udelay(100); writel(pin_reg, gpio_dev->base + hwirq * 4); raw_spin_unlock_irqrestore(&gpio_dev->lock, flags); + if (timeout <= 0) + printk("%s: applying Cirrus quirk after timeout when setting irq pin\n", + __func__); + return ret; } diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index b668a3cc118e..38603b2cb34e 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -354,6 +354,173 @@ static ssize_t ap_mode_entry_show(struct device *dev, return sysfs_emit(buf, "%s\n", ap_driven_altmode ? "yes" : "no"); } +static ssize_t cec_wake_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cros_ec_dev *ec = to_cros_ec_dev(dev); + ssize_t count = 0; + struct ec_response_cec_port_count resp_cec_ports; + int ret; + int i; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_PORT_COUNT, NULL, 0, + &resp_cec_ports, sizeof(resp_cec_ports)); + if (ret < 0) + return -EIO; + + for (i = 0; i < resp_cec_ports.port_count; i++) { + struct ec_response_cec_get resp; + struct ec_params_cec_get req = { + .cmd = CEC_CMD_WAKE_ENABLE, + .port = i, + }; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_GET, + &req, sizeof(req), &resp, sizeof(resp)); + if (ret >= 0) { + count += sysfs_emit_at(buf, count, "%d %d\n", i, resp.val); + } + } + + return count ? : -EIO; +} + +static ssize_t cec_wake_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cros_ec_dev *ec = to_cros_ec_dev(dev); + struct ec_params_cec_set params = { + .cmd = CEC_CMD_WAKE_ENABLE, + }; + char *mut, *port_token, *val_token; + u8 port, val; + int ret; + + mut = kstrdup(buf, GFP_KERNEL); + if (!mut) + return -ENOMEM; + + val_token = mut; + port_token = strsep(&val_token, " "); + if (!port_token || !*port_token) { + ret = -EINVAL; + goto exit; + } + ret = kstrtou8(port_token, 0, &port); + if (ret) + goto exit; + if (port >= EC_CEC_MAX_PORTS) { + ret = -EINVAL; + goto exit; + } + params.port = port; + + ret = kstrtou8(val_token, 0, &val); + if (ret) + goto exit; + if (val > 1) { + ret = -EINVAL; + goto exit; + } + params.val = val; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_SET, ¶ms, sizeof(params), + NULL, 0); + if (ret < 0) { + ret = -EIO; + goto exit; + } + +exit: + kfree(mut); + return ret != 0 ? ret : count; +} + +static ssize_t cec_phys_addr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cros_ec_dev *ec = to_cros_ec_dev(dev); + ssize_t count = 0; + struct ec_response_cec_port_count resp_cec_ports; + int ret; + int i; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_PORT_COUNT, NULL, 0, + &resp_cec_ports, sizeof(resp_cec_ports)); + if (ret < 0) + return -EIO; + + for (i = 0; i < resp_cec_ports.port_count; i++) { + struct ec_response_cec_get_pa resp; + struct ec_params_cec_get_pa req = { + .cmd = CEC_CMD_PHYSICAL_ADDRESS, + .port = i, + }; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_GET_PA, + &req, sizeof(req), &resp, sizeof(resp)); + if (ret >= 0) { + count += sysfs_emit_at(buf, count, "%d %d\n", i, resp.val); + } + } + + return count ? : -EIO; +} + +static ssize_t cec_phys_addr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct cros_ec_dev *ec = to_cros_ec_dev(dev); + struct ec_params_cec_set_pa params = { + .cmd = CEC_CMD_PHYSICAL_ADDRESS, + }; + char *mut, *port_token, *val_token; + u8 port; + u16 val; + int ret; + + mut = kstrdup(buf, GFP_KERNEL); + if (!mut) + return -ENOMEM; + + val_token = mut; + port_token = strsep(&val_token, " "); + if (!port_token || !*port_token) { + ret = -EINVAL; + goto exit; + } + ret = kstrtou8(port_token, 0, &port); + if (ret) + goto exit; + if (port >= EC_CEC_MAX_PORTS) { + ret = -EINVAL; + goto exit; + } + params.port = port; + + ret = kstrtou16(val_token, 0, &val); + if (ret) + goto exit; + if (val > 0xFFFF) { + ret = -EINVAL; + goto exit; + } + params.val = val; + + ret = cros_ec_cmd(ec->ec_dev, 0, EC_CMD_CEC_SET_PA, ¶ms, sizeof(params), + NULL, 0); + if (ret < 0) { + ret = -EIO; + goto exit; + } + +exit: + kfree(mut); + return ret != 0 ? ret : count; +} + /* Module initialization */ static DEVICE_ATTR_RW(reboot); @@ -362,6 +529,8 @@ static DEVICE_ATTR_RO(flashinfo); static DEVICE_ATTR_RW(kb_wake_angle); static DEVICE_ATTR_RO(usbpdmuxinfo); static DEVICE_ATTR_RO(ap_mode_entry); +static DEVICE_ATTR_RW(cec_wake_enable); +static DEVICE_ATTR_RW(cec_phys_addr); static struct attribute *__ec_attrs[] = { &dev_attr_kb_wake_angle.attr, @@ -370,6 +539,8 @@ static struct attribute *__ec_attrs[] = { &dev_attr_flashinfo.attr, &dev_attr_usbpdmuxinfo.attr, &dev_attr_ap_mode_entry.attr, + &dev_attr_cec_wake_enable.attr, + &dev_attr_cec_phys_addr.attr, NULL, }; diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index b54b5212b204..690b6c342f99 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -572,9 +572,12 @@ config MSI_WMI config MSI_WMI_PLATFORM tristate "MSI WMI Platform features" + depends on ACPI_BATTERY depends on ACPI_WMI depends on DMI depends on HWMON + select ACPI_PLATFORM_PROFILE + select FW_ATTR_CLASS help Say Y here if you want to have support for WMI-based platform features like fan sensor access on MSI machines. @@ -1063,6 +1066,16 @@ config OXP_EC source "drivers/platform/x86/tuxedo/Kconfig" +config ZOTAC_ZONE_PLATFORM + tristate "Zotac Zone platform driver" + select FW_ATTR_CLASS + select ACPI_PLATFORM_PROFILE + help + Support for fans and PPT on Zotac Zone devices. + + To compile this driver as a module, choose M here: the module + will be called zotac-zone. + endif # X86_PLATFORM_DEVICES config P2SB diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 872ac3842391..5f172304abe5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -170,3 +170,5 @@ obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o # OneXPlayer obj-$(CONFIG_OXP_EC) += oxpec.o + +obj-$(CONFIG_ZOTAC_ZONE_PLATFORM) += zotac-zone-platform.o diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index e835779b6f5f..1bd159496700 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4192,7 +4192,7 @@ static int asus_wmi_platform_profile_get(struct device *dev, *profile = PLATFORM_PROFILE_PERFORMANCE; break; case ASUS_THROTTLE_THERMAL_POLICY_SILENT: - *profile = PLATFORM_PROFILE_QUIET; + *profile = PLATFORM_PROFILE_LOW_POWER; break; default: return -EINVAL; @@ -4216,7 +4216,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, case PLATFORM_PROFILE_BALANCED: tp = ASUS_THROTTLE_THERMAL_POLICY_DEFAULT; break; - case PLATFORM_PROFILE_QUIET: + case PLATFORM_PROFILE_LOW_POWER: tp = ASUS_THROTTLE_THERMAL_POLICY_SILENT; break; default: @@ -4229,7 +4229,7 @@ static int asus_wmi_platform_profile_set(struct device *dev, static int asus_wmi_platform_profile_probe(void *drvdata, unsigned long *choices) { - set_bit(PLATFORM_PROFILE_QUIET, choices); + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); set_bit(PLATFORM_PROFILE_BALANCED, choices); set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); diff --git a/drivers/platform/x86/msi-wmi-platform.c b/drivers/platform/x86/msi-wmi-platform.c index e912fcc12d12..0b1b64182cc3 100644 --- a/drivers/platform/x86/msi-wmi-platform.c +++ b/drivers/platform/x86/msi-wmi-platform.c @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Linux driver for WMI platform features on MSI notebooks. + * Linux driver for WMI platform features on MSI notebooks and handhelds. * - * Copyright (C) 2024 Armin Wolf + * Copyright (C) 2024-2025 Armin Wolf + * Copyright (C) 2025 Antheas Kapenekakis */ #define pr_format(fmt) KBUILD_MODNAME ": " fmt @@ -16,26 +17,38 @@ #include #include #include +#include +#include #include +#include #include +#include +#include #include #include #include #include +#include +#include #include #include +#include #include +#include "firmware_attributes_class.h" + #define DRIVER_NAME "msi-wmi-platform" #define MSI_PLATFORM_GUID "ABBC0F6E-8EA1-11D1-00A0-C90629100000" #define MSI_WMI_PLATFORM_INTERFACE_VERSION 2 +/* Get_WMI() WMI method */ #define MSI_PLATFORM_WMI_MAJOR_OFFSET 1 #define MSI_PLATFORM_WMI_MINOR_OFFSET 2 +/* Get_EC() and Set_EC() WMI methods */ #define MSI_PLATFORM_EC_FLAGS_OFFSET 1 #define MSI_PLATFORM_EC_MINOR_MASK GENMASK(3, 0) #define MSI_PLATFORM_EC_MAJOR_MASK GENMASK(5, 4) @@ -43,6 +56,39 @@ #define MSI_PLATFORM_EC_IS_TIGERLAKE BIT(7) #define MSI_PLATFORM_EC_VERSION_OFFSET 2 +/* Get_Fan() and Set_Fan() WMI methods */ +#define MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED 0x0 +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE 0x1 +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE 0x2 +#define MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE 0x1 +#define MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE 0x2 + +/* Get_AP() and Set_AP() WMI methods */ +#define MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE 0x1 +#define MSI_PLATFORM_AP_FAN_FLAGS_OFFSET 1 +#define MSI_PLATFORM_AP_ENABLE_FAN_TABLES BIT(7) + +/* Get_Data() and Set_Data() Shift Mode Register */ +#define MSI_PLATFORM_SHIFT_ADDR 0xd2 +#define MSI_PLATFORM_SHIFT_DISABLE BIT(7) +#define MSI_PLATFORM_SHIFT_ENABLE (BIT(7) | BIT(6)) +#define MSI_PLATFORM_SHIFT_SPORT (MSI_PLATFORM_SHIFT_ENABLE + 4) +#define MSI_PLATFORM_SHIFT_COMFORT (MSI_PLATFORM_SHIFT_ENABLE + 0) +#define MSI_PLATFORM_SHIFT_GREEN (MSI_PLATFORM_SHIFT_ENABLE + 1) +#define MSI_PLATFORM_SHIFT_ECO (MSI_PLATFORM_SHIFT_ENABLE + 2) +#define MSI_PLATFORM_SHIFT_USER (MSI_PLATFORM_SHIFT_ENABLE + 3) +#define MSI_PLATFORM_SHIFT_MANUAL (MSI_PLATFORM_SHIFT_ENABLE + 6) + +/* Get_Data() and Set_Data() Params */ +#define MSI_PLATFORM_PL1_ADDR 0x50 +#define MSI_PLATFORM_PL2_ADDR 0x51 +#define MSI_PLATFORM_PL3_ADDR 0x52 +#define MSI_PLATFORM_AMDSTAPM_ADDR 0xc5 +#define MSI_PLATFORM_BAT_ADDR 0xd7 + +#define MSI_PLATFORM_AMDSTAPM_SUPPORTED BIT(7) +#define MSI_PLATFORM_AMDSTAPM_DISABLE BIT(0) + static bool force; module_param_unsafe(force, bool, 0); MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions"); @@ -79,9 +125,76 @@ enum msi_wmi_platform_method { MSI_PLATFORM_GET_WMI = 0x1d, }; +struct msi_wmi_platform_quirk { + bool shift_mode; /* Shift mode is supported */ + bool charge_threshold; /* Charge threshold is supported */ + bool dual_fans; /* For devices with two hwmon fans */ + bool restore_curves; /* Restore factory curves on unload */ + bool disable_stapm; /* Disable AMD STAPM auto power management on load */ + u8 custom_shift; /* EC Shift value for PLATFORM_PROFILE_CUSTOM, 0 if unsupported */ + int pl1_min; /* Minimum PL1 value */ + int pl1_max; /* Maximum PL1 value */ + int pl2_min; /* Minimum PL2 value */ + int pl2_max; /* Maximum PL2 value */ + int pl3_min; /* Minimum PL3 value */ + int pl3_max; /* Maximum PL3 value */ +}; + +struct msi_wmi_platform_factory_curves { + u8 cpu_fan_table[32]; + u8 gpu_fan_table[32]; + u8 cpu_temp_table[32]; + u8 gpu_temp_table[32]; +}; + struct msi_wmi_platform_data { struct wmi_device *wdev; + struct msi_wmi_platform_quirk *quirks; struct mutex wmi_lock; /* Necessary when calling WMI methods */ + struct device *ppdev; + struct msi_wmi_platform_factory_curves factory_curves; + struct acpi_battery_hook battery_hook; + struct device *fw_attrs_dev; + struct kset *fw_attrs_kset; +}; + +enum msi_fw_attr_id { + MSI_ATTR_PPT_PL1_SPL, + MSI_ATTR_PPT_PL2_SPPT, + MSI_ATTR_PPT_PL3_FPPT, +}; + +static const char *const msi_fw_attr_name[] = { + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl", + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt", + [MSI_ATTR_PPT_PL3_FPPT] = "ppt_pl3_fppt", +}; + +static const char *const msi_fw_attr_desc[] = { + [MSI_ATTR_PPT_PL1_SPL] = "CPU Steady package limit (PL1/SPL)", + [MSI_ATTR_PPT_PL2_SPPT] = "CPU Boost slow package limit (PL2/SPPT)", + [MSI_ATTR_PPT_PL3_FPPT] = "CPU Boost fast package limit (PL3/FPPT)", +}; + +#define MSI_ATTR_LANGUAGE_CODE "en_US.UTF-8" + +struct msi_fw_attr { + struct msi_wmi_platform_data *data; + enum msi_fw_attr_id fw_attr_id; + struct attribute_group attr_group; + struct kobj_attribute display_name; + struct kobj_attribute current_value; + struct kobj_attribute min_value; + struct kobj_attribute max_value; + + u32 min; + u32 max; + + int (*get_value)(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, char *buf); + ssize_t (*set_value)(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, const char *buf, + size_t count); }; struct msi_wmi_platform_debugfs_data { @@ -124,6 +237,96 @@ static const char * const msi_wmi_platform_debugfs_names[] = { "get_wmi" }; +static struct msi_wmi_platform_quirk quirk_default = {}; +static struct msi_wmi_platform_quirk quirk_gen1 = { + .shift_mode = true, + .charge_threshold = true, + .dual_fans = true, + .restore_curves = true, + .pl1_min = 8, + .pl1_max = 43, + .pl2_min = 9, + .pl2_max = 45 +}; +static struct msi_wmi_platform_quirk quirk_gen2 = { + .shift_mode = true, + .charge_threshold = true, + .dual_fans = true, + .restore_curves = true, + .pl1_min = 8, + .pl1_max = 30, + .pl2_min = 9, + .pl2_max = 37 +}; +static struct msi_wmi_platform_quirk quirk_gen3 = { + .shift_mode = true, + .charge_threshold = true, + .dual_fans = true, + .restore_curves = true, + .disable_stapm = true, + .pl1_min = 7, + .pl1_max = 35, + .pl2_min = 9, + .pl2_max = 40, + .pl3_min = 10, + .pl3_max = 48, +}; +static struct msi_wmi_platform_quirk quirk_gen4 = { + .shift_mode = true, + .charge_threshold = true, + .dual_fans = true, + .restore_curves = true, + .custom_shift = MSI_PLATFORM_SHIFT_MANUAL, + .pl1_min = 8, + .pl1_max = 35, + .pl2_min = 9, + .pl2_max = 45, +}; + +static const struct dmi_system_id msi_quirks[] = { + { + .ident = "MSI Claw A1M", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T41"), + }, + .driver_data = &quirk_gen1, + }, + { + .ident = "MSI Claw 7 AI+ A2VM", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T42"), + }, + .driver_data = &quirk_gen2, + }, + { + .ident = "MSI Claw 8 AI+ A2VM", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"), + }, + .driver_data = &quirk_gen2, + }, + { + .ident = "MSI Claw A8 BZ2EM", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T8K"), + }, + .driver_data = &quirk_gen3, + }, + { + .ident = "MSI Claw 8 EX AI+ CG3EM", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T91"), + }, + .driver_data = &quirk_gen4, + }, + { } +}; + static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length) { if (obj->type != ACPI_TYPE_BUFFER) @@ -140,45 +343,332 @@ static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, siz return 0; } -static int msi_wmi_platform_query(struct msi_wmi_platform_data *data, - enum msi_wmi_platform_method method, u8 *input, - size_t input_length, u8 *output, size_t output_length) +static int msi_wmi_platform_query_unlocked(struct msi_wmi_platform_data *data, + enum msi_wmi_platform_method method, u8 *buffer, + size_t length) { struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; struct acpi_buffer in = { - .length = input_length, - .pointer = input + .length = length, + .pointer = buffer }; union acpi_object *obj; acpi_status status; int ret; - if (!input_length || !output_length) + if (!length) return -EINVAL; + status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = out.pointer; + if (!obj) + return -ENODATA; + + ret = msi_wmi_platform_parse_buffer(obj, buffer, length); + kfree(obj); + + return ret; +} + +static int msi_wmi_platform_query(struct msi_wmi_platform_data *data, + enum msi_wmi_platform_method method, u8 *buffer, + size_t length) +{ /* * The ACPI control method responsible for handling the WMI method calls * is not thread-safe. Because of this we have to do the locking ourself. */ scoped_guard(mutex, &data->wmi_lock) { - status = wmidev_evaluate_method(data->wdev, 0x0, method, &in, &out); - if (ACPI_FAILURE(status)) - return -EIO; + return msi_wmi_platform_query_unlocked(data, method, buffer, length); } +} - obj = out.pointer; - if (!obj) - return -ENODATA; +static ssize_t msi_wmi_platform_fan_table_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { sattr->nr }; + u8 fan_percent; + int ret; - ret = msi_wmi_platform_parse_buffer(obj, output, output_length); - kfree(obj); + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, sizeof(buffer)); + if (ret < 0) + return ret; - return ret; + fan_percent = buffer[sattr->index + 1]; + if (fan_percent > 100) + return -EIO; + + return sysfs_emit(buf, "%d\n", fixp_linear_interpolate(0, 0, 100, 255, fan_percent)); +} + +static ssize_t msi_wmi_platform_fan_table_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { sattr->nr }; + long speed; + int ret; + + ret = kstrtol(buf, 10, &speed); + if (ret < 0) + return ret; + + speed = clamp_val(speed, 0, 255); + + guard(mutex)(&data->wmi_lock); + + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_FAN, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + buffer[0] = sattr->nr; + buffer[sattr->index + 1] = fixp_linear_interpolate(0, 0, 255, 100, speed); + + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t msi_wmi_platform_temp_table_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { sattr->nr }; + u8 temp_c; + int ret; + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_TEMPERATURE, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + temp_c = buffer[sattr->index + 1]; + + return sysfs_emit(buf, "%d\n", temp_c); +} + +static ssize_t msi_wmi_platform_temp_table_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { sattr->nr }; + long temp_c; + int ret; + + ret = kstrtol(buf, 10, &temp_c); + if (ret < 0) + return ret; + + temp_c = clamp_val(temp_c, 0, 255); + + guard(mutex)(&data->wmi_lock); + + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_GET_TEMPERATURE, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + buffer[0] = sattr->nr; + buffer[sattr->index + 1] = temp_c; + + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_TEMPERATURE, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + return count; +} + +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x0); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x3); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x4); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x5); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x6); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE, 0x7); + +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x1); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x2); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x3); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x4); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x5); +static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE, 0x6); + +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x0); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x3); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x4); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x5); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x6); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, msi_wmi_platform_temp_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE, 0x7); + +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x1); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x2); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x3); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x4); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x5); +static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, msi_wmi_platform_fan_table, + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE, 0x6); + +static struct attribute *msi_wmi_platform_hwmon_attrs[] = { + &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, + + &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr, + + &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr, + NULL +}; +ATTRIBUTE_GROUPS(msi_wmi_platform_hwmon); + +static int msi_wmi_platform_curves_save(struct msi_wmi_platform_data *data) +{ + int ret; + + data->factory_curves.cpu_fan_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE; + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_GET_FAN, + data->factory_curves.cpu_fan_table, + sizeof(data->factory_curves.cpu_fan_table)); + if (ret < 0) + return ret; + data->factory_curves.cpu_fan_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_CPU_FAN_TABLE; + + data->factory_curves.gpu_fan_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE; + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_GET_FAN, + data->factory_curves.gpu_fan_table, + sizeof(data->factory_curves.gpu_fan_table)); + if (ret < 0) + return ret; + data->factory_curves.gpu_fan_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_GPU_FAN_TABLE; + + data->factory_curves.cpu_temp_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE; + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_GET_TEMPERATURE, + data->factory_curves.cpu_temp_table, + sizeof(data->factory_curves.cpu_temp_table)); + if (ret < 0) + return ret; + data->factory_curves.cpu_temp_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_CPU_TEMP_TABLE; + + data->factory_curves.gpu_temp_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE; + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_GET_TEMPERATURE, + data->factory_curves.gpu_temp_table, + sizeof(data->factory_curves.gpu_temp_table)); + if (ret < 0) + return ret; + data->factory_curves.gpu_temp_table[0] = + MSI_PLATFORM_FAN_SUBFEATURE_GPU_TEMP_TABLE; + + return 0; } +static int msi_wmi_platform_curves_load(struct msi_wmi_platform_data *data) +{ + u8 buffer[32] = { }; + int ret; + + memcpy(buffer, data->factory_curves.cpu_fan_table, + sizeof(data->factory_curves.cpu_fan_table)); + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + memcpy(buffer, data->factory_curves.gpu_fan_table, + sizeof(data->factory_curves.gpu_fan_table)); + ret = msi_wmi_platform_query_unlocked(data, MSI_PLATFORM_SET_FAN, + buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + memcpy(buffer, data->factory_curves.cpu_temp_table, + sizeof(data->factory_curves.cpu_temp_table)); + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + memcpy(buffer, data->factory_curves.gpu_temp_table, + sizeof(data->factory_curves.gpu_temp_table)); + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_SET_TEMPERATURE, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + return 0; +} + + static umode_t msi_wmi_platform_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) { + if (type == hwmon_pwm && attr == hwmon_pwm_enable) + return 0644; + return 0444; } @@ -186,28 +676,114 @@ static int msi_wmi_platform_read(struct device *dev, enum hwmon_sensor_types typ int channel, long *val) { struct msi_wmi_platform_data *data = dev_get_drvdata(dev); - u8 input[32] = { 0 }; - u8 output[32]; + u8 buffer[32] = { 0 }; u16 value; + u8 flags; int ret; - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, input, sizeof(input), output, - sizeof(output)); - if (ret < 0) - return ret; + switch (type) { + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + buffer[0] = MSI_PLATFORM_FAN_SUBFEATURE_FAN_SPEED; + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_FAN, buffer, + sizeof(buffer)); + if (ret < 0) + return ret; + + value = get_unaligned_be16(&buffer[channel * 2 + 1]); + if (!value) + *val = 0; + else + *val = 480000 / value; + + return 0; + default: + return -EOPNOTSUPP; + } + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE; + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_AP, buffer, + sizeof(buffer)); + if (ret < 0) + return ret; + + flags = buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET]; + if (flags & MSI_PLATFORM_AP_ENABLE_FAN_TABLES) + *val = 1; + else + *val = 2; + + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} - value = get_unaligned_be16(&output[channel * 2 + 1]); - if (!value) - *val = 0; - else - *val = 480000 / value; +static int msi_wmi_platform_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { }; + int ret; - return 0; + guard(mutex)(&data->wmi_lock); + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_enable: + + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE; + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_GET_AP, buffer, + sizeof(buffer)); + if (ret < 0) + return ret; + + buffer[0] = MSI_PLATFORM_AP_SUBFEATURE_FAN_MODE; + switch (val) { + case 1: + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] |= + MSI_PLATFORM_AP_ENABLE_FAN_TABLES; + break; + case 2: + buffer[MSI_PLATFORM_AP_FAN_FLAGS_OFFSET] &= + ~MSI_PLATFORM_AP_ENABLE_FAN_TABLES; + break; + default: + return -EINVAL; + } + + ret = msi_wmi_platform_query_unlocked( + data, MSI_PLATFORM_SET_AP, buffer, + sizeof(buffer)); + if (ret < 0) + return ret; + + if (val == 2 && data->quirks->restore_curves) { + ret = msi_wmi_platform_curves_load(data); + if (ret < 0) + return ret; + } + + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } } static const struct hwmon_ops msi_wmi_platform_ops = { .is_visible = msi_wmi_platform_is_visible, .read = msi_wmi_platform_read, + .write = msi_wmi_platform_write, }; static const struct hwmon_channel_info * const msi_wmi_platform_info[] = { @@ -217,6 +793,10 @@ static const struct hwmon_channel_info * const msi_wmi_platform_info[] = { HWMON_F_INPUT, HWMON_F_INPUT ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_ENABLE, + HWMON_PWM_ENABLE + ), NULL }; @@ -225,8 +805,519 @@ static const struct hwmon_chip_info msi_wmi_platform_chip_info = { .info = msi_wmi_platform_info, }; -static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, size_t length, - loff_t *offset) +static const struct hwmon_channel_info * const msi_wmi_platform_info_dual[] = { + HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT, + HWMON_F_INPUT + ), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_ENABLE, + HWMON_PWM_ENABLE + ), + NULL +}; + +static const struct hwmon_chip_info msi_wmi_platform_chip_info_dual = { + .ops = &msi_wmi_platform_ops, + .info = msi_wmi_platform_info_dual, +}; + +static int msi_wmi_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + struct msi_wmi_platform_data *data = drvdata; + + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + + if (data->quirks->custom_shift) + set_bit(PLATFORM_PROFILE_CUSTOM, choices); + + return 0; +} + +static int msi_wmi_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 custom = data->quirks->custom_shift; + int ret; + + u8 buffer[32] = { }; + + buffer[0] = MSI_PLATFORM_SHIFT_ADDR; + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + if (buffer[0] != 1) + return -EINVAL; + + if (custom && buffer[1] == custom) { + *profile = PLATFORM_PROFILE_CUSTOM; + return 0; + } + + switch (buffer[1]) { + case MSI_PLATFORM_SHIFT_SPORT: + *profile = PLATFORM_PROFILE_PERFORMANCE; + return 0; + case MSI_PLATFORM_SHIFT_COMFORT: + *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; + return 0; + case MSI_PLATFORM_SHIFT_GREEN: + *profile = PLATFORM_PROFILE_BALANCED; + return 0; + case MSI_PLATFORM_SHIFT_ECO: + *profile = PLATFORM_PROFILE_LOW_POWER; + return 0; + default: + return -EINVAL; + } +} + +static int msi_wmi_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct msi_wmi_platform_data *data = dev_get_drvdata(dev); + u8 buffer[32] = { }; + + buffer[0] = MSI_PLATFORM_SHIFT_ADDR; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + buffer[1] = MSI_PLATFORM_SHIFT_SPORT; + break; + case PLATFORM_PROFILE_BALANCED_PERFORMANCE: + buffer[1] = MSI_PLATFORM_SHIFT_COMFORT; + break; + case PLATFORM_PROFILE_BALANCED: + buffer[1] = MSI_PLATFORM_SHIFT_GREEN; + break; + case PLATFORM_PROFILE_LOW_POWER: + buffer[1] = MSI_PLATFORM_SHIFT_ECO; + break; + case PLATFORM_PROFILE_CUSTOM: + if (!data->quirks->custom_shift) + return -EINVAL; + buffer[1] = data->quirks->custom_shift; + break; + default: + return -EINVAL; + } + + return msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer)); +} + +static const struct platform_profile_ops msi_wmi_platform_profile_ops = { + .probe = msi_wmi_platform_profile_probe, + .profile_get = msi_wmi_platform_profile_get, + .profile_set = msi_wmi_platform_profile_set, +}; + +/* Firmware Attributes setup */ +static int data_get_addr(struct msi_wmi_platform_data *data, + const enum msi_fw_attr_id id) +{ + switch (id) { + case MSI_ATTR_PPT_PL1_SPL: + return MSI_PLATFORM_PL1_ADDR; + case MSI_ATTR_PPT_PL2_SPPT: + return MSI_PLATFORM_PL2_ADDR; + case MSI_ATTR_PPT_PL3_FPPT: + return MSI_PLATFORM_PL3_ADDR; + default: + pr_warn("Invalid attribute id %d\n", id); + return -EINVAL; + } +} + +static ssize_t data_set_value(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, const char *buf, + size_t count) +{ + u8 buffer[32] = { 0 }; + int ret, fwid; + u32 value; + + fwid = data_get_addr(data, fw_attr->fw_attr_id); + if (fwid < 0) + return fwid; + + ret = kstrtou32(buf, 10, &value); + if (ret) + return ret; + + if (fw_attr->min >= 0 && value < fw_attr->min) + return -EINVAL; + if (fw_attr->max >= 0 && value > fw_attr->max) + return -EINVAL; + + buffer[0] = fwid; + put_unaligned_le32(value, &buffer[1]); + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer)); + if (ret) { + pr_warn("Failed to set_data with id %d: %d\n", + fw_attr->fw_attr_id, ret); + return ret; + } + + return count; +} + +static int data_get_value(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, char *buf) +{ + u8 buffer[32] = { 0 }; + u32 value; + int ret, addr; + + addr = data_get_addr(data, fw_attr->fw_attr_id); + if (addr < 0) + return addr; + + buffer[0] = addr; + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer)); + if (ret) { + pr_warn("Failed to show set_data for id %d: %d\n", + fw_attr->fw_attr_id, ret); + return ret; + } + + value = get_unaligned_le32(&buffer[1]); + + return sysfs_emit(buf, "%d\n", value); +} + +static ssize_t display_name_language_code_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "%s\n", MSI_ATTR_LANGUAGE_CODE); +} + +static struct kobj_attribute fw_attr_display_name_language_code = + __ATTR_RO(display_name_language_code); + +static ssize_t scalar_increment_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "1\n"); +} + +static struct kobj_attribute fw_attr_scalar_increment = + __ATTR_RO(scalar_increment); + +static ssize_t pending_reboot_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return sysfs_emit(buf, "0\n"); +} + +static struct kobj_attribute fw_attr_pending_reboot = __ATTR_RO(pending_reboot); + +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct msi_fw_attr *fw_attr = + container_of(attr, struct msi_fw_attr, display_name); + + return sysfs_emit(buf, "%s\n", msi_fw_attr_desc[fw_attr->fw_attr_id]); +} + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct msi_fw_attr *fw_attr = + container_of(attr, struct msi_fw_attr, current_value); + + return fw_attr->get_value(fw_attr->data, fw_attr, buf); +} + +static ssize_t current_value_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct msi_fw_attr *fw_attr = + container_of(attr, struct msi_fw_attr, current_value); + + return fw_attr->set_value(fw_attr->data, fw_attr, buf, count); +} + +static ssize_t type_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "integer\n"); +} + +static struct kobj_attribute fw_attr_type_int = { + .attr = { .name = "type", .mode = 0444 }, + .show = type_show, +}; + +static ssize_t min_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct msi_fw_attr *fw_attr = + container_of(attr, struct msi_fw_attr, min_value); + + return sysfs_emit(buf, "%d\n", fw_attr->min); +} + +static ssize_t max_value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct msi_fw_attr *fw_attr = + container_of(attr, struct msi_fw_attr, max_value); + + return sysfs_emit(buf, "%d\n", fw_attr->max); +} + +#define FW_ATTR_ENUM_MAX_ATTRS 7 + +static int +msi_fw_attr_init(struct msi_wmi_platform_data *data, + const enum msi_fw_attr_id fw_attr_id, + struct kobj_attribute *fw_attr_type, const s32 min, + const s32 max, + int (*get_value)(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, char *buf), + ssize_t (*set_value)(struct msi_wmi_platform_data *data, + struct msi_fw_attr *fw_attr, + const char *buf, size_t count)) +{ + struct msi_fw_attr *fw_attr; + struct attribute **attrs; + int idx = 0; + + fw_attr = devm_kzalloc(&data->wdev->dev, sizeof(*fw_attr), GFP_KERNEL); + if (!fw_attr) + return -ENOMEM; + + attrs = devm_kcalloc(&data->wdev->dev, FW_ATTR_ENUM_MAX_ATTRS + 1, + sizeof(*attrs), GFP_KERNEL); + if (!attrs) + return -ENOMEM; + + fw_attr->data = data; + fw_attr->fw_attr_id = fw_attr_id; + fw_attr->attr_group.name = msi_fw_attr_name[fw_attr_id]; + fw_attr->attr_group.attrs = attrs; + fw_attr->get_value = get_value; + fw_attr->set_value = set_value; + + attrs[idx++] = &fw_attr_type->attr; + if (fw_attr_type == &fw_attr_type_int) + attrs[idx++] = &fw_attr_scalar_increment.attr; + attrs[idx++] = &fw_attr_display_name_language_code.attr; + + sysfs_attr_init(&fw_attr->display_name.attr); + fw_attr->display_name.attr.name = "display_name"; + fw_attr->display_name.attr.mode = 0444; + fw_attr->display_name.show = display_name_show; + attrs[idx++] = &fw_attr->display_name.attr; + + sysfs_attr_init(&fw_attr->current_value.attr); + fw_attr->current_value.attr.name = "current_value"; + fw_attr->current_value.attr.mode = 0644; + fw_attr->current_value.show = current_value_show; + fw_attr->current_value.store = current_value_store; + attrs[idx++] = &fw_attr->current_value.attr; + + if (min >= 0) { + fw_attr->min = min; + sysfs_attr_init(&fw_attr->min_value.attr); + fw_attr->min_value.attr.name = "min_value"; + fw_attr->min_value.attr.mode = 0444; + fw_attr->min_value.show = min_value_show; + attrs[idx++] = &fw_attr->min_value.attr; + } else { + fw_attr->min = -1; + } + + if (max >= 0) { + fw_attr->max = max; + sysfs_attr_init(&fw_attr->max_value.attr); + fw_attr->max_value.attr.name = "max_value"; + fw_attr->max_value.attr.mode = 0444; + fw_attr->max_value.show = max_value_show; + attrs[idx++] = &fw_attr->max_value.attr; + } else { + fw_attr->max = -1; + } + + attrs[idx] = NULL; + return sysfs_create_group(&data->fw_attrs_kset->kobj, &fw_attr->attr_group); +} + +static void msi_kset_unregister(void *data) +{ + struct kset *kset = data; + + sysfs_remove_file(&kset->kobj, &fw_attr_pending_reboot.attr); + kset_unregister(kset); +} + +static void msi_fw_attrs_dev_unregister(void *data) +{ + struct device *fw_attrs_dev = data; + + device_unregister(fw_attrs_dev); +} + +static int msi_wmi_fw_attrs_init(struct msi_wmi_platform_data *data) +{ + int err; + + data->fw_attrs_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(data->fw_attrs_dev)) + return PTR_ERR(data->fw_attrs_dev); + + err = devm_add_action_or_reset(&data->wdev->dev, + msi_fw_attrs_dev_unregister, + data->fw_attrs_dev); + if (err) + return err; + + data->fw_attrs_kset = kset_create_and_add("attributes", NULL, + &data->fw_attrs_dev->kobj); + if (!data->fw_attrs_kset) + return -ENOMEM; + + err = sysfs_create_file(&data->fw_attrs_kset->kobj, + &fw_attr_pending_reboot.attr); + if (err) { + kset_unregister(data->fw_attrs_kset); + return err; + } + + err = devm_add_action_or_reset(&data->wdev->dev, msi_kset_unregister, + data->fw_attrs_kset); + if (err) + return err; + + if (data->quirks->pl1_max) { + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL1_SPL, + &fw_attr_type_int, data->quirks->pl1_min, + data->quirks->pl1_max, &data_get_value, + &data_set_value); + if (err) + return err; + } + + if (data->quirks->pl2_max) { + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL2_SPPT, + &fw_attr_type_int, data->quirks->pl2_min, + data->quirks->pl2_max, &data_get_value, + &data_set_value); + if (err) + return err; + } + + if (data->quirks->pl3_max) { + err = msi_fw_attr_init(data, MSI_ATTR_PPT_PL3_FPPT, + &fw_attr_type_int, data->quirks->pl3_min, + data->quirks->pl3_max, &data_get_value, + &data_set_value); + if (err) + return err; + } + + return 0; +} + +static int msi_platform_psy_ext_get_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct msi_wmi_platform_data *msi = data; + u8 buffer[32] = { 0 }; + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + buffer[0] = MSI_PLATFORM_BAT_ADDR; + ret = msi_wmi_platform_query(msi, MSI_PLATFORM_GET_DATA, + buffer, sizeof(buffer)); + if (ret) + return ret; + + val->intval = buffer[1] & ~BIT(7); + if (val->intval > 100) + return -EINVAL; + + return 0; + default: + return -EINVAL; + } +} + +static int msi_platform_psy_ext_set_prop(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct msi_wmi_platform_data *msi = data; + u8 buffer[32] = { 0 }; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD: + if (val->intval > 100) + return -EINVAL; + buffer[0] = MSI_PLATFORM_BAT_ADDR; + buffer[1] = val->intval | BIT(7); + return msi_wmi_platform_query(msi, MSI_PLATFORM_SET_DATA, + buffer, sizeof(buffer)); + default: + return -EINVAL; + } +} + +static int +msi_platform_psy_prop_is_writeable(struct power_supply *psy, + const struct power_supply_ext *ext, + void *data, enum power_supply_property psp) +{ + return true; +} + +static const enum power_supply_property oxp_psy_ext_props[] = { + POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, +}; + +static const struct power_supply_ext msi_platform_psy_ext = { + .name = "msi-platform-charge-control", + .properties = oxp_psy_ext_props, + .num_properties = ARRAY_SIZE(oxp_psy_ext_props), + .get_property = msi_platform_psy_ext_get_prop, + .set_property = msi_platform_psy_ext_set_prop, + .property_is_writeable = msi_platform_psy_prop_is_writeable, +}; + +static int msi_wmi_platform_battery_add(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + struct msi_wmi_platform_data *data = + container_of(hook, struct msi_wmi_platform_data, battery_hook); + + return power_supply_register_extension(battery, &msi_platform_psy_ext, + &data->wdev->dev, data); +} + +static int msi_wmi_platform_battery_remove(struct power_supply *battery, + struct acpi_battery_hook *hook) +{ + power_supply_unregister_extension(battery, &msi_platform_psy_ext); + return 0; +} + +static ssize_t msi_wmi_platform_debugfs_write(struct file *fp, const char __user *input, + size_t length, loff_t *offset) { struct seq_file *seq = fp->private_data; struct msi_wmi_platform_debugfs_data *data = seq->private; @@ -246,7 +1337,8 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, return ret; down_write(&data->buffer_lock); - ret = msi_wmi_platform_query(data->data, data->method, payload, data->length, data->buffer, + memcpy(data->buffer, payload, data->length); + ret = msi_wmi_platform_query(data->data, data->method, data->buffer, data->length); up_write(&data->buffer_lock); @@ -256,7 +1348,7 @@ static ssize_t msi_wmi_platform_write(struct file *fp, const char __user *input, return length; } -static int msi_wmi_platform_show(struct seq_file *seq, void *p) +static int msi_wmi_platform_debugfs_show(struct seq_file *seq, void *p) { struct msi_wmi_platform_debugfs_data *data = seq->private; int ret; @@ -268,19 +1360,19 @@ static int msi_wmi_platform_show(struct seq_file *seq, void *p) return ret; } -static int msi_wmi_platform_open(struct inode *inode, struct file *fp) +static int msi_wmi_platform_debugfs_open(struct inode *inode, struct file *fp) { struct msi_wmi_platform_debugfs_data *data = inode->i_private; /* The seq_file uses the last byte of the buffer for detecting buffer overflows */ - return single_open_size(fp, msi_wmi_platform_show, data, data->length + 1); + return single_open_size(fp, msi_wmi_platform_debugfs_show, data, data->length + 1); } static const struct file_operations msi_wmi_platform_debugfs_fops = { .owner = THIS_MODULE, - .open = msi_wmi_platform_open, + .open = msi_wmi_platform_debugfs_open, .read = seq_read, - .write = msi_wmi_platform_write, + .write = msi_wmi_platform_debugfs_write, .llseek = seq_lseek, .release = single_release, }; @@ -341,31 +1433,32 @@ static int msi_wmi_platform_hwmon_init(struct msi_wmi_platform_data *data) { struct device *hdev; - hdev = devm_hwmon_device_register_with_info(&data->wdev->dev, "msi_wmi_platform", data, - &msi_wmi_platform_chip_info, NULL); + hdev = devm_hwmon_device_register_with_info( + &data->wdev->dev, "msi_wmi_platform", data, + data->quirks->dual_fans ? &msi_wmi_platform_chip_info_dual : + &msi_wmi_platform_chip_info, + msi_wmi_platform_hwmon_groups); return PTR_ERR_OR_ZERO(hdev); } static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data) { - u8 input[32] = { 0 }; - u8 output[32]; + u8 buffer[32] = { 0 }; u8 flags; int ret; - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, input, sizeof(input), output, - sizeof(output)); + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_EC, buffer, sizeof(buffer)); if (ret < 0) return ret; - flags = output[MSI_PLATFORM_EC_FLAGS_OFFSET]; + flags = buffer[MSI_PLATFORM_EC_FLAGS_OFFSET]; dev_dbg(&data->wdev->dev, "EC RAM version %lu.%lu\n", FIELD_GET(MSI_PLATFORM_EC_MAJOR_MASK, flags), FIELD_GET(MSI_PLATFORM_EC_MINOR_MASK, flags)); dev_dbg(&data->wdev->dev, "EC firmware version %.28s\n", - &output[MSI_PLATFORM_EC_VERSION_OFFSET]); + &buffer[MSI_PLATFORM_EC_VERSION_OFFSET]); if (!(flags & MSI_PLATFORM_EC_IS_TIGERLAKE)) { if (!force) @@ -379,35 +1472,71 @@ static int msi_wmi_platform_ec_init(struct msi_wmi_platform_data *data) static int msi_wmi_platform_init(struct msi_wmi_platform_data *data) { - u8 input[32] = { 0 }; - u8 output[32]; + u8 buffer[32] = { 0 }; int ret; - ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, input, sizeof(input), output, - sizeof(output)); + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_WMI, buffer, sizeof(buffer)); if (ret < 0) return ret; dev_dbg(&data->wdev->dev, "WMI interface version %u.%u\n", - output[MSI_PLATFORM_WMI_MAJOR_OFFSET], - output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET], + buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]); - if (output[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) { + if (buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET] != MSI_WMI_PLATFORM_INTERFACE_VERSION) { if (!force) return -ENODEV; dev_warn(&data->wdev->dev, "Loading despite unsupported WMI interface version (%u.%u)\n", - output[MSI_PLATFORM_WMI_MAJOR_OFFSET], - output[MSI_PLATFORM_WMI_MINOR_OFFSET]); + buffer[MSI_PLATFORM_WMI_MAJOR_OFFSET], + buffer[MSI_PLATFORM_WMI_MINOR_OFFSET]); } return 0; } +static int msi_wmi_platform_disable_stapm(struct msi_wmi_platform_data *data) +{ + u8 buffer[32] = { 0 }; + int ret; + + buffer[0] = MSI_PLATFORM_AMDSTAPM_ADDR; + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_GET_DATA, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + /* Only touch it if firmware self-reports STAPM support */ + if (!(buffer[1] & MSI_PLATFORM_AMDSTAPM_SUPPORTED)) + return 0; + + buffer[0] = MSI_PLATFORM_AMDSTAPM_ADDR; + buffer[1] |= MSI_PLATFORM_AMDSTAPM_DISABLE; + + ret = msi_wmi_platform_query(data, MSI_PLATFORM_SET_DATA, buffer, sizeof(buffer)); + if (ret < 0) + return ret; + + return 0; +} + +static int msi_wmi_platform_profile_setup(struct msi_wmi_platform_data *data) +{ + if (!data->quirks->shift_mode) + return 0; + + data->ppdev = devm_platform_profile_register( + &data->wdev->dev, "msi-wmi-platform", data, + &msi_wmi_platform_profile_ops); + + return PTR_ERR_OR_ZERO(data->ppdev); +} + static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) { struct msi_wmi_platform_data *data; + const struct dmi_system_id *dmi_id; int ret; data = devm_kzalloc(&wdev->dev, sizeof(*data), GFP_KERNEL); @@ -417,6 +1546,12 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) data->wdev = wdev; dev_set_drvdata(&wdev->dev, data); + dmi_id = dmi_first_match(msi_quirks); + if (dmi_id) + data->quirks = dmi_id->driver_data; + else + data->quirks = &quirk_default; + ret = devm_mutex_init(&wdev->dev, &data->wmi_lock); if (ret < 0) return ret; @@ -425,15 +1560,54 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) if (ret < 0) return ret; + if (data->quirks->disable_stapm) { + ret = msi_wmi_platform_disable_stapm(data); + if (ret < 0) + return ret; + } + ret = msi_wmi_platform_ec_init(data); if (ret < 0) return ret; + ret = msi_wmi_fw_attrs_init(data); + if (ret < 0) + return ret; + + if (data->quirks->charge_threshold) { + data->battery_hook.name = "MSI Battery"; + data->battery_hook.add_battery = msi_wmi_platform_battery_add; + data->battery_hook.remove_battery = msi_wmi_platform_battery_remove; + battery_hook_register(&data->battery_hook); + } + msi_wmi_platform_debugfs_init(data); + msi_wmi_platform_profile_setup(data); + + if (data->quirks->restore_curves) { + guard(mutex)(&data->wmi_lock); + ret = msi_wmi_platform_curves_save(data); + if (ret < 0) + return ret; + } + return msi_wmi_platform_hwmon_init(data); } +static void msi_wmi_platform_remove(struct wmi_device *wdev) +{ + struct msi_wmi_platform_data *data = dev_get_drvdata(&wdev->dev); + + if (data->quirks->charge_threshold) + battery_hook_unregister(&data->battery_hook); + + if (data->quirks->restore_curves) { + guard(mutex)(&data->wmi_lock); + msi_wmi_platform_curves_load(data); + } +} + static const struct wmi_device_id msi_wmi_platform_id_table[] = { { MSI_PLATFORM_GUID, NULL }, { } @@ -447,6 +1621,7 @@ static struct wmi_driver msi_wmi_platform_driver = { }, .id_table = msi_wmi_platform_id_table, .probe = msi_wmi_platform_probe, + .remove = msi_wmi_platform_remove, .no_singleton = true, }; diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 4a7ac85c4db4..c9db750fe5ae 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -46,6 +46,12 @@ enum msi_scancodes { WIND_KEY_WLAN = 0x5f, /* Fn+F11 Wi-Fi toggle */ WIND_KEY_TURBO, /* Fn+F10 turbo mode toggle */ WIND_KEY_ECO = 0x69, /* Fn+F10 ECO mode toggle */ + /* MSI Claw keys */ + CLAW_KEY_VOLUMEDOWN = 0x21, + CLAW_KEY_CENTER = 0x29, /* MSI M-Center main menu */ + CLAW_KEY_QUICK_LONG = 0x2a, /* MSI M-Center quick access long hold */ + CLAW_KEY_VOLUMEUP = 0x32, + CLAW_KEY_QUICK_SHORT = 0x58, /* MSI M-Center quick access short press */ }; static struct key_entry msi_wmi_keymap[] = { { KE_KEY, MSI_KEY_BRIGHTNESSUP, {KEY_BRIGHTNESSUP} }, @@ -69,6 +75,15 @@ static struct key_entry msi_wmi_keymap[] = { { KE_KEY, WIND_KEY_TURBO, {KEY_PROG1} }, { KE_KEY, WIND_KEY_ECO, {KEY_PROG2} }, + /* These are MSI Claw keys, used for MSI M-Center in Windows */ + { KE_KEY, CLAW_KEY_CENTER, {KEY_F15} }, + + /* These MSI Claw keys work without WMI. Ignore them to avoid double keycodes */ + { KE_IGNORE, CLAW_KEY_QUICK_SHORT }, + { KE_IGNORE, CLAW_KEY_QUICK_LONG }, + { KE_IGNORE, CLAW_KEY_VOLUMEUP }, + { KE_IGNORE, CLAW_KEY_VOLUMEDOWN }, + { KE_END, 0 } }; @@ -172,44 +187,62 @@ static const struct backlight_ops msi_backlight_ops = { static void msi_wmi_notify(union acpi_object *obj, void *context) { - struct key_entry *key; + struct key_entry *key = NULL; + int eventcode = 0; + + if (!obj) + return; - if (obj && obj->type == ACPI_TYPE_INTEGER) { - int eventcode = obj->integer.value; + switch (obj->type) { + case ACPI_TYPE_INTEGER: + eventcode = obj->integer.value; pr_debug("Eventcode: 0x%x\n", eventcode); - key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, - eventcode); - if (!key) { - pr_info("Unknown key pressed - %x\n", eventcode); + break; + case ACPI_TYPE_BUFFER: + /* Field returns u8[2] here, but is u32 by spec. Allow "oversized" buffers. */ + if (obj->buffer.length < 2) return; - } - if (event_wmi->quirk_last_pressed) { - ktime_t cur = ktime_get_real(); - ktime_t diff = ktime_sub(cur, last_pressed); - /* Ignore event if any event happened in a 50 ms - timeframe -> Key press may result in 10-20 GPEs */ - if (ktime_to_us(diff) < 1000 * 50) { - pr_debug("Suppressed key event 0x%X - " - "Last press was %lld us ago\n", - key->code, ktime_to_us(diff)); - return; - } - last_pressed = cur; - } + /* pointer[0] is key ID, pointer[1] is active state. We don't get release + * events, so ignore the active state and treat as autorelease. + */ + eventcode = obj->buffer.pointer[0]; + pr_debug("Eventcode: 0x%x\n", eventcode); + break; + default: + pr_info("Unknown event received\n"); + return; + } + + key = sparse_keymap_entry_from_scancode(msi_wmi_input_dev, eventcode); - if (key->type == KE_KEY && - /* Brightness is served via acpi video driver */ - (backlight || - (key->code != MSI_KEY_BRIGHTNESSUP && - key->code != MSI_KEY_BRIGHTNESSDOWN))) { - pr_debug("Send key: 0x%X - Input layer keycode: %d\n", - key->code, key->keycode); - sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, - true); + if (!key) { + pr_info("Unknown key pressed - 0x%x\n", eventcode); + return; + } + + if (event_wmi->quirk_last_pressed) { + ktime_t cur = ktime_get_real(); + ktime_t diff = ktime_sub(cur, last_pressed); + /* Ignore event if any event happened in a 50 ms + * timeframe -> Key press may result in 10-20 GPEs + */ + if (ktime_to_us(diff) < 1000 * 50) { + pr_debug("Suppressed key event 0x%X - Last press was %lld us ago\n", + key->code, ktime_to_us(diff)); + return; } - } else - pr_info("Unknown event received\n"); + last_pressed = cur; + } + + /* Brightness is served via acpi video driver */ + if (key->type == KE_KEY && + (backlight || (key->code == MSI_KEY_BRIGHTNESSUP || + key->code == MSI_KEY_BRIGHTNESSDOWN))) + return; + + pr_debug("Send key: 0x%X - Input layer keycode: %d\n", key->code, key->keycode); + sparse_keymap_report_entry(msi_wmi_input_dev, key, 1, true); } static int __init msi_wmi_backlight_setup(void) diff --git a/drivers/platform/x86/zotac-zone-platform.c b/drivers/platform/x86/zotac-zone-platform.c new file mode 100644 index 000000000000..d097f7b9c5a0 --- /dev/null +++ b/drivers/platform/x86/zotac-zone-platform.c @@ -0,0 +1,1122 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Zotac Handheld Platform Driver + * + * Copyright (C) 2025 Luke D. Jones + */ + +#include "linux/mod_devicetable.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "firmware_attributes_class.h" + +#define DRIVER_NAME "zotac_zone_platform" + +#define EC_COMMAND_PORT 0x4E +#define EC_DATA_PORT 0x4F + +#define EC_FAN_CTRL_ADDR 0x44A +#define EC_FAN_DUTY_ADDR 0x44B +#define EC_FAN_SPEED_UPPER_ADDR 0x476 +#define EC_FAN_SPEED_LOWER_ADDR 0x477 +#define EC_CPU_TEMP_ADDR 0x462 + +/* Internal values */ +#define EC_FAN_MODE_AUTO 0 +#define EC_FAN_MODE_MANUAL 1 +/* Follow standard convention for userspace */ +#define PWM_ENABLE_OFF 0 +#define PWM_ENABLE_MANUAL 1 +#define PWM_ENABLE_AUTO 2 /* Automatic control (EC control) */ +#define PWM_ENABLE_CURVE 3 /* Custom curve control */ + +#define PWM_MIN 0 +#define PWM_MAX 255 + +#define FAN_CURVE_POINTS 9 /* 9 points for 10-90°C like in the Zotac C# code */ + +/* AMD APU WMI DPTC constants */ +#define AMD_APU_WMI_METHODS_GUID "1f72b0f1-bfea-4472-9877-6e62937ab616" +#define AMD_APU_WMI_DATA_GUID "05901221-d566-11d1-b2f0-00a0c9062910" + +/* DPTC command IDs */ +#define DPTC_STAPM_TIME_CONSTANT 1 +#define DPTC_SUSTAINED_POWER 5 +#define DPTC_FAST_POWER_1 6 +#define DPTC_FAST_POWER_2 7 +#define DPTC_SLOW_PPT_CONSTANT 8 +#define DPTC_P3T_LIMIT 0x32 + +/* DPTC power limits in milliwatts */ +#define DPTC_MIN_POWER 5000 +#define DPTC_MAX_POWER 28000 + +#define PPT_PL1_SPL_MIN 8 +#define PPT_PL1_SPL_MAX 28 +#define PPT_PL2_SPPT_MIN 8 +#define PPT_PL2_SPPT_MAX 28 +#define PPT_PL3_FPPT_MIN 8 +#define PPT_PL3_FPPT_MAX 28 + +struct power_limits { + u8 ppt_pl1_spl; + u8 ppt_pl2_sppt; + u8 ppt_pl3_fppt; +}; + +static const struct power_limits ppt_quiet_profile = { + .ppt_pl1_spl = 5, + .ppt_pl2_sppt = 10, + .ppt_pl3_fppt = 15, +}; + +static const struct power_limits ppt_balanced_profile = { + .ppt_pl1_spl = 12, + .ppt_pl2_sppt = 19, + .ppt_pl3_fppt = 26, +}; + +static const struct power_limits ppt_performance_profile = { + .ppt_pl1_spl = 28, + .ppt_pl2_sppt = 35, + .ppt_pl3_fppt = 45, +}; + +static struct timer_list fan_curve_timer; + +struct zotac_platform_data { + struct device *hwmon_dev; + struct mutex update_lock; + unsigned int fan_rpm; + unsigned int pwm; + unsigned int pwm_enable; + unsigned int temp; + unsigned long last_updated; + bool valid; + bool curve_enabled; + + /* Fan curve points */ + unsigned int curve_temp[FAN_CURVE_POINTS]; /* Temperature points */ + unsigned int curve_pwm[FAN_CURVE_POINTS]; /* PWM/duty points */ + + /* DPTC values */ + struct device *ppdev; + struct device *fw_attr_dev; + struct kset *fw_attr_kset; + + bool wmi_dptc_supported; + struct power_limits current_power_limits; + enum platform_profile_option current_profile; + /* TODO: hacking - must be removed later */ + unsigned int ppt_pl1_stapm_time_const; + unsigned int ppt_pl2_sppt_time_const; + unsigned int ppt_platform_sppt; +}; + +static struct platform_device *zotac_platform_device; +static DEFINE_MUTEX(ec_mutex); +static struct resource ec_io_ports[] = { + { + .start = EC_COMMAND_PORT, + .end = EC_COMMAND_PORT, + .name = "ec-command", + .flags = IORESOURCE_IO, + }, + { + .start = EC_DATA_PORT, + .end = EC_DATA_PORT, + .name = "ec-data", + .flags = IORESOURCE_IO, + }, +}; + +static u8 ec_read_byte(u16 addr) +{ + u8 addr_upper = (addr >> 8) & 0xFF; + u8 addr_lower = addr & 0xFF; + u8 value; + + mutex_lock(&ec_mutex); + + /* Select upper byte address */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x11, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + outb(addr_upper, EC_DATA_PORT); + + /* Select lower byte address */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x10, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + outb(addr_lower, EC_DATA_PORT); + + /* Read data */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x12, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + value = inb(EC_DATA_PORT); + + mutex_unlock(&ec_mutex); + + return value; +} + +static int ec_write_byte(u16 addr, u8 value) +{ + u8 addr_upper = (addr >> 8) & 0xFF; + u8 addr_lower = addr & 0xFF; + + mutex_lock(&ec_mutex); + + /* Select upper byte address */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x11, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + outb(addr_upper, EC_DATA_PORT); + + /* Select lower byte address */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x10, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + outb(addr_lower, EC_DATA_PORT); + + /* Write data */ + outb(0x2E, EC_COMMAND_PORT); + outb(0x12, EC_DATA_PORT); + outb(0x2F, EC_COMMAND_PORT); + outb(value, EC_DATA_PORT); + + mutex_unlock(&ec_mutex); + + return 0; +} + +static int send_dptc_cmd(u8 cmd_id, u32 value) +{ + struct acpi_buffer input = { 0, NULL }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + u8 *buffer; + acpi_status status; + int ret; + + buffer = kzalloc(8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + buffer[0] = cmd_id; + *(u32 *)(buffer + 4) = value; + + input.length = 8; + input.pointer = buffer; + + status = wmi_evaluate_method(AMD_APU_WMI_METHODS_GUID, 0, 9, + &input, &output); + + ret = ACPI_SUCCESS(status) ? 0 : -EIO; + + kfree(buffer); + if (output.pointer) + kfree(output.pointer); + + return ret; +} + +static struct zotac_platform_data *zotac_platform_update_device(struct device *dev) +{ + struct zotac_platform_data *data = dev_get_drvdata(dev); + unsigned long current_time = jiffies; + + if (time_after(current_time, data->last_updated + HZ) || !data->valid) { + mutex_lock(&data->update_lock); + + data->pwm_enable = ec_read_byte(EC_FAN_CTRL_ADDR); + data->pwm = ec_read_byte(EC_FAN_DUTY_ADDR); + + u32 upper = ec_read_byte(EC_FAN_SPEED_UPPER_ADDR); + u32 lower = ec_read_byte(EC_FAN_SPEED_LOWER_ADDR); + data->fan_rpm = (upper << 8) | lower; + + data->temp = ec_read_byte(EC_CPU_TEMP_ADDR); + + data->last_updated = current_time; + data->valid = true; + + mutex_unlock(&data->update_lock); + } + + return data; +} + +/* Internal version doesn't acquire the lock */ +static int set_fan_duty_internal(unsigned int duty_percent) +{ + u8 duty_val; + + if (duty_percent > 100) + return -EINVAL; + + duty_val = + (duty_percent * (PWM_MAX - PWM_MIN)) / 100 + + PWM_MIN; + return ec_write_byte(EC_FAN_DUTY_ADDR, duty_val); +} + +static void fan_curve_function(struct timer_list *t) +{ + struct zotac_platform_data *data = platform_get_drvdata(zotac_platform_device); + unsigned int current_temp; + unsigned int pwm = 0; + int i; + + if (!data || !data->curve_enabled) { + if (data && data->curve_enabled) + mod_timer(&fan_curve_timer, jiffies + HZ); + return; + } + + mutex_lock(&data->update_lock); + + current_temp = ec_read_byte(EC_CPU_TEMP_ADDR); + data->temp = current_temp; + + pwm = data->curve_pwm[0]; + + if (current_temp >= data->curve_temp[FAN_CURVE_POINTS - 1]) { + /* Above highest temperature point - use maximum PWM */ + pwm = data->curve_pwm[FAN_CURVE_POINTS - 1]; + } else { + /* Find the temperature range and interpolate */ + for (i = 0; i < FAN_CURVE_POINTS - 1; i++) { + if (current_temp >= data->curve_temp[i] && + current_temp < data->curve_temp[i + 1]) { + /* Linear interpolation between points */ + int temp_range = data->curve_temp[i + 1] - + data->curve_temp[i]; + int pwm_range = data->curve_pwm[i + 1] - + data->curve_pwm[i]; + int temp_offset = + current_temp - data->curve_temp[i]; + + if (temp_range > 0) { + pwm = data->curve_pwm[i] + + (pwm_range * temp_offset) / + temp_range; + } else { + pwm = data->curve_pwm[i]; + } + + break; + } + } + } + + set_fan_duty_internal(pwm); + mutex_unlock(&data->update_lock); + + mod_timer(&fan_curve_timer, jiffies + HZ); +} + +/* Fan speed RPM */ +static ssize_t fan1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zotac_platform_data *data = zotac_platform_update_device(dev); + return sprintf(buf, "%u\n", data->fan_rpm); +} +static DEVICE_ATTR_RO(fan1_input); + +/* Fan mode */ + +static int set_pwm_enable(struct device *dev, u8 mode) +{ + struct zotac_platform_data *data = dev_get_drvdata(dev); + int err = 0; + u8 ec_mode; + + /* Convert from standard modes to EC-specific modes */ + switch (mode) { + case PWM_ENABLE_OFF: + /* If supported by EC, turn fan off */ + return -EOPNOTSUPP; /* If EC doesn't support OFF mode */ + case PWM_ENABLE_MANUAL: + ec_mode = + EC_FAN_MODE_MANUAL; /* Assuming this is your actual EC value */ + data->curve_enabled = false; + if (data->curve_enabled) { + data->curve_enabled = false; + timer_delete(&fan_curve_timer); + } + break; + case PWM_ENABLE_AUTO: + ec_mode = + EC_FAN_MODE_AUTO; /* Assuming this is your actual EC value */ + data->curve_enabled = false; + if (data->curve_enabled) { + data->curve_enabled = false; + timer_delete(&fan_curve_timer); + } + break; + case PWM_ENABLE_CURVE: + ec_mode = + EC_FAN_MODE_MANUAL; /* We'll control manually but via the curve */ + data->curve_enabled = true; + break; + default: + return -EINVAL; + } + + /* Set mode to EC if needed */ + if (!data->curve_enabled || mode != PWM_ENABLE_CURVE) { + err = ec_write_byte(EC_FAN_CTRL_ADDR, ec_mode); + } + + if (err == 0) { + data->pwm_enable = mode; + if (mode == PWM_ENABLE_CURVE) + mod_timer(&fan_curve_timer, jiffies + HZ); + } + + return err; +} + +/* Replace fan1_duty with pwm1 and scale to 0-255 */ +static int set_pwm(struct device *dev, u8 pwm_value) +{ + struct zotac_platform_data *data = dev_get_drvdata(dev); + int err; + + if (pwm_value > PWM_MAX) + return -EINVAL; + + mutex_lock(&data->update_lock); + err = ec_write_byte(EC_FAN_DUTY_ADDR, pwm_value); + if (err == 0) + data->pwm = pwm_value; + mutex_unlock(&data->update_lock); + + return err; +} + +static ssize_t pwm1_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zotac_platform_data *data = zotac_platform_update_device(dev); + return sprintf(buf, "%u\n", data->pwm_enable); +} + +static ssize_t pwm1_enable_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + unsigned long mode; + int err; + + err = kstrtoul(buf, 10, &mode); + if (err) + return err; + + if (mode > PWM_ENABLE_CURVE) + return -EINVAL; + + err = set_pwm_enable(dev, mode); + if (err) + return err; + + return count; +} +static DEVICE_ATTR_RW(pwm1_enable); + +/* Fan duty cycle (percent) */ +static ssize_t pwm1_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct zotac_platform_data *data = zotac_platform_update_device(dev); + return sprintf(buf, "%u\n", data->pwm); +} + +static ssize_t pwm1_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long pwm_value; + int err; + + err = kstrtoul(buf, 10, &pwm_value); + if (err) + return err; + + if (pwm_value > PWM_MAX) + return -EINVAL; + + err = set_pwm(dev, pwm_value); + if (err) + return err; + + return count; +} +static DEVICE_ATTR_RW(pwm1); + +/* Macro to generate temperature point attributes */ +#define CURVE_TEMP_ATTR(index) \ + static ssize_t pwm1_auto_point##index##_temp_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", data->curve_temp[index - 1]); \ + } \ + \ + static ssize_t pwm1_auto_point##index##_temp_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + unsigned long temp; \ + int err; \ + \ + err = kstrtoul(buf, 10, &temp); \ + if (err) \ + return err; \ + \ + mutex_lock(&data->update_lock); \ + data->curve_temp[index - 1] = temp; \ + mutex_unlock(&data->update_lock); \ + \ + return count; \ + } \ + static DEVICE_ATTR_RW(pwm1_auto_point##index##_temp) + +/* Macro to generate PWM point attributes */ +#define CURVE_PWM_ATTR(index) \ + static ssize_t pwm1_auto_point##index##_pwm_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", data->curve_pwm[index - 1]); \ + } \ + \ + static ssize_t pwm1_auto_point##index##_pwm_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + unsigned long pwm; \ + int err; \ + \ + err = kstrtoul(buf, 10, &pwm); \ + if (err) \ + return err; \ + \ + if (pwm > 100) \ + return -EINVAL; \ + \ + mutex_lock(&data->update_lock); \ + data->curve_pwm[index - 1] = pwm; \ + mutex_unlock(&data->update_lock); \ + \ + return count; \ + } \ + static DEVICE_ATTR_RW(pwm1_auto_point##index##_pwm) + +/* Generate attributes for each point */ +CURVE_TEMP_ATTR(1); +CURVE_PWM_ATTR(1); +CURVE_TEMP_ATTR(2); +CURVE_PWM_ATTR(2); +CURVE_TEMP_ATTR(3); +CURVE_PWM_ATTR(3); +CURVE_TEMP_ATTR(4); +CURVE_PWM_ATTR(4); +CURVE_TEMP_ATTR(5); +CURVE_PWM_ATTR(5); +CURVE_TEMP_ATTR(6); +CURVE_PWM_ATTR(6); +CURVE_TEMP_ATTR(7); +CURVE_PWM_ATTR(7); +CURVE_TEMP_ATTR(8); +CURVE_PWM_ATTR(8); +CURVE_TEMP_ATTR(9); +CURVE_PWM_ATTR(9); + +/* Temperature reading */ +static ssize_t temp1_input_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct zotac_platform_data *data = zotac_platform_update_device(dev); + return sprintf(buf, "%u\n", + data->temp * 1000); /* Convert to milli-degrees */ +} +static DEVICE_ATTR_RO(temp1_input); + +static struct attribute *zotac_platform_hwmon_attrs[] = { + &dev_attr_fan1_input.attr, + &dev_attr_pwm1_enable.attr, + &dev_attr_pwm1.attr, + &dev_attr_temp1_input.attr, + &dev_attr_pwm1_auto_point1_temp.attr, + &dev_attr_pwm1_auto_point1_pwm.attr, + &dev_attr_pwm1_auto_point2_temp.attr, + &dev_attr_pwm1_auto_point2_pwm.attr, + &dev_attr_pwm1_auto_point3_temp.attr, + &dev_attr_pwm1_auto_point3_pwm.attr, + &dev_attr_pwm1_auto_point4_temp.attr, + &dev_attr_pwm1_auto_point4_pwm.attr, + &dev_attr_pwm1_auto_point5_temp.attr, + &dev_attr_pwm1_auto_point5_pwm.attr, + &dev_attr_pwm1_auto_point6_temp.attr, + &dev_attr_pwm1_auto_point6_pwm.attr, + &dev_attr_pwm1_auto_point7_temp.attr, + &dev_attr_pwm1_auto_point7_pwm.attr, + &dev_attr_pwm1_auto_point8_temp.attr, + &dev_attr_pwm1_auto_point8_pwm.attr, + &dev_attr_pwm1_auto_point9_temp.attr, + &dev_attr_pwm1_auto_point9_pwm.attr, + NULL +}; + +/* DPTC attributes */ +#define DPTC_ATTR(display_name, cmd_id, min_val, max_val) \ + static ssize_t display_name##_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + return sprintf(buf, "%u\n", data->display_name); \ + } \ + \ + static ssize_t display_name##_store(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct zotac_platform_data *data = dev_get_drvdata(dev); \ + unsigned long val; \ + int err; \ + \ + if (!data->wmi_dptc_supported) \ + return -ENODEV; \ + \ + err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + \ + if (val < min_val || val > max_val) \ + return -EINVAL; \ + \ + mutex_lock(&data->update_lock); \ + err = send_dptc_cmd(cmd_id, val); \ + if (err == 0) \ + data->display_name = val; \ + mutex_unlock(&data->update_lock); \ + \ + return err ? err : count; \ + } \ + static DEVICE_ATTR_RW(display_name) + +/* Generate DPTC attributes with AMD-specific naming */ +DPTC_ATTR(ppt_pl1_stapm_time_const, DPTC_STAPM_TIME_CONSTANT, 1, 10000); +DPTC_ATTR(ppt_pl2_sppt_time_const, DPTC_SLOW_PPT_CONSTANT, 1, 0xFF); +DPTC_ATTR(ppt_platform_sppt, DPTC_P3T_LIMIT, DPTC_MIN_POWER, DPTC_MAX_POWER); + +static struct attribute *zotac_platform_dptc_attrs[] = { + &dev_attr_ppt_pl1_stapm_time_const.attr, + &dev_attr_ppt_pl2_sppt_time_const.attr, + &dev_attr_ppt_platform_sppt.attr, + NULL +}; + +static const struct attribute_group zotac_platform_hwmon_group = { + .attrs = zotac_platform_hwmon_attrs, +}; + +static const struct attribute_group zotac_platform_dptc_group = { + .name = "dptc", + .attrs = zotac_platform_dptc_attrs, +}; + +static const struct attribute_group *zotac_platform_hwmon_groups[] = { + &zotac_platform_hwmon_group, + NULL +}; + +/* Helper function to show a simple integer attribute */ +static ssize_t show_int_attr(struct device *dev, + struct device_attribute *attr, + char *buf, int value) +{ + return sprintf(buf, "%d\n", value); +} + +/* Helper function to show a simple string attribute */ +static ssize_t show_string_attr(struct device *dev, + struct device_attribute *attr, + char *buf, const char *value) +{ + return sprintf(buf, "%s\n", value); +} + +#define PPT_ATTR_RO(_name, _attr_name) \ + struct device_attribute dev_attr_##_name = { \ + .attr = { .name = _attr_name, .mode = 0444 }, \ + .show = _name##_show, \ + } + +#define PPT_ATTR_RW(_name, _attr_name) \ + struct device_attribute dev_attr_##_name = { \ + .attr = { .name = _attr_name, .mode = 0644 }, \ + .show = _name##_show, \ + .store = _name##_store, \ + } + +/* Macro that creates a complete set of attributes for a power limit */ +#define DEFINE_POWER_LIMIT_ATTRS(attr_name, cmd_id, min, max, desc) \ + static ssize_t attr_name##_current_value_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct zotac_platform_data *data = \ + platform_get_drvdata(zotac_platform_device); \ + return show_int_attr(dev, attr, buf, \ + data->current_power_limits.attr_name); \ + } \ + \ + static ssize_t attr_name##_current_value_store( \ + struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct zotac_platform_data *data = \ + platform_get_drvdata(zotac_platform_device); \ + unsigned long val; \ + int err; \ + \ + if (!data->wmi_dptc_supported) \ + return -ENODEV; \ + \ + err = kstrtoul(buf, 10, &val); \ + if (err) \ + return err; \ + \ + if (val < min || val > max) \ + return -EINVAL; \ + \ + mutex_lock(&data->update_lock); \ + data->current_power_limits.attr_name = val; \ + data->current_profile = PLATFORM_PROFILE_CUSTOM; \ + \ + err = send_dptc_cmd(cmd_id, val * 1000); \ + mutex_unlock(&data->update_lock); \ + \ + return err ? err : count; \ + } \ + \ + static ssize_t attr_name##_min_value_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return show_int_attr(dev, attr, buf, min); \ + } \ + \ + static ssize_t attr_name##_max_value_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return show_int_attr(dev, attr, buf, max); \ + } \ + \ + static ssize_t attr_name##_default_value_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + struct zotac_platform_data *data = \ + platform_get_drvdata(zotac_platform_device); \ + int default_value; \ + \ + if (data->current_profile == PLATFORM_PROFILE_CUSTOM) { \ + default_value = ppt_balanced_profile.attr_name; \ + } else { \ + const struct power_limits *profile; \ + \ + switch (data->current_profile) { \ + case PLATFORM_PROFILE_LOW_POWER: \ + profile = &ppt_quiet_profile; \ + break; \ + case PLATFORM_PROFILE_BALANCED: \ + profile = &ppt_balanced_profile; \ + break; \ + case PLATFORM_PROFILE_PERFORMANCE: \ + profile = &ppt_performance_profile; \ + break; \ + default: \ + profile = &ppt_balanced_profile; \ + break; \ + } \ + \ + default_value = profile->attr_name; \ + } \ + \ + return show_int_attr(dev, attr, buf, default_value); \ + } \ + \ + static ssize_t attr_name##_scalar_increment_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return show_int_attr(dev, attr, buf, 1); \ + } \ + \ + static ssize_t attr_name##_display_name_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return show_string_attr(dev, attr, buf, desc); \ + } \ + \ + static ssize_t attr_name##_type_show( \ + struct device *dev, struct device_attribute *attr, char *buf) \ + { \ + return show_string_attr(dev, attr, buf, "int"); \ + } \ + \ + static PPT_ATTR_RW(attr_name##_current_value, "current_value"); \ + static PPT_ATTR_RO(attr_name##_min_value, "min_value"); \ + static PPT_ATTR_RO(attr_name##_max_value, "max_value"); \ + static PPT_ATTR_RO(attr_name##_default_value, "default_value"); \ + static PPT_ATTR_RO(attr_name##_scalar_increment, "scalar_increment"); \ + static PPT_ATTR_RO(attr_name##_display_name, "display_name"); \ + static PPT_ATTR_RO(attr_name##_type, "type"); \ + \ + static struct attribute *attr_name##_attrs[] = { \ + &dev_attr_##attr_name##_current_value.attr, \ + &dev_attr_##attr_name##_min_value.attr, \ + &dev_attr_##attr_name##_max_value.attr, \ + &dev_attr_##attr_name##_default_value.attr, \ + &dev_attr_##attr_name##_scalar_increment.attr, \ + &dev_attr_##attr_name##_display_name.attr, \ + &dev_attr_##attr_name##_type.attr, \ + NULL \ + }; \ + \ + static const struct attribute_group attr_name##_attr_group = { \ + .name = #attr_name, .attrs = attr_name##_attrs \ + } + +/* Define the power limit attribute groups */ +DEFINE_POWER_LIMIT_ATTRS(ppt_pl1_spl, DPTC_SUSTAINED_POWER, PPT_PL1_SPL_MIN, + PPT_PL1_SPL_MAX, + "CPU Sustained Power Limit (PL1/SPL)"); + +DEFINE_POWER_LIMIT_ATTRS(ppt_pl2_sppt, DPTC_FAST_POWER_1, PPT_PL2_SPPT_MIN, + PPT_PL2_SPPT_MAX, + "CPU Short Term Power Limit (PL2/SPPT)"); + +DEFINE_POWER_LIMIT_ATTRS(ppt_pl3_fppt, DPTC_FAST_POWER_2, PPT_PL3_FPPT_MIN, + PPT_PL3_FPPT_MAX, "CPU Fast Power Limit (PL3/FPPT)"); + +/* Combine all power attribute groups */ +static const struct attribute_group *zotac_platform_power_groups[] = { + &ppt_pl1_spl_attr_group, &ppt_pl2_sppt_attr_group, + &ppt_pl3_fppt_attr_group, NULL +}; + +/* Helper function to create all power attribute groups */ +static int create_power_attributes(struct platform_device *pdev, struct zotac_platform_data *data) +{ + int i, ret = 0; + + if (!data->wmi_dptc_supported) + return 0; + + data->fw_attr_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + NULL, "%s", DRIVER_NAME); + if (IS_ERR(data->fw_attr_dev)) { + ret = PTR_ERR(data->fw_attr_dev); + goto fail_class_get; + } + + data->fw_attr_kset = kset_create_and_add("attributes", NULL, + &data->fw_attr_dev->kobj); + if (!data->fw_attr_kset) { + ret = -ENOMEM; + goto err_destroy_kset; + } + + for (i = 0; zotac_platform_power_groups[i]; i++) { + ret = sysfs_create_group(&data->fw_attr_kset->kobj, + zotac_platform_power_groups[i]); + if (ret) { + dev_warn(&pdev->dev, + "Failed to create power limit group %d\n", i); + goto error; + } + } + + return 0; + +error: + while (--i >= 0) + sysfs_remove_group(&data->fw_attr_kset->kobj, + zotac_platform_power_groups[i]); +err_destroy_kset: + kset_unregister(data->fw_attr_kset); +fail_class_get: + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + return ret; +} + +/* Helper function to remove all power attribute groups */ +static void remove_power_attributes(struct platform_device *pdev) +{ + struct zotac_platform_data *data = platform_get_drvdata(pdev); + int i; + + for (i = 0; zotac_platform_power_groups[i]; i++) + sysfs_remove_group(&data->fw_attr_kset->kobj, + zotac_platform_power_groups[i]); + + kset_unregister(data->fw_attr_kset); + device_destroy(&firmware_attributes_class, MKDEV(0, 0)); +} + +static int zotac_platform_profile_get(struct device *dev, + enum platform_profile_option *profile) +{ + struct zotac_platform_data *data = dev_get_drvdata(dev); + *profile = data->current_profile; + return 0; +} + +static int zotac_platform_profile_set(struct device *dev, + enum platform_profile_option profile) +{ + struct zotac_platform_data *data = dev_get_drvdata(dev); + const struct power_limits *limits; + int ret = 0; + + if (!data->wmi_dptc_supported) + return -ENODEV; + + switch (profile) { + case PLATFORM_PROFILE_PERFORMANCE: + limits = &ppt_performance_profile; + break; + case PLATFORM_PROFILE_BALANCED: + limits = &ppt_balanced_profile; + break; + case PLATFORM_PROFILE_LOW_POWER: + limits = &ppt_quiet_profile; + break; + case PLATFORM_PROFILE_CUSTOM: + limits = &data->current_power_limits; + break; + default: + return -EOPNOTSUPP; + } + + mutex_lock(&data->update_lock); + ret = send_dptc_cmd(DPTC_SUSTAINED_POWER, limits->ppt_pl1_spl * 1000); + if (ret == 0) + ret = send_dptc_cmd(DPTC_FAST_POWER_1, limits->ppt_pl2_sppt * 1000); + if (ret == 0) + ret = send_dptc_cmd(DPTC_FAST_POWER_2, limits->ppt_pl3_fppt * 1000); + if (ret == 0) { + data->current_profile = profile; + if (profile != PLATFORM_PROFILE_CUSTOM) + memcpy(&data->current_power_limits, limits, sizeof(struct power_limits)); + } + mutex_unlock(&data->update_lock); + + return ret; +} + +static int zotac_platform_profile_probe(void *drvdata, unsigned long *choices) +{ + set_bit(PLATFORM_PROFILE_LOW_POWER, choices); + set_bit(PLATFORM_PROFILE_BALANCED, choices); + set_bit(PLATFORM_PROFILE_PERFORMANCE, choices); + set_bit(PLATFORM_PROFILE_CUSTOM, choices); + return 0; +} + +static const struct platform_profile_ops zotac_platform_profile_ops = { + .probe = zotac_platform_profile_probe, + .profile_get = zotac_platform_profile_get, + .profile_set = zotac_platform_profile_set, +}; + +static int platform_profile_setup(struct zotac_platform_data *data) +{ + struct device *dev = &zotac_platform_device->dev; + + if (!data->wmi_dptc_supported) + return 0; + + data->ppdev = devm_platform_profile_register(dev, DRIVER_NAME, data, + &zotac_platform_profile_ops); + if (IS_ERR(data->ppdev)) { + dev_err(dev, "Failed to register platform_profile device\n"); + return PTR_ERR(data->ppdev); + } + + /* Set default profile */ + zotac_platform_profile_set(dev, PLATFORM_PROFILE_BALANCED); + + return 0; +} + +static int zotac_platform_probe(struct platform_device *pdev) +{ + struct zotac_platform_data *data; + struct device *hwmon_dev; + int i, ret; + + data = devm_kzalloc(&pdev->dev, sizeof(struct zotac_platform_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->wmi_dptc_supported = wmi_has_guid(AMD_APU_WMI_METHODS_GUID); + data->valid = false; + data->curve_enabled = false; + mutex_init(&data->update_lock); + + for (i = 0; i < FAN_CURVE_POINTS; i++) { + /* Set default temperature points from 10°C to 90°C */ + data->curve_temp[i] = 10 + (i * 10); + /* Set default PWM values - simple linear curve from 20% to 100% */ + data->curve_pwm[i] = 20 + (i * 10); + if (data->curve_pwm[i] > 100) + data->curve_pwm[i] = 100; + } + + data->current_profile = PLATFORM_PROFILE_BALANCED; + memcpy(&data->current_power_limits, &ppt_balanced_profile, sizeof(struct power_limits)); + data->ppt_pl1_stapm_time_const = 300; + data->ppt_pl2_sppt_time_const = 0x11; + data->ppt_platform_sppt = 13000; + + hwmon_dev = devm_hwmon_device_register_with_groups( + &pdev->dev, "zotac_platform", data, zotac_platform_hwmon_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + data->hwmon_dev = hwmon_dev; + + if (data->wmi_dptc_supported) { + ret = sysfs_create_group(&pdev->dev.kobj, &zotac_platform_dptc_group); + if (ret) + dev_warn(&pdev->dev, "Failed to create DPTC sysfs group\n"); + } + + platform_set_drvdata(pdev, data); + + timer_setup(&fan_curve_timer, fan_curve_function, 0); + + zotac_platform_update_device(&pdev->dev); + + ret = platform_profile_setup(data); + if (ret) + dev_warn(&pdev->dev, "Failed to setup platform profile\n"); + + ret = create_power_attributes(pdev, data); + if (ret) + dev_warn(&pdev->dev, "Failed to setup firmware attributes\n"); + + return 0; +} + +static void zotac_platform_remove(struct platform_device *pdev) +{ + struct zotac_platform_data *data = platform_get_drvdata(pdev); + + if (data && data->wmi_dptc_supported) { + sysfs_remove_group(&pdev->dev.kobj, &zotac_platform_dptc_group); + remove_power_attributes(pdev); + } +} + +static struct platform_driver zotac_platform_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = zotac_platform_probe, + .remove = zotac_platform_remove, +}; + +static const struct dmi_system_id zotac_platform_dmi_table[] __initconst = { + { + .ident = "Zotac Gaming Handheld", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ZOTAC"), + DMI_MATCH(DMI_BOARD_NAME, "G0A1W"), + }, + }, + { + .ident = "Zotac ZONE", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ZOTAC"), + DMI_MATCH(DMI_PRODUCT_NAME, "ZOTAC GAMING ZONE"), + }, + }, + {} /* Terminate list */ +}; +MODULE_DEVICE_TABLE(dmi, zotac_platform_dmi_table); + +static int __init zotac_platform_init(void) +{ + int err; + + if (!dmi_check_system(zotac_platform_dmi_table)) { + pr_info("No compatible Zotac hardware found\n"); + return -ENODEV; + } + + /* Request I/O regions */ + if (!request_region(EC_COMMAND_PORT, 1, "zotac_platform_ec") || + !request_region(EC_DATA_PORT, 1, "zotac_platform_ec")) { + pr_err("Failed to request EC I/O ports\n"); + err = -EBUSY; + goto err_release; + } + + zotac_platform_device = platform_device_register_simple( + DRIVER_NAME, -1, ec_io_ports, ARRAY_SIZE(ec_io_ports)); + if (IS_ERR(zotac_platform_device)) { + err = PTR_ERR(zotac_platform_device); + goto err_release; + } + + err = platform_driver_register(&zotac_platform_driver); + if (err) + goto err_device_unregister; + + return 0; + +err_device_unregister: + platform_device_unregister(zotac_platform_device); +err_release: + release_region(EC_COMMAND_PORT, 1); + release_region(EC_DATA_PORT, 1); + return err; +} + +static void __exit zotac_platform_exit(void) +{ + timer_delete_sync(&fan_curve_timer); + + platform_driver_unregister(&zotac_platform_driver); + platform_device_unregister(zotac_platform_device); + release_region(EC_COMMAND_PORT, 1); + release_region(EC_DATA_PORT, 1); +} + +module_init(zotac_platform_init); +module_exit(zotac_platform_exit); + +MODULE_AUTHOR("Luke D. Jones"); +MODULE_DESCRIPTION("Zotac Handheld Platform Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index 8e8a5f59b319..a71605398540 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1825,6 +1825,13 @@ void usb_hcd_flush_endpoint(struct usb_device *udev, } } +static void not_enough_bandwidth_notify(struct usb_hcd *hcd) +{ + static char *envp[2] = { "USB_NOT_ENOUGH_BANDWIDTH=1", NULL }; + + kobject_uevent_env(&hcd->self.root_hub->dev.kobj, KOBJ_CHANGE, envp); +} + /** * usb_hcd_alloc_bandwidth - check whether a new bandwidth setting exceeds * the bus bandwidth @@ -1954,6 +1961,8 @@ int usb_hcd_alloc_bandwidth(struct usb_device *udev, } } ret = hcd->driver->check_bandwidth(hcd, udev); + if (ret == -ENOSPC) + not_enough_bandwidth_notify(hcd); reset: if (ret < 0) hcd->driver->reset_bandwidth(hcd, udev); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c index 6c1cbb722ca8..ae66f4d2267b 100644 --- a/drivers/usb/dwc3/dwc3-pci.c +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -193,9 +193,8 @@ static const struct property_entry dwc3_pci_amd_properties[] = { static const struct property_entry dwc3_pci_mr_properties[] = { PROPERTY_ENTRY_STRING("dr_mode", "otg"), - PROPERTY_ENTRY_BOOL("usb-role-switch"), - PROPERTY_ENTRY_STRING("role-switch-default-mode", "host"), PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"), + PROPERTY_ENTRY_STRING("linux,extcon-name", "steamdeck-extcon"), {} }; diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c index 39e8faf4c18c..0308f95da955 100644 --- a/drivers/usb/usbip/vhci_hcd.c +++ b/drivers/usb/usbip/vhci_hcd.c @@ -1443,11 +1443,6 @@ static void vhci_hcd_remove(struct platform_device *pdev) static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) { struct usb_hcd *hcd; - struct vhci *vhci; - int rhport; - int connected = 0; - int ret = 0; - unsigned long flags; dev_dbg(&pdev->dev, "%s\n", __func__); @@ -1455,33 +1450,9 @@ static int vhci_hcd_suspend(struct platform_device *pdev, pm_message_t state) if (!hcd) return 0; - vhci = *((void **)dev_get_platdata(hcd->self.controller)); - - spin_lock_irqsave(&vhci->lock, flags); - - for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) { - if (vhci->vhci_hcd_hs->port_status[rhport] & - USB_PORT_STAT_CONNECTION) - connected += 1; - - if (vhci->vhci_hcd_ss->port_status[rhport] & - USB_PORT_STAT_CONNECTION) - connected += 1; - } + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - spin_unlock_irqrestore(&vhci->lock, flags); - - if (connected > 0) { - dev_info(&pdev->dev, - "We have %d active connection%s. Do not suspend.\n", - connected, str_plural(connected)); - ret = -EBUSY; - } else { - dev_info(&pdev->dev, "suspend vhci_hcd"); - clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - } - - return ret; + return 0; } static int vhci_hcd_resume(struct platform_device *pdev) diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c index a22d0bbb6e63..6cc186a80c4e 100644 --- a/drivers/video/backlight/backlight.c +++ b/drivers/video/backlight/backlight.c @@ -116,6 +116,7 @@ EXPORT_SYMBOL(backlight_notify_blank_all); static void backlight_generate_event(struct backlight_device *bd, enum backlight_update_reason reason) { +#if 0 // We don't want to generate udev events for brightness changes on Steam Deck, as some games like Celeste will re-enumerate controller devices in response to this event. char *envp[2]; switch (reason) { @@ -131,6 +132,7 @@ static void backlight_generate_event(struct backlight_device *bd, } envp[1] = NULL; kobject_uevent_env(&bd->dev.kobj, KOBJ_CHANGE, envp); +#endif // 0 sysfs_notify(&bd->dev.kobj, NULL, "actual_brightness"); } diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 714d111d8053..9e5b2e64b58d 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -806,6 +806,7 @@ static inline bool acpi_quirk_skip_gpio_event_handlers(void) #ifdef CONFIG_PM void acpi_pm_wakeup_event(struct device *dev); +void acpi_pm_wakeup_event_hard(struct device *dev, bool hard); acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, void (*func)(struct acpi_device_wakeup_context *context)); acpi_status acpi_remove_pm_notifier(struct acpi_device *adev); @@ -816,6 +817,9 @@ int acpi_pm_set_device_wakeup(struct device *dev, bool enable); static inline void acpi_pm_wakeup_event(struct device *dev) { } +static inline void acpi_pm_wakeup_event_hard(struct device *dev, bool hard) +{ +} static inline acpi_status acpi_add_pm_notifier(struct acpi_device *adev, struct device *dev, void (*func)(struct acpi_device_wakeup_context *context)) diff --git a/include/drm/drm_sysfs.h b/include/drm/drm_sysfs.h index 96a5d858404b..725bea791151 100644 --- a/include/drm/drm_sysfs.h +++ b/include/drm/drm_sysfs.h @@ -1,17 +1,27 @@ /* SPDX-License-Identifier: GPL-2.0 */ #ifndef _DRM_SYSFS_H_ #define _DRM_SYSFS_H_ +#include + +#define DRM_GPU_RESET_FLAG_VRAM_INVALID (1 << 0) struct drm_device; struct device; struct drm_connector; struct drm_property; +struct drm_reset_event { + uint32_t pid; + uint32_t flags; + char pname[TASK_COMM_LEN]; +}; + int drm_class_device_register(struct device *dev); void drm_class_device_unregister(struct device *dev); void drm_sysfs_hotplug_event(struct drm_device *dev); void drm_sysfs_connector_hotplug_event(struct drm_connector *connector); +void drm_sysfs_reset_event(struct drm_device *dev, struct drm_reset_event *reset_info); void drm_sysfs_connector_property_event(struct drm_connector *connector, struct drm_property *property); #endif diff --git a/include/linux/mhi.h b/include/linux/mhi.h index 4b86ae6f6a82..db28bd827a69 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -321,6 +321,7 @@ struct mhi_controller_config { * if fw_image is NULL and fbc_download is true (optional) * @fw_sz: Firmware image data size for normal booting, used only if fw_image * is NULL and fbc_download is true (optional) + * @prev_fw_sz: Previous firmware image data size, when fbc_download is true * @edl_image: Firmware image name for emergency download mode (optional) * @rddm_size: RAM dump size that host should allocate for debugging purpose * @sbl_size: SBL image size downloaded through BHIe (optional) @@ -397,6 +398,7 @@ struct mhi_controller { const char *fw_image; const u8 *fw_data; size_t fw_sz; + size_t prev_fw_sz; const char *edl_image; size_t rddm_size; size_t sbl_size; diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index 9dc4750296af..b4b6df2def9d 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -308,6 +308,7 @@ struct mmc_card { unsigned int state; /* (our) card state */ unsigned int quirks; /* card quirks */ unsigned int quirk_max_rate; /* max rate set by quirks */ + unsigned int safe_trim_quirk_version; /* advertise if we have protection for cards which misbehave with trim */ #define MMC_QUIRK_LENIENT_FN0 (1<<0) /* allow SDIO FN0 writes outside of the VS CCCR range */ #define MMC_QUIRK_BLKSZ_FOR_BYTE_MODE (1<<1) /* use func->cur_blksize */ /* for byte mode */ diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index ba84f02c2a10..a2403062d770 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -588,6 +588,7 @@ struct mmc_host *devm_mmc_alloc_host(struct device *dev, int extra); int mmc_add_host(struct mmc_host *); void mmc_remove_host(struct mmc_host *); void mmc_free_host(struct mmc_host *); +void mmc_host_set_removed(struct mmc_host *host); void mmc_of_parse_clk_phase(struct device *dev, struct mmc_clk_phase_map *map); int mmc_of_parse(struct mmc_host *host); diff --git a/include/linux/pci.h b/include/linux/pci.h index 64b308b6e61c..095075db0e12 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -415,6 +415,7 @@ struct pci_dev { unsigned int d2_support:1; /* Low power state D2 is supported */ unsigned int no_d1d2:1; /* D1 and D2 are forbidden */ unsigned int no_d3cold:1; /* D3cold is forbidden */ + unsigned int no_runtime_d3:1; /* Bridge must not enter runtime D3 */ unsigned int bridge_d3:1; /* Allow D3 for bridge */ unsigned int d3cold_allowed:1; /* D3cold is allowed by user */ unsigned int mmio_always_on:1; /* Disallow turning off io/mem diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index 749d985e9da2..f7e22e034f10 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -4686,7 +4686,10 @@ struct ec_response_cec_read { * @val: in case cmd is CEC_CMD_ENABLE, this field can be 0 to disable CEC * or 1 to enable CEC functionality, in case cmd is * CEC_CMD_LOGICAL_ADDRESS, this field encodes the requested logical - * address between 0 and 15 or 0xff to unregister + * address between 0 and 15 or 0xff to unregister, in case cmd is + * CEC_CMD_WAKE_ENABLE this field can be 0 to disable all CEC wakeups + * or 1 to enable them while leaving all other CEC functionality + * unchanged. */ struct ec_params_cec_set { uint8_t cmd : 4; /* enum cec_command */ @@ -4729,12 +4732,52 @@ struct ec_response_cec_port_count { uint8_t port_count; } __ec_align1; +/* Set the CEC physical address */ +#define EC_CMD_CEC_SET_PA 0x00C2 + +/** + * struct ec_params_cec_set_pa - CEC parameters to set physical address + * @cmd: parameter type, can be CEC_CMD_PHYSICAL_ADDRESS + * @port: CEC port to set the parameter on + * @val: 2 bytes for physical address, ex. 0x1234 -> 1.2.3.4 + */ +struct ec_params_cec_set_pa { + uint8_t cmd : 4; /* enum cec_command */ + uint8_t port : 4; + uint16_t val; /* physical address, 0x1234 -> 1.2.3.4 */ +} __ec_align1; + +/* Read the CEC physical address */ +#define EC_CMD_CEC_GET_PA 0x00C3 + +/** + * struct ec_params_cec_get - CEC parameters to get physical address + * @cmd: parameter CEC_CMD_PHYSICAL_ADDRESS + * @port: CEC port to get the parameter on + */ +struct ec_params_cec_get_pa { + uint8_t cmd : 4; /* enum cec_command */ + uint8_t port : 4; +} __ec_align1; + +/** + * struct ec_response_cec_get - CEC get physical address response + * @val: 2 byte physical address, ex. 0x1234 -> 1.2.3.4 + */ +struct ec_response_cec_get_pa { + uint16_t val; /* physical address, 0x1234 -> 1.2.3.4 */ +} __ec_align1; + /* CEC parameters command */ enum cec_command { /* CEC reading, writing and events enable */ CEC_CMD_ENABLE, /* CEC logical address */ CEC_CMD_LOGICAL_ADDRESS, + /* CEC physical address */ + CEC_CMD_PHYSICAL_ADDRESS, + /* CEC wake enable */ + CEC_CMD_WAKE_ENABLE, }; /* Events from CEC to AP */ diff --git a/include/linux/pm.h b/include/linux/pm.h index afcaaa37a812..d61a50e95720 100644 --- a/include/linux/pm.h +++ b/include/linux/pm.h @@ -693,6 +693,9 @@ struct dev_pm_info { #else bool should_wakeup:1; #endif +#ifdef CONFIG_HIBERNATE_CALLBACKS + bool is_frozen:1; /* Owned by the driver */ +#endif #ifdef CONFIG_PM struct hrtimer suspend_timer; u64 timer_expires; @@ -819,6 +822,7 @@ extern void dpm_resume_end(pm_message_t state); extern void dpm_resume_noirq(pm_message_t state); extern void dpm_resume_early(pm_message_t state); extern void dpm_resume(pm_message_t state); +extern void dpm_resume_suspended_devices(pm_message_t state); extern void dpm_complete(pm_message_t state); extern void device_pm_unlock(void); diff --git a/include/linux/rtsx_pci.h b/include/linux/rtsx_pci.h index 3c5689356004..94367fd7a32e 100644 --- a/include/linux/rtsx_pci.h +++ b/include/linux/rtsx_pci.h @@ -1271,6 +1271,8 @@ struct rtsx_pcr { u8 ocp_stat2; u8 ovp_stat; u8 rtd3_en; + bool sd_pm_keepalive; + bool sd_express; }; #define PID_524A 0x524A @@ -1305,6 +1307,8 @@ struct rtsx_pcr { (((ddr50) << 16) | ((sdr50) << 8) | (sdr104)) void rtsx_pci_start_run(struct rtsx_pcr *pcr); +void rtsx_pci_sd_express_handoff(struct rtsx_pcr *pcr); +void rtsx_pci_set_sd_pm_keepalive(struct rtsx_pcr *pcr, bool keepalive); int rtsx_pci_write_register(struct rtsx_pcr *pcr, u16 addr, u8 mask, u8 data); int rtsx_pci_read_register(struct rtsx_pcr *pcr, u16 addr, u8 *data); int rtsx_pci_write_phy_register(struct rtsx_pcr *pcr, u8 addr, u16 val); diff --git a/include/linux/suspend.h b/include/linux/suspend.h index b02876f1ae38..7859a5078728 100644 --- a/include/linux/suspend.h +++ b/include/linux/suspend.h @@ -432,6 +432,8 @@ int is_hibernate_resume_dev(dev_t dev); static inline int is_hibernate_resume_dev(dev_t dev) { return 0; } #endif +bool pm_hibernate_is_recovering(void); + /* Hibernation and suspend events */ #define PM_HIBERNATION_PREPARE 0x0001 /* Going to hibernate */ #define PM_POST_HIBERNATION 0x0002 /* Hibernation finished */ diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 50f0eef71fb1..cf6f2dc1ffb0 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -483,6 +483,7 @@ enum { #define HCI_CMD_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ #define HCI_NCMD_TIMEOUT msecs_to_jiffies(4000) /* 4 seconds */ #define HCI_ACL_TX_TIMEOUT msecs_to_jiffies(45000) /* 45 seconds */ +#define HCI_AUTO_ON_TIMEOUT msecs_to_jiffies(200) /* 0.2 seconds */ #define HCI_AUTO_OFF_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ #define HCI_ACL_CONN_TIMEOUT msecs_to_jiffies(20000) /* 20 seconds */ #define HCI_LE_CONN_TIMEOUT msecs_to_jiffies(20000) /* 20 seconds */ diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 3df59849dcbe..61716ea8062b 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -494,7 +494,7 @@ struct hci_dev { struct workqueue_struct *workqueue; struct workqueue_struct *req_workqueue; - struct work_struct power_on; + struct delayed_work power_on; struct delayed_work power_off; struct work_struct error_reset; struct work_struct cmd_sync_work; diff --git a/include/sound/hda_codec.h b/include/sound/hda_codec.h index b4e327877739..1d05f991f2ce 100644 --- a/include/sound/hda_codec.h +++ b/include/sound/hda_codec.h @@ -256,6 +256,7 @@ struct hda_codec { unsigned int link_down_at_suspend:1; /* link down at runtime suspend */ unsigned int relaxed_resume:1; /* don't resume forcibly for jack */ unsigned int forced_resume:1; /* forced resume for jack */ + unsigned int acomp_requested_resume:1; /* resume requested by acomp */ unsigned int no_stream_clean_at_suspend:1; /* do not clean streams at suspend */ unsigned int ctl_dev_id:1; /* old control element id build behaviour */ unsigned int eld_jack_detect:1; /* Machine jack-detection by ELD */ diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c index d2479c69d71a..3911d76a40d9 100644 --- a/kernel/power/hibernate.c +++ b/kernel/power/hibernate.c @@ -672,9 +672,16 @@ int hibernation_platform_enter(void) * state corresponding to hibernation, or try to power it off or reboot, * depending on the value of hibernation_mode. */ -static void power_down(void) +static int power_down(void) { - int error; + int error = 0; + + if (pm_wakeup_pending()) { + pm_wakeup_clear(0); + pr_info("Wakeup event detected after image write, aborting power down.\n"); + error = -EAGAIN; + goto exit; + } #ifdef CONFIG_SUSPEND if (hibernation_mode == HIBERNATION_SUSPEND) { @@ -718,9 +725,9 @@ static void power_down(void) exit: /* Restore swap signature. */ - error = swsusp_unmark(); - if (error) + if (swsusp_unmark()) pr_err("Swap will be unusable! Try swapon -a.\n"); + return error; } static int load_image_and_restore(void) @@ -842,7 +849,11 @@ int hibernate(void) if (hibernation_mode == HIBERNATION_TEST_RESUME) snapshot_test = true; else - power_down(); + error = power_down(); + } + if (error) { + /* recover any devices that refused to thaw */ + dpm_resume_suspended_devices(PMSG_RECOVER); } in_suspend = 0; pm_restore_gfp_mask(); diff --git a/kernel/power/main.c b/kernel/power/main.c index 5429e9f19b65..7f8a44470a5e 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -639,6 +639,7 @@ bool pm_sleep_transition_in_progress(void) { return pm_suspend_in_progress() || hibernation_in_progress(); } +EXPORT_SYMBOL_GPL(pm_sleep_transition_in_progress); #endif /* CONFIG_PM_SLEEP */ #ifdef CONFIG_PM_SLEEP_DEBUG diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c index 57c44268698f..7ccec8425b75 100644 --- a/kernel/power/suspend.c +++ b/kernel/power/suspend.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "power.h" @@ -61,6 +62,7 @@ static DECLARE_SWAIT_QUEUE_HEAD(s2idle_wait_head); enum s2idle_states __read_mostly s2idle_state; static DEFINE_RAW_SPINLOCK(s2idle_lock); +static bool s2idle_unsupported; /** * pm_suspend_default_s2idle - Check if suspend-to-idle is the default suspend. @@ -187,6 +189,8 @@ static bool valid_state(suspend_state_t state) void __init pm_states_init(void) { + const char *sys_vendor, *product_name; + /* "mem" and "freeze" are always present in /sys/power/state. */ pm_states[PM_SUSPEND_MEM] = pm_labels[PM_SUSPEND_MEM]; pm_states[PM_SUSPEND_TO_IDLE] = pm_labels[PM_SUSPEND_TO_IDLE]; @@ -195,6 +199,21 @@ void __init pm_states_init(void) * initialize mem_sleep_states[] accordingly here. */ mem_sleep_states[PM_SUSPEND_TO_IDLE] = mem_sleep_labels[PM_SUSPEND_TO_IDLE]; + + /* + * Identify here if we're running on Steam Deck - if so, we shouldn't + * support s2idle mem_sleep mode, since this mode doesn't work on Deck. + */ + + sys_vendor = dmi_get_system_info(DMI_SYS_VENDOR); + product_name = dmi_get_system_info(DMI_PRODUCT_NAME); + + if (sys_vendor && (!strncmp("Valve", sys_vendor, 5)) && + ((product_name && (!strncmp("Jupiter", product_name, 7))) || + (product_name && (!strncmp("Galileo", product_name, 7))))) { + s2idle_unsupported = true; + pr_info("Steam Deck quirk - no s2idle allowed!\n"); + } } static int __init mem_sleep_default_setup(char *str) @@ -585,6 +604,10 @@ static int enter_state(suspend_state_t state) return -EAGAIN; } #endif + if (s2idle_unsupported) { + pr_info("s2idle sleep is not supported\n"); + return -EINVAL; + } } else if (!valid_state(state)) { return -EINVAL; } diff --git a/kernel/power/swap.c b/kernel/power/swap.c index c626e9dc3c1c..12cb21ee5cbc 100644 --- a/kernel/power/swap.c +++ b/kernel/power/swap.c @@ -542,9 +542,17 @@ static int save_image(struct swap_map_handle *handle, ret = swap_write_page(handle, data_of(*snapshot), &hb); if (ret) break; - if (!(nr_pages % m)) + if (!(nr_pages % m)) { pr_info("Image saving progress: %3d%%\n", nr_pages / m * 10); + /* Check for wakeup events periodically during image write */ + if (pm_wakeup_pending()) { + pm_wakeup_clear(0); + pr_info("Wakeup pending, aborting image write\n"); + ret = -EAGAIN; + break; + } + } nr_pages++; } err2 = hib_wait_io(&hb); @@ -810,9 +818,17 @@ static int save_compressed_image(struct swap_map_handle *handle, memcpy(data[thr].unc + off, data_of(*snapshot), PAGE_SIZE); - if (!(nr_pages % m)) + if (!(nr_pages % m)) { pr_info("Image saving progress: %3d%%\n", nr_pages / m * 10); + /* Check for wakeup events periodically during image write */ + if (pm_wakeup_pending()) { + pm_wakeup_clear(0); + pr_info("Wakeup pending, aborting compressed image write\n"); + ret = -EAGAIN; + goto out_finish; + } + } nr_pages++; } if (!off) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 5ba9fe8261ec..18752f5d2bb4 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -517,7 +517,7 @@ int hci_dev_close(__u16 dev) goto done; } - cancel_work_sync(&hdev->power_on); + cancel_delayed_work(&hdev->power_on); if (hci_dev_test_and_clear_flag(hdev, HCI_AUTO_OFF)) cancel_delayed_work(&hdev->power_off); @@ -903,7 +903,7 @@ static const struct rfkill_ops hci_rfkill_ops = { static void hci_power_on(struct work_struct *work) { - struct hci_dev *hdev = container_of(work, struct hci_dev, power_on); + struct hci_dev *hdev = container_of(work, struct hci_dev, power_on.work); int err; BT_DBG("%s", hdev->name); @@ -2508,7 +2508,7 @@ struct hci_dev *hci_alloc_dev_priv(int sizeof_priv) INIT_WORK(&hdev->rx_work, hci_rx_work); INIT_WORK(&hdev->cmd_work, hci_cmd_work); INIT_WORK(&hdev->tx_work, hci_tx_work); - INIT_WORK(&hdev->power_on, hci_power_on); + INIT_DELAYED_WORK(&hdev->power_on, hci_power_on); INIT_WORK(&hdev->error_reset, hci_error_reset); hci_cmd_sync_init(hdev); @@ -2629,7 +2629,7 @@ int hci_register_dev(struct hci_dev *hdev) if (error) BT_WARN("register suspend notifier failed error:%d\n", error); - queue_work(hdev->req_workqueue, &hdev->power_on); + queue_delayed_work(hdev->req_workqueue, &hdev->power_on,HCI_AUTO_ON_TIMEOUT); idr_init(&hdev->adv_monitors_idr); msft_register(hdev); @@ -2666,7 +2666,7 @@ void hci_unregister_dev(struct hci_dev *hdev) disable_work_sync(&hdev->rx_work); disable_work_sync(&hdev->cmd_work); disable_work_sync(&hdev->tx_work); - disable_work_sync(&hdev->power_on); + disable_delayed_work(&hdev->power_on); disable_work_sync(&hdev->error_reset); disable_delayed_work_sync(&hdev->cmd_timer); disable_delayed_work_sync(&hdev->ncmd_timer); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 8ccc3b22ef5b..1a46479dee83 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -2843,6 +2843,7 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) struct bdaddr_list *b, *t; u8 num_entries = 0; bool pend_conn, pend_report; + struct hci_conn_params *conn_params; u8 filter_policy; size_t i, n; int err; @@ -2931,6 +2932,15 @@ static u8 hci_update_accept_list_sync(struct hci_dev *hdev) continue; } + /* During suspend, only wakeable devices can be in acceptlist */ + conn_params = hci_conn_params_lookup(hdev,&b->bdaddr,b->bdaddr_type); + if (conn_params && hdev->suspended && + !(conn_params->flags & HCI_CONN_FLAG_REMOTE_WAKEUP)) { + hci_le_del_accept_list_sync(hdev, &b->bdaddr, + b->bdaddr_type); + continue; + } + num_entries++; } @@ -6437,7 +6447,7 @@ int hci_suspend_sync(struct hci_dev *hdev) if (hci_conn_count(hdev)) { /* Soft disconnect everything (power off) */ - err = hci_disconnect_all_sync(hdev, HCI_ERROR_REMOTE_POWER_OFF); + err = hci_disconnect_all_sync(hdev, HCI_ERROR_REMOTE_USER_TERM); if (err) { /* Set state to BT_RUNNING so resume doesn't notify */ hdev->suspend_state = BT_RUNNING; diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index c16b0b80c193..fbdcd80148a2 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -8222,7 +8222,7 @@ static int set_external_config(struct sock *sk, struct hci_dev *hdev, hci_dev_set_flag(hdev, HCI_CONFIG); hci_dev_set_flag(hdev, HCI_AUTO_OFF); - queue_work(hdev->req_workqueue, &hdev->power_on); + queue_work(hdev->req_workqueue, &hdev->power_on.work); } else { set_bit(HCI_RAW, &hdev->flags); mgmt_index_added(hdev); @@ -8278,7 +8278,7 @@ static int set_public_address(struct sock *sk, struct hci_dev *hdev, hci_dev_set_flag(hdev, HCI_CONFIG); hci_dev_set_flag(hdev, HCI_AUTO_OFF); - queue_work(hdev->req_workqueue, &hdev->power_on); + queue_work(hdev->req_workqueue, &hdev->power_on.work); } unlock: diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 1e8214d6b6d8..0e13e4511ad1 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -231,7 +231,7 @@ static DECLARE_WORK(reg_work, reg_todo); /* We keep a static world regulatory domain in case of the absence of CRDA */ static const struct ieee80211_regdomain world_regdom = { - .n_reg_rules = 8, + .n_reg_rules = 9, .alpha2 = "00", .reg_rules = { /* IEEE 802.11b/g, channels 1..11 */ @@ -264,6 +264,17 @@ static const struct ieee80211_regdomain world_regdom = { REG_RULE(5745-10, 5825+10, 80, 6, 20, NL80211_RRF_NO_IR), + /* IEEE 802.11ax/WiFi 6E, 6 GHz (UNII-5 through UNII-8, + * channels 1..233). Allow passive scanning so that beacon + * hints can unlock individual channels once an AP is observed + * transmitting there. This mirrors the treatment of 5 GHz + * channels in this world domain and enables connection to + * 6 GHz-only networks without requiring the user to first set + * a country code. + */ + REG_RULE(5955-10, 7125+10, 320, 0, 23, + NL80211_RRF_NO_IR), + /* IEEE 802.11ad (60GHz), channels 1..3 */ REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), } diff --git a/sound/hda/codecs/hdmi/hdmi.c b/sound/hda/codecs/hdmi/hdmi.c index a4a2f2f0540c..053c41d98028 100644 --- a/sound/hda/codecs/hdmi/hdmi.c +++ b/sound/hda/codecs/hdmi/hdmi.c @@ -2236,8 +2236,10 @@ void snd_hda_hdmi_acomp_pin_eld_notify(void *audio_ptr, int port, int dev_id) /* skip notification during system suspend (but not in runtime PM); * the state will be updated at resume */ - if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) + if (codec->core.dev.power.power_state.event == PM_EVENT_SUSPEND) { + codec->acomp_requested_resume = 1; return; + } snd_hda_hdmi_check_presence_and_report(codec, pin_nid, dev_id); } diff --git a/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 2c2e7181d4a9..a7a10d96f2de 100644 --- a/sound/hda/codecs/realtek/alc269.c +++ b/sound/hda/codecs/realtek/alc269.c @@ -2841,6 +2841,20 @@ static void alc294_gx502_toggle_output(struct hda_codec *codec, alc_write_coef_idx(codec, 0x10, 0x0a20); } +static void alc269_fixup_headphone_volume(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + /* Pin 0x21: Some devices share 0x14 for headphones and speakers. + * This will fix ensure these devices have volume controls. */ + if (!is_jack_detectable(codec, 0x21)) + return; + + if (action == HDA_FIXUP_ACT_PRE_PROBE) { + static const hda_nid_t conn1[] = { 0x02 }; + snd_hda_override_conn_list(codec, 0x14, ARRAY_SIZE(conn1), conn1); + } +} + static void alc294_fixup_gx502_hp(struct hda_codec *codec, const struct hda_fixup *fix, int action) { @@ -3957,6 +3971,8 @@ enum { ALC269_FIXUP_DELL4_MIC_NO_PRESENCE, ALC269_FIXUP_DELL4_MIC_NO_PRESENCE_QUIET, ALC269_FIXUP_HEADSET_MODE, + ALC269_FIXUP_DMI_MATCH, + ALC269_FIXUP_AYA_HEADSET_VOLUME, ALC269_FIXUP_HEADSET_MODE_NO_HP_MIC, ALC269_FIXUP_ASPIRE_HEADSET_MIC, ALC269_FIXUP_ASUS_X101_FUNC, @@ -3970,6 +3986,7 @@ enum { ALC269VB_FIXUP_ASUS_ZENBOOK, ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, ALC269VB_FIXUP_ASUS_MIC_NO_PRESENCE, + ALC269VB_FIXUP_AYANEO_SPKR_PIN_FIX, ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED, ALC269VB_FIXUP_ORDISSIMO_EVE2, ALC283_FIXUP_CHROME_BOOK, @@ -4272,6 +4289,30 @@ enum { ALC287_FIXUP_LENOVO_LEGION_AW88399, }; +/* A special fixup for AYN and AYANEO handhelds as both +* have the same PCI SSID as well as the same codec, but +* require different quirks, falling back to DMI matching. +*/ +static void alc269_fixup_match_via_dmi(struct hda_codec *codec, + const struct hda_fixup *fix, int action) +{ + int alc269_fix_id; + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (dmi_name_in_vendors("AYANEO") || dmi_name_in_vendors("AYADEVICE") || dmi_name_in_vendors("AYA DEVICE")) { + if (board_name && (strcmp(board_name, "AYANEO 2") || strcmp(board_name, "AYANEO 2S") || strcmp(board_name, "GEEK") || strcmp(board_name, "GEEK 1S"))) { + alc269_fix_id = ALC269_FIXUP_AYA_HEADSET_VOLUME; + } else { + return; + } + } else if (dmi_name_in_vendors("ayn") && strcmp(board_name, "Loki MiniPro")) { + alc269_fix_id = ALC269VB_FIXUP_AYANEO_SPKR_PIN_FIX; + } else { + return; + } + __snd_hda_apply_fixup(codec, alc269_fix_id, action, 0); +} + /* A special fixup for Lenovo C940 and Yoga Duet 7; * both have the very same PCI SSID, and we need to apply different fixups * depending on the codec ID @@ -4792,6 +4833,13 @@ static const struct hda_fixup alc269_fixups[] = { .chained = true, .chain_id = ALC269_FIXUP_HEADSET_MIC }, + [ALC269VB_FIXUP_AYANEO_SPKR_PIN_FIX] = { + .type = HDA_FIXUP_PINS, + .v.pins = (const struct hda_pintbl[]) { + { 0x1a, 0x90170110 }, + { } + }, + }, [ALC269_FIXUP_LIMIT_INT_MIC_BOOST_MUTE_LED] = { .type = HDA_FIXUP_FUNC, .v.func = alc269_fixup_limit_int_mic_boost, @@ -5619,6 +5667,14 @@ static const struct hda_fixup alc269_fixups[] = { { 0x1b, 0x90170152 } /* use as internal speaker (back) */ } }, + [ALC269_FIXUP_DMI_MATCH] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_match_via_dmi, + }, + [ALC269_FIXUP_AYA_HEADSET_VOLUME] = { + .type = HDA_FIXUP_FUNC, + .v.func = alc269_fixup_headphone_volume, + }, [ALC299_FIXUP_PREDATOR_SPK] = { .type = HDA_FIXUP_PINS, .v.pins = (const struct hda_pintbl[]) { @@ -8170,6 +8226,8 @@ static const struct hda_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1ee7, 0x2078, "HONOR BRB-X M1010", ALC2XX_FIXUP_HEADSET_MIC), SND_PCI_QUIRK(0x1ee7, 0x2081, "HONOR MRB-XXX M1020", ALC256_FIXUP_HONOR_MRB_XXX_M1020_AUDIO), SND_PCI_QUIRK(0x1f4c, 0xe001, "Minisforum V3 (SE)", ALC245_FIXUP_BASS_HP_DAC), + SND_PCI_QUIRK(0x1f66, 0x0101, "AYANEO 2/GEEK/Ayn MiniPro", ALC269_FIXUP_DMI_MATCH), + SND_PCI_QUIRK(0x1f66, 0x0103, "AYANEO AIR 1S", ALC269VB_FIXUP_AYANEO_SPKR_PIN_FIX), SND_PCI_QUIRK(0x1f66, 0x0105, "Ayaneo Portable Game Player", ALC287_FIXUP_CS35L41_I2C_2), SND_PCI_QUIRK(0x2014, 0x800a, "Positivo ARN50", ALC269_FIXUP_LIMIT_INT_MIC_BOOST), SND_PCI_QUIRK(0x2039, 0x0001, "Inspur S14-G1", ALC295_FIXUP_CHROME_BOOK), @@ -8315,6 +8373,7 @@ static const struct hda_model_fixup alc269_fixup_models[] = { {.id = ALC269VB_FIXUP_ASUS_ZENBOOK, .name = "asus-zenbook"}, {.id = ALC269VB_FIXUP_ASUS_ZENBOOK_UX31A, .name = "asus-zenbook-ux31a"}, {.id = ALC269VB_FIXUP_ORDISSIMO_EVE2, .name = "ordissimo"}, + {.id = ALC269VB_FIXUP_AYANEO_SPKR_PIN_FIX, .name = "ayaneo-speaker-pin-fix"}, {.id = ALC282_FIXUP_ASUS_TX300, .name = "asus-tx300"}, {.id = ALC283_FIXUP_INT_MIC, .name = "alc283-int-mic"}, {.id = ALC290_FIXUP_MONO_SPEAKERS_HSJACK, .name = "mono-speakers"}, diff --git a/sound/hda/common/codec.c b/sound/hda/common/codec.c index ef533770179b..b9ded149ea11 100644 --- a/sound/hda/common/codec.c +++ b/sound/hda/common/codec.c @@ -2967,8 +2967,11 @@ static void hda_codec_pm_complete(struct device *dev) dev->power.power_state = PMSG_RESUME; if (pm_runtime_suspended(dev) && (codec->jackpoll_interval || - hda_codec_need_resume(codec) || codec->forced_resume)) + hda_codec_need_resume(codec) || codec->forced_resume || + codec->acomp_requested_resume)) { + codec->acomp_requested_resume = 0; pm_request_resume(dev); + } } static int hda_codec_pm_suspend(struct device *dev) diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index d3298dcbac08..cb77216cd179 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2925,4 +2925,6 @@ config SND_SOC_LPASS_TX_MACRO select SND_SOC_LPASS_MACRO_COMMON tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)" +source "sound/soc/codecs/aw87xxx/Kconfig" + endmenu diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index d2a689006d69..d72afdea8bf4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -881,6 +881,7 @@ obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o # Amp +obj-$(CONFIG_SND_SOC_AW87XXX) += aw87xxx/ obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_SIMPLE_AMPLIFIER) += snd-soc-simple-amplifier.o diff --git a/sound/soc/codecs/aw87xxx/Kconfig b/sound/soc/codecs/aw87xxx/Kconfig new file mode 100644 index 000000000000..bd0f208e2cfe --- /dev/null +++ b/sound/soc/codecs/aw87xxx/Kconfig @@ -0,0 +1,5 @@ +config SND_SOC_AW87XXX + tristate "SoC Audio for awinic AW87XXX Smart K PA" + depends on I2C + help + This option enables support for AW87XXX Smart K PA. diff --git a/sound/soc/codecs/aw87xxx/Makefile b/sound/soc/codecs/aw87xxx/Makefile new file mode 100644 index 000000000000..d32f319a5b01 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/Makefile @@ -0,0 +1,4 @@ +#for AWINIC AW87XXX Smart K PA +snd-soc-aw87xxx-objs := aw87xxx.o aw87xxx_device.o aw87xxx_monitor.o aw87xxx_bin_parse.o aw87xxx_dsp.o aw87xxx_acf_bin.o +obj-$(CONFIG_SND_SOC_AW87XXX) += snd-soc-aw87xxx.o + diff --git a/sound/soc/codecs/aw87xxx/aw87xxx.c b/sound/soc/codecs/aw87xxx/aw87xxx.c new file mode 100644 index 000000000000..18b29f609219 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx.c @@ -0,0 +1,1600 @@ +/* + * aw87xxx.c aw87xxx pa module + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * Author: Barry + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx.h" +#include "aw87xxx_device.h" +#include "aw87xxx_log.h" +#include "aw87xxx_monitor.h" +#include "aw87xxx_acf_bin.h" +#include "aw87xxx_bin_parse.h" +#include "aw87xxx_dsp.h" + +/***************************************************************** +* aw87xxx marco +******************************************************************/ +#define AW87XXX_I2C_NAME "aw87xxx_pa" +#define AW87XXX_DRIVER_VERSION "v2.7.0" +#define AW87XXX_FW_BIN_NAME "aw87xxx_acf.bin" +#define AW87XXX_PROF_MUSIC "Music" +/************************************************************************* + * aw87xxx variable + ************************************************************************/ +static LIST_HEAD(g_aw87xxx_list); +static DEFINE_MUTEX(g_aw87xxx_mutex_lock); +unsigned int g_aw87xxx_dev_cnt = 0; + +static const char *const aw87xxx_monitor_switch[] = {"Disable", "Enable"}; +static const char *const aw87xxx_spin_switch[] = {"spin_0", "spin_90", + "spin_180", "spin_270"}; +#if defined(AW_KERNEL_VER_OVER_6_16_1) +static void new_snd_soc_unregister_component(struct device *dev) +{ + return snd_soc_unregister_component_by_driver(dev, NULL); +} +static struct aw_componet_codec_ops aw_componet_codec_ops = { + .add_codec_controls = snd_soc_add_component_controls, + .unregister_codec = new_snd_soc_unregister_component, +}; +#elif defined(AW_KERNEL_VER_OVER_4_19_1) +static struct aw_componet_codec_ops aw_componet_codec_ops = { + .add_codec_controls = snd_soc_add_component_controls, + .unregister_codec = snd_soc_unregister_component, +}; +#else +static struct aw_componet_codec_ops aw_componet_codec_ops = { + .add_codec_controls = snd_soc_add_codec_controls, + .unregister_codec = snd_soc_unregister_codec, +}; +#endif + +enum smi_bus_type { + SMI_I2C, + SMI_SPI, + SMI_AUTO_DETECT, +}; + +struct smi_instance { + const char *type; + unsigned int flags; + int irq_idx; +}; + +struct smi_node { + enum smi_bus_type bus_type; + struct smi_instance instances[]; +}; + +/************************************************************************ + * + * aw87xxx device update profile + * + ************************************************************************/ +static int aw87xxx_power_down(struct aw87xxx *aw87xxx, char *profile) +{ + int ret = 0; + struct aw_prof_desc *prof_desc = NULL; + struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info; + struct aw_data_container *data_container = NULL; + struct aw_device *aw_dev = &aw87xxx->aw_dev; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + + if (!prof_info->status) { + AW_DEV_LOGE(aw87xxx->dev, "profile_cfg not load"); + return -EINVAL; + } + + prof_desc = aw87xxx_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info, profile); + if (prof_desc == NULL) + goto no_bin_pwr_off; + + if (!prof_desc->prof_st) + goto no_bin_pwr_off; + + + data_container = &prof_desc->data_container; + AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]", + profile, data_container->len); + + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGI(aw87xxx->dev, "profile[%s] has already load ", profile); + } else { + if (aw_dev->ops.pwr_off_func) { + ret = aw_dev->ops.pwr_off_func(aw_dev, data_container); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile); + goto pwr_off_failed; + } + } else { + ret = aw87xxx_dev_default_pwr_off(aw_dev, data_container); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", profile); + goto pwr_off_failed; + } + } + } + + aw87xxx->current_profile = prof_desc->prof_name; + return 0; + +pwr_off_failed: +no_bin_pwr_off: + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false); + aw87xxx->current_profile = aw87xxx->prof_off_name; + return ret; +} + +static int aw87xxx_power_on(struct aw87xxx *aw87xxx, char *profile) +{ + int ret = -EINVAL; + struct aw_prof_desc *prof_desc = NULL; + struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info; + struct aw_data_container *data_container = NULL; + struct aw_device *aw_dev = &aw87xxx->aw_dev; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + + if (!prof_info->status) { + AW_DEV_LOGE(aw87xxx->dev, "profile_cfg not load"); + return -EINVAL; + } + + if (0 == strncmp(profile, aw87xxx->prof_off_name, AW_PROFILE_STR_MAX)) + return aw87xxx_power_down(aw87xxx, profile); + + prof_desc = aw87xxx_acf_get_prof_desc_form_name(aw87xxx->dev, &aw87xxx->acf_info, profile); + if (prof_desc == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "not found [%s] parameter", profile); + return -EINVAL; + } + + if (!prof_desc->prof_st) { + AW_DEV_LOGE(aw87xxx->dev, "not found data container"); + return -EINVAL; + } + + data_container = &prof_desc->data_container; + AW_DEV_LOGD(aw87xxx->dev, "get profile[%s] data len [%d]", + profile, data_container->len); + + if (aw_dev->ops.pwr_on_func) { + ret = aw_dev->ops.pwr_on_func(aw_dev, data_container); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", + profile); + return aw87xxx_power_down(aw87xxx, aw87xxx->prof_off_name); + } + } else { + ret = aw87xxx_dev_default_pwr_on(aw_dev, data_container); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", + profile); + return aw87xxx_power_down(aw87xxx, aw87xxx->prof_off_name); + } + } + + aw87xxx->current_profile = prof_desc->prof_name; + AW_DEV_LOGD(aw87xxx->dev, "load profile[%s] succeed", profile); + + return 0; +} + + + +int aw87xxx_update_profile(struct aw87xxx *aw87xxx, char *profile) +{ + int ret = -1; + + AW_DEV_LOGD(aw87xxx->dev, "load profile[%s] enter", profile); + mutex_lock(&aw87xxx->reg_lock); + aw87xxx_monitor_stop(&aw87xxx->monitor); + if (0 == strncmp(profile, aw87xxx->prof_off_name, AW_PROFILE_STR_MAX)) { + ret = aw87xxx_power_down(aw87xxx, profile); + } else { + ret = aw87xxx_power_on(aw87xxx, profile); + if (!ret) + aw87xxx_monitor_start(&aw87xxx->monitor); + } + mutex_unlock(&aw87xxx->reg_lock); + + return ret; +} + +int aw87xxx_update_profile_esd(struct aw87xxx *aw87xxx, char *profile) +{ + int ret = -1; + + if (0 == strncmp(profile, aw87xxx->prof_off_name, AW_PROFILE_STR_MAX)) + ret = aw87xxx_power_down(aw87xxx, profile); + else + ret = aw87xxx_power_on(aw87xxx, profile); + + return ret; +} + +char *aw87xxx_show_current_profile(int dev_index) +{ + struct list_head *pos = NULL; + struct aw87xxx *aw87xxx = NULL; + + list_for_each(pos, &g_aw87xxx_list) { + aw87xxx = list_entry(pos, struct aw87xxx, list); + if (aw87xxx->dev_index == dev_index) { + AW_DEV_LOGI(aw87xxx->dev, "current profile is [%s]", + aw87xxx->current_profile); + return aw87xxx->current_profile; + } + } + + AW_LOGE("not found struct aw87xxx, dev_index = [%d]", dev_index); + return NULL; +} +EXPORT_SYMBOL(aw87xxx_show_current_profile); + +int aw87xxx_set_profile(int dev_index, char *profile) +{ + struct list_head *pos = NULL; + struct aw87xxx *aw87xxx = NULL; + + list_for_each(pos, &g_aw87xxx_list) { + aw87xxx = list_entry(pos, struct aw87xxx, list); + if (profile && aw87xxx->dev_index == dev_index) { + AW_DEV_LOGD(aw87xxx->dev, "set dev_index = %d, profile = %s", + dev_index, profile); + return aw87xxx_update_profile(aw87xxx, profile); + } + } + + AW_LOGE("not found struct aw87xxx, dev_index = [%d]", dev_index); + return -EINVAL; +} +EXPORT_SYMBOL(aw87xxx_set_profile); + +int aw87xxx_set_profile_by_id(int dev_index, int profile_id) +{ + char *profile = NULL; + + profile = aw87xxx_ctos_get_prof_name(profile_id); + if (profile == NULL) { + AW_LOGE("aw87xxx, dev_index[%d] profile[%d] not support!", + dev_index, profile_id); + return -EINVAL; + } + + AW_LOGI("aw87xxx, dev_index[%d] set profile[%s] by id[%d]", + dev_index, profile, profile_id); + return aw87xxx_set_profile(dev_index, profile); +} +EXPORT_SYMBOL(aw87xxx_set_profile_by_id); + +/**************************************************************************** + * + * aw87xxx Kcontrols + * + ****************************************************************************/ +static int aw87xxx_profile_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int count = 0; + char *name = NULL; + char *profile_name = NULL; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + + if (aw87xxx == NULL) { + AW_LOGE("get struct aw87xxx failed"); + return -EINVAL; + } + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + + /*make sure have prof */ + count = aw87xxx_acf_get_profile_count(aw87xxx->dev, &aw87xxx->acf_info); + if (count <= 0) { + uinfo->value.enumerated.items = 0; + AW_DEV_LOGE(aw87xxx->dev, "get count[%d] failed", count); + return 0; + } + + uinfo->value.enumerated.items = count; + if (uinfo->value.enumerated.item >= count) + uinfo->value.enumerated.item = count - 1; + + name = uinfo->value.enumerated.name; + count = uinfo->value.enumerated.item; + profile_name = aw87xxx_acf_get_prof_name_form_index(aw87xxx->dev, + &aw87xxx->acf_info, count); + if (profile_name == NULL) { + strscpy(uinfo->value.enumerated.name, "NULL", + strlen("NULL") + 1); + return 0; + } + + strscpy(name, profile_name, sizeof(uinfo->value.enumerated.name)); + + return 0; +} + +static int aw87xxx_profile_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = -1; + char *profile_name = NULL; + int index = ucontrol->value.integer.value[0]; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + struct acf_bin_info *acf_info = NULL; + + if (aw87xxx == NULL) { + AW_LOGE("get struct aw87xxx failed"); + return -EINVAL; + } + + acf_info = &aw87xxx->acf_info; + + profile_name = aw87xxx_acf_get_prof_name_form_index(aw87xxx->dev, acf_info, index); + if (!profile_name) { + AW_DEV_LOGE(aw87xxx->dev, "not found profile name,index=[%d]", + index); + return -EINVAL; + } + + AW_DEV_LOGI(aw87xxx->dev, "set profile [%s]", profile_name); + + ret = aw87xxx_update_profile(aw87xxx, profile_name); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "set dev_index[%d] profile failed, profile = %s", + aw87xxx->dev_index, profile_name); + return ret; + } + + return 0; +} + +static int aw87xxx_profile_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int index = 0; + char *profile; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + + if (aw87xxx == NULL) { + AW_LOGE("get struct aw87xxx failed"); + return -EINVAL; + } + + if (!aw87xxx->current_profile) { + AW_DEV_LOGE(aw87xxx->dev, "profile not init"); + return -EINVAL; + } + + profile = aw87xxx->current_profile; + AW_DEV_LOGI(aw87xxx->dev, "current profile:[%s]", + aw87xxx->current_profile); + + + index = aw87xxx_acf_get_prof_index_form_name(aw87xxx->dev, + &aw87xxx->acf_info, aw87xxx->current_profile); + if (index < 0) { + AW_DEV_LOGE(aw87xxx->dev, "get profile index failed"); + return index; + } + + ucontrol->value.integer.value[0] = index; + + return 0; +} + +static int aw87xxx_vmax_get_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = INT_MIN; + uinfo->value.integer.max = AW_VMAX_MAX; + + return 0; +} + +static int aw87xxx_vmax_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int ret = -1; + int vmax_val = 0; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + + if (aw87xxx == NULL) { + AW_LOGE("get struct aw87xxx failed"); + return -EINVAL; + } + + ret = aw87xxx_monitor_no_dsp_get_vmax(&aw87xxx->monitor, &vmax_val); + if (ret < 0) + return ret; + + ucontrol->value.integer.value[0] = vmax_val; + AW_DEV_LOGI(aw87xxx->dev, "get vmax = [0x%x]", vmax_val); + + return 0; +} + +static int aw87xxx_monitor_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int count; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + count = ARRAY_SIZE(aw87xxx_monitor_switch); + + uinfo->value.enumerated.items = count; + + if (uinfo->value.enumerated.item >= count) + uinfo->value.enumerated.item = count - 1; + + strscpy(uinfo->value.enumerated.name, + aw87xxx_monitor_switch[uinfo->value.enumerated.item], + strlen(aw87xxx_monitor_switch[uinfo->value.enumerated.item]) + 1); + + return 0; +} + +static int aw87xxx_monitor_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint32_t ctrl_value = ucontrol->value.integer.value[0]; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + struct aw_monitor *aw_monitor = &aw87xxx->monitor; + int ret = -1; + + ret = aw87xxx_dev_monitor_switch_set(aw_monitor, ctrl_value); + if (ret) + return ret; + + return 0; +} + +static int aw87xxx_monitor_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + struct aw_monitor *aw_monitor = &aw87xxx->monitor; + + ucontrol->value.integer.value[0] = aw_monitor->monitor_hdr.monitor_switch; + + AW_DEV_LOGI(aw87xxx->dev, "monitor switch is %ld", ucontrol->value.integer.value[0]); + return 0; +} + +static int aw87xxx_spin_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int count; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + count = ARRAY_SIZE(aw87xxx_spin_switch); + + uinfo->value.enumerated.items = count; + + if (uinfo->value.enumerated.item >= count) + uinfo->value.enumerated.item = count - 1; + + strscpy(uinfo->value.enumerated.name, + aw87xxx_spin_switch[uinfo->value.enumerated.item], + strlen(aw87xxx_spin_switch[uinfo->value.enumerated.item]) + 1); + + return 0; +} + +static int aw87xxx_spin_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + uint32_t ctrl_value = 0; + int ret = 0; + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + ctrl_value = ucontrol->value.integer.value[0]; + + ret = aw87xxx_dsp_set_spin(ctrl_value); + if (ret) { + AW_DEV_LOGE(aw87xxx->dev, "write spin failed"); + return ret; + } + AW_DEV_LOGD(aw87xxx->dev, "write spin done ctrl_value=%d", ctrl_value); + return 0; +} + +static int aw87xxx_spin_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct aw87xxx *aw87xxx = (struct aw87xxx *)kcontrol->private_value; + + ucontrol->value.integer.value[0] = aw87xxx_dsp_get_spin(); + AW_DEV_LOGD(aw87xxx->dev, "current spin is %ld", ucontrol->value.integer.value[0]); + + return 0; +} + + +static int aw87xxx_kcontrol_dynamic_create(struct aw87xxx *aw87xxx, + void *codec) +{ + struct snd_kcontrol_new *aw87xxx_kcontrol = NULL; + aw_snd_soc_codec_t *soc_codec = (aw_snd_soc_codec_t *)codec; + char *kctl_name[AW87XXX_PRIVATE_KCONTROL_NUM]; + int kcontrol_num = AW87XXX_PRIVATE_KCONTROL_NUM; + int ret = -1; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + aw87xxx->codec = soc_codec; + + aw87xxx_kcontrol = devm_kzalloc(aw87xxx->dev, + sizeof(struct snd_kcontrol_new) * kcontrol_num, + GFP_KERNEL); + if (aw87xxx_kcontrol == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "aw87xxx_kcontrol devm_kzalloc failed"); + return -ENOMEM; + } + + kctl_name[0] = devm_kzalloc(aw87xxx->dev, AW_NAME_BUF_MAX, + GFP_KERNEL); + if (kctl_name[0] == NULL) + return -ENOMEM; + + snprintf(kctl_name[0], AW_NAME_BUF_MAX, "aw87xxx_profile_switch_%d", + aw87xxx->dev_index); + + aw87xxx_kcontrol[0].name = kctl_name[0]; + aw87xxx_kcontrol[0].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + aw87xxx_kcontrol[0].info = aw87xxx_profile_switch_info; + aw87xxx_kcontrol[0].get = aw87xxx_profile_switch_get; + aw87xxx_kcontrol[0].put = aw87xxx_profile_switch_put; + aw87xxx_kcontrol[0].private_value = (unsigned long)aw87xxx; + + kctl_name[1] = devm_kzalloc(aw87xxx->codec->dev, AW_NAME_BUF_MAX, + GFP_KERNEL); + if (kctl_name[1] == NULL) + return -ENOMEM; + + snprintf(kctl_name[1], AW_NAME_BUF_MAX, "aw87xxx_vmax_get_%d", + aw87xxx->dev_index); + + aw87xxx_kcontrol[1].name = kctl_name[1]; + aw87xxx_kcontrol[1].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + aw87xxx_kcontrol[1].access = SNDRV_CTL_ELEM_ACCESS_READ; + aw87xxx_kcontrol[1].info = aw87xxx_vmax_get_info; + aw87xxx_kcontrol[1].get = aw87xxx_vmax_get; + aw87xxx_kcontrol[1].private_value = (unsigned long)aw87xxx; + + kctl_name[2] = devm_kzalloc(aw87xxx->codec->dev, AW_NAME_BUF_MAX, + GFP_KERNEL); + if (kctl_name[2] == NULL) + return -ENOMEM; + + snprintf(kctl_name[2], AW_NAME_BUF_MAX, "aw87xxx_monitor_switch_%d", + aw87xxx->dev_index); + + aw87xxx_kcontrol[2].name = kctl_name[2]; + aw87xxx_kcontrol[2].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + aw87xxx_kcontrol[2].info = aw87xxx_monitor_switch_info; + aw87xxx_kcontrol[2].get = aw87xxx_monitor_switch_get; + aw87xxx_kcontrol[2].put = aw87xxx_monitor_switch_put; + aw87xxx_kcontrol[2].private_value = (unsigned long)aw87xxx; + + ret = aw_componet_codec_ops.add_codec_controls(aw87xxx->codec, + aw87xxx_kcontrol, kcontrol_num); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "add codec controls failed, ret = %d", + ret); + return ret; + } + + AW_DEV_LOGI(aw87xxx->dev, "add codec controls[%s,%s,%s]", + aw87xxx_kcontrol[0].name, + aw87xxx_kcontrol[1].name, + aw87xxx_kcontrol[2].name); + + return 0; +} + +static int aw87xxx_public_kcontrol_create(struct aw87xxx *aw87xxx, + void *codec) +{ + struct snd_kcontrol_new *aw87xxx_kcontrol = NULL; + aw_snd_soc_codec_t *soc_codec = (aw_snd_soc_codec_t *)codec; + char *kctl_name[AW87XXX_PUBLIC_KCONTROL_NUM]; + int kcontrol_num = AW87XXX_PUBLIC_KCONTROL_NUM; + int ret = -1; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + aw87xxx->codec = soc_codec; + + aw87xxx_kcontrol = devm_kzalloc(aw87xxx->dev, + sizeof(struct snd_kcontrol_new) * kcontrol_num, + GFP_KERNEL); + if (aw87xxx_kcontrol == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "aw87xxx_kcontrol devm_kzalloc failed"); + return -ENOMEM; + } + + kctl_name[0] = devm_kzalloc(aw87xxx->dev, AW_NAME_BUF_MAX, + GFP_KERNEL); + if (kctl_name[0] == NULL) + return -ENOMEM; + + snprintf(kctl_name[0], AW_NAME_BUF_MAX, "aw87xxx_spin_switch"); + + aw87xxx_kcontrol[0].name = kctl_name[0]; + aw87xxx_kcontrol[0].iface = SNDRV_CTL_ELEM_IFACE_MIXER; + aw87xxx_kcontrol[0].info = aw87xxx_spin_switch_info; + aw87xxx_kcontrol[0].get = aw87xxx_spin_switch_get; + aw87xxx_kcontrol[0].put = aw87xxx_spin_switch_put; + aw87xxx_kcontrol[0].private_value = (unsigned long)aw87xxx; + + ret = aw_componet_codec_ops.add_codec_controls(aw87xxx->codec, + aw87xxx_kcontrol, kcontrol_num); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "add codec controls failed, ret = %d", + ret); + return ret; + } + + AW_DEV_LOGI(aw87xxx->dev, "add public codec controls[%s]", + aw87xxx_kcontrol[0].name); + + return 0; +} + +/**************************************************************************** + * + *aw87xxx kcontrol create + * + ****************************************************************************/ +int aw87xxx_add_codec_controls(void *codec) +{ + struct list_head *pos = NULL; + struct aw87xxx *aw87xxx = NULL; + int ret = -1; + + list_for_each(pos, &g_aw87xxx_list) { + aw87xxx = list_entry(pos, struct aw87xxx, list); + ret = aw87xxx_kcontrol_dynamic_create(aw87xxx, codec); + if (ret < 0) + return ret; + + if (aw87xxx->dev_index == 0) { + ret = aw87xxx_public_kcontrol_create(aw87xxx, codec); + if (ret < 0) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL(aw87xxx_add_codec_controls); + + +/**************************************************************************** + * + * aw87xxx firmware cfg load + * + ***************************************************************************/ +static void aw87xxx_fw_cfg_free(struct aw87xxx *aw87xxx) +{ + AW_DEV_LOGD(aw87xxx->dev, "enter"); + aw87xxx_acf_profile_free(aw87xxx->dev, &aw87xxx->acf_info); + aw87xxx_monitor_cfg_free(&aw87xxx->monitor); +} + +static int aw87xxx_init_default_prof(struct aw87xxx *aw87xxx) +{ + char *profile = NULL; + + profile = aw87xxx_acf_get_prof_off_name(aw87xxx->dev, &aw87xxx->acf_info); + if (profile == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "get profile off name failed"); + return -EINVAL; + } + + snprintf(aw87xxx->prof_off_name, AW_PROFILE_STR_MAX, "%s", profile); + aw87xxx->current_profile = profile; + AW_DEV_LOGI(aw87xxx->dev, "init profile name [%s]", + aw87xxx->current_profile); + + return 0; +} + +static void aw87xxx_fw_load_retry(struct aw87xxx *aw87xxx) +{ + struct acf_bin_info *acf_info = &aw87xxx->acf_info; + int ram_timer_val = 2000; + + AW_DEV_LOGD(aw87xxx->dev, "failed to read [%s]", + aw87xxx->fw_name); + + if (acf_info->load_count < AW_LOAD_FW_RETRIES) { + AW_DEV_LOGD(aw87xxx->dev, + "restart hrtimer to load firmware"); + schedule_delayed_work(&aw87xxx->fw_load_work, + msecs_to_jiffies(ram_timer_val)); + } else { + acf_info->load_count = 0; + AW_DEV_LOGE(aw87xxx->dev, + "can not load firmware,please check name or file exists"); + return; + } + acf_info->load_count++; +} + +static void aw87xxx_fw_load(const struct firmware *fw, void *context) +{ + int ret = -1; + struct aw87xxx *aw87xxx = context; + struct acf_bin_info *acf_info = &aw87xxx->acf_info; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + + if (!fw) { + aw87xxx_fw_load_retry(aw87xxx); + return; + } + + AW_DEV_LOGD(aw87xxx->dev, "loaded %s - size: %ld", + aw87xxx->fw_name, (u_long)(fw ? fw->size : 0)); + + mutex_lock(&aw87xxx->reg_lock); + acf_info->fw_data = vmalloc(fw->size); + if (!acf_info->fw_data) { + AW_DEV_LOGE(aw87xxx->dev, "fw_data kzalloc memory failed"); + goto exit_vmalloc_failed; + } + memset(acf_info->fw_data, 0, fw->size); + memcpy(acf_info->fw_data, fw->data, fw->size); + acf_info->fw_size = fw->size; + + ret = aw87xxx_acf_parse(aw87xxx->dev, &aw87xxx->acf_info); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "fw_data parse failed"); + goto exit_acf_parse_failed; + } + + ret = aw87xxx_init_default_prof(aw87xxx); + if (ret < 0) { + aw87xxx_fw_cfg_free(aw87xxx); + goto exit_acf_parse_failed; + } + + AW_DEV_LOGI(aw87xxx->dev, "acf parse succeed"); + mutex_unlock(&aw87xxx->reg_lock); + release_firmware(fw); + // Updating profile to "Music" because the firmware is set to "off" during init + aw87xxx_update_profile(aw87xxx, AW87XXX_PROF_MUSIC); + + return; + +exit_acf_parse_failed: +exit_vmalloc_failed: + release_firmware(fw); + mutex_unlock(&aw87xxx->reg_lock); +} + +static void aw87xxx_fw_load_work_routine(struct work_struct *work) +{ + struct aw87xxx *aw87xxx = container_of(work, + struct aw87xxx, fw_load_work.work); + struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + + if (prof_info->status == AW_ACF_WAIT) { + request_firmware_nowait(THIS_MODULE, +// FW_ACTION_HOTPLUG, + FW_ACTION_UEVENT, + aw87xxx->fw_name, + aw87xxx->dev, + GFP_KERNEL, aw87xxx, + aw87xxx_fw_load); + } +} + +static const struct dmi_system_id firmware_names[] = { + { + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AYANEO"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "KUN"), + }, + .driver_data = (void *)"aw87xxx_acf_kun.bin", + }, + { + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "Loki MiniPro"), + }, + .driver_data = (void *)"aw87xxx_acf_minipro.bin", + }, + { + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "AIR 1S"), + }, + .driver_data = (void *)"aw87xxx_acf_air1s.bin", + }, + { + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "AIR Plus"), + }, + .driver_data = (void *)"aw87xxx_acf_airplus.bin", + }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "AYANEO"), + DMI_MATCH(DMI_PRODUCT_NAME, "FLIP"), + }, + .driver_data = (void *)"aw87xxx_acf_flip.bin", + }, + { + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "NEO-01"), + }, + .driver_data = (void *)"aw87xxx_acf_orangepi.bin", + }, + {} +}; + +static void aw87xxx_fw_load_init(struct aw87xxx *aw87xxx) +{ +#ifdef AW_CFG_UPDATE_DELAY + int cfg_timer_val = AW_CFG_UPDATE_DELAY_TIMER; +#else + int cfg_timer_val = 0; +#endif + const struct dmi_system_id *fwname_sysid = dmi_first_match(firmware_names); + const char *fwname = fwname_sysid ? + fwname_sysid->driver_data : AW87XXX_FW_BIN_NAME; + + AW_DEV_LOGI(aw87xxx->dev, "loading firmware name: [%s]", fwname); + snprintf(aw87xxx->fw_name, AW87XXX_FW_NAME_MAX, "%s", fwname); + aw87xxx_acf_init(&aw87xxx->aw_dev, &aw87xxx->acf_info, aw87xxx->dev_index); + + INIT_DELAYED_WORK(&aw87xxx->fw_load_work, aw87xxx_fw_load_work_routine); + schedule_delayed_work(&aw87xxx->fw_load_work, + msecs_to_jiffies(cfg_timer_val)); +} + +/**************************************************************************** + * + *aw87xxx attribute node + * + ****************************************************************************/ +static ssize_t aw87xxx_attr_get_reg(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int ret = 0; + unsigned int i = 0; + unsigned char reg_val = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_device *aw_dev = &aw87xxx->aw_dev; + + mutex_lock(&aw87xxx->reg_lock); + for (i = 0; i < aw_dev->reg_max_addr; i++) { + if (!(aw_dev->reg_access[i] & AW_DEV_REG_RD_ACCESS)) + continue; + ret = aw87xxx_dev_i2c_read_byte(&aw87xxx->aw_dev, i, ®_val); + if (ret < 0) { + len += snprintf(buf + len, PAGE_SIZE - len, + "read reg [0x%x] failed\n", i); + AW_DEV_LOGE(aw87xxx->dev, "read reg [0x%x] failed", i); + } else { + len += snprintf(buf + len, PAGE_SIZE - len, + "reg:0x%02X=0x%02X\n", i, reg_val); + AW_DEV_LOGD(aw87xxx->dev, "reg:0x%02X=0x%02X", + i, reg_val); + } + } + mutex_unlock(&aw87xxx->reg_lock); + + return len; +} + +static ssize_t aw87xxx_attr_set_reg(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + unsigned int databuf[2] = { 0 }; + int ret = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + + mutex_lock(&aw87xxx->reg_lock); + if (sscanf(buf, "0x%x 0x%x", &databuf[0], &databuf[1]) == 2) { + if (databuf[0] >= aw87xxx->aw_dev.reg_max_addr) { + AW_DEV_LOGE(aw87xxx->dev, "set reg[0x%x] error,is out of reg_addr_max[0x%x]", + databuf[0], aw87xxx->aw_dev.reg_max_addr); + mutex_unlock(&aw87xxx->reg_lock); + return -EINVAL; + } + + ret = aw87xxx_dev_i2c_write_byte(&aw87xxx->aw_dev, + databuf[0], databuf[1]); + if (ret < 0) + AW_DEV_LOGE(aw87xxx->dev, "set [0x%x]=0x%x failed", + databuf[0], databuf[1]); + else + AW_DEV_LOGD(aw87xxx->dev, "set [0x%x]=0x%x succeed", + databuf[0], databuf[1]); + } else { + AW_DEV_LOGE(aw87xxx->dev, "i2c write cmd input error"); + } + mutex_unlock(&aw87xxx->reg_lock); + + return len; +} + +static ssize_t aw87xxx_attr_get_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + unsigned int i = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_prof_info *prof_info = &aw87xxx->acf_info.prof_info; + + if (!prof_info->status) { + len += snprintf(buf + len, PAGE_SIZE - len, + "profile_cfg not load\n"); + return len; + } + + AW_DEV_LOGI(aw87xxx->dev, "current profile:[%s]", aw87xxx->current_profile); + + for (i = 0; i < prof_info->count; i++) { + if (!strncmp(aw87xxx->current_profile, prof_info->prof_name_list[i], + AW_PROFILE_STR_MAX)) + len += snprintf(buf + len, PAGE_SIZE - len, + ">%s\n", prof_info->prof_name_list[i]); + else + len += snprintf(buf + len, PAGE_SIZE - len, + " %s\n", prof_info->prof_name_list[i]); + } + + return len; +} + +static ssize_t aw87xxx_attr_set_profile(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + char profile[AW_PROFILE_STR_MAX] = {0}; + int ret = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + + if (strlen(buf) > AW_PROFILE_STR_MAX) { + AW_DEV_LOGE(aw87xxx->dev, "input profile_str_len is out of max[%d]", + AW_PROFILE_STR_MAX); + return -EINVAL; + } + + if (sscanf(buf, "%s", profile) == 1) { + AW_DEV_LOGD(aw87xxx->dev, "set profile [%s]", profile); + ret = aw87xxx_update_profile(aw87xxx, profile); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "set profile[%s] failed", + profile); + return ret; + } + } + + return len; +} + +static ssize_t aw87xxx_attr_get_hwen(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + int hwen = aw87xxx->aw_dev.hwen_status; + + if (hwen >= AW_DEV_HWEN_INVALID) + len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: invalid\n"); + else if (hwen == AW_DEV_HWEN_ON) + len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: on\n"); + else if (hwen == AW_DEV_HWEN_OFF) + len += snprintf(buf + len, PAGE_SIZE - len, "hwen_status: off\n"); + + return len; +} + +static ssize_t aw87xxx_attr_set_hwen(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t len) +{ + int ret = -1; + unsigned int state; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + + ret = kstrtouint(buf, 0, &state); + if (ret) { + AW_DEV_LOGE(aw87xxx->dev, "fail to channelge str to int"); + return ret; + } + + mutex_lock(&aw87xxx->reg_lock); + if (state == AW_DEV_HWEN_OFF) + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false); /*OFF*/ + else if (state == AW_DEV_HWEN_ON) + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true); /*ON*/ + else + AW_DEV_LOGE(aw87xxx->dev, "input [%d] error, hwen_on=[%d],hwen_off=[%d]", + state, AW_DEV_HWEN_ON, AW_DEV_HWEN_OFF); + mutex_unlock(&aw87xxx->reg_lock); + return len; +} + +int aw87xxx_awrw_write(struct aw87xxx *aw87xxx, + const char *buf, size_t count) +{ + int i = 0, ret = -1; + char *data_buf = NULL; + int buf_len = 0; + int temp_data = 0; + int data_str_size = 0; + char *reg_data; + struct aw_i2c_packet *packet = &aw87xxx->i2c_packet; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + /* one addr or one data string Composition of Contains two bytes of symbol(0X)*/ + /* and two byte of hexadecimal data*/ + data_str_size = 2 + 2 * AWRW_DATA_BYTES; + + /* The buf includes the first address of the register to be written and all data */ + buf_len = AWRW_ADDR_BYTES + packet->reg_num * AWRW_DATA_BYTES; + AW_DEV_LOGI(aw87xxx->dev, "buf_len = %d,reg_num = %d", buf_len, packet->reg_num); + data_buf = vmalloc(buf_len); + if (data_buf == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "alloc memory failed"); + return -ENOMEM; + } + memset(data_buf, 0, buf_len); + + data_buf[0] = packet->reg_addr; + reg_data = data_buf + 1; + + AW_DEV_LOGD(aw87xxx->dev, "reg_addr: 0x%02x", data_buf[0]); + + /*ag:0x00 0x01 0x01 0x01 0x01 0x00\x0a*/ + for (i = 0; i < packet->reg_num; i++) { + ret = sscanf(buf + AWRW_HDR_LEN + 1 + i * (data_str_size + 1), + "0x%x", &temp_data); + if (ret != 1) { + AW_DEV_LOGE(aw87xxx->dev, "sscanf failed,ret=%d", ret); + vfree(data_buf); + data_buf = NULL; + return ret; + } + reg_data[i] = temp_data; + AW_DEV_LOGD(aw87xxx->dev, "[%d] : 0x%02x", i, reg_data[i]); + } + + mutex_lock(&aw87xxx->reg_lock); + ret = i2c_master_send(aw87xxx->aw_dev.i2c, data_buf, buf_len); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "write failed"); + vfree(data_buf); + data_buf = NULL; + return -EFAULT; + } + mutex_unlock(&aw87xxx->reg_lock); + + vfree(data_buf); + data_buf = NULL; + + AW_DEV_LOGD(aw87xxx->dev, "down"); + return 0; +} + +static int aw87xxx_awrw_data_check(struct aw87xxx *aw87xxx, + int *data, size_t count) +{ + struct aw_i2c_packet *packet = &aw87xxx->i2c_packet; + int req_data_len = 0; + int act_data_len = 0; + int data_str_size = 0; + + if ((data[AWRW_HDR_ADDR_BYTES] != AWRW_ADDR_BYTES) || + (data[AWRW_HDR_DATA_BYTES] != AWRW_DATA_BYTES)) { + AW_DEV_LOGE(aw87xxx->dev, "addr_bytes [%d] or data_bytes [%d] unsupport", + data[AWRW_HDR_ADDR_BYTES], data[AWRW_HDR_DATA_BYTES]); + return -EINVAL; + } + + /* one data string Composition of Contains two bytes of symbol(0x)*/ + /* and two byte of hexadecimal data*/ + data_str_size = 2 + 2 * AWRW_DATA_BYTES; + act_data_len = count - AWRW_HDR_LEN - 1; + + /* There is a comma(,) or space between each piece of data */ + if (data[AWRW_HDR_WR_FLAG] == AWRW_FLAG_WRITE) { + /*ag:0x00 0x01 0x01 0x01 0x01 0x00\x0a*/ + req_data_len = (data_str_size + 1) * packet->reg_num; + if (req_data_len > act_data_len) { + AW_DEV_LOGE(aw87xxx->dev, "data_len checkfailed,requeset data_len [%d],actaul data_len [%d]", + req_data_len, act_data_len); + return -EINVAL; + } + } + + return 0; +} + +/* flag addr_bytes data_bytes reg_num reg_addr*/ +static int aw87xxx_awrw_parse_buf(struct aw87xxx *aw87xxx, + const char *buf, size_t count, int *wr_status) +{ + int data[AWRW_HDR_MAX] = {0}; + struct aw_i2c_packet *packet = &aw87xxx->i2c_packet; + int ret = -1; + + if (sscanf(buf, "0x%02x 0x%02x 0x%02x 0x%02x 0x%02x", + &data[AWRW_HDR_WR_FLAG], &data[AWRW_HDR_ADDR_BYTES], + &data[AWRW_HDR_DATA_BYTES], &data[AWRW_HDR_REG_NUM], + &data[AWRW_HDR_REG_ADDR]) == 5) { + + packet->reg_addr = data[AWRW_HDR_REG_ADDR]; + packet->reg_num = data[AWRW_HDR_REG_NUM]; + *wr_status = data[AWRW_HDR_WR_FLAG]; + ret = aw87xxx_awrw_data_check(aw87xxx, data, count); + if (ret < 0) + return ret; + + return 0; + } + + return -EINVAL; +} + +static ssize_t aw87xxx_attr_awrw_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_i2c_packet *packet = &aw87xxx->i2c_packet; + int wr_status = 0; + int ret = -1; + + if (count < AWRW_HDR_LEN) { + AW_DEV_LOGE(aw87xxx->dev, "data count too smaller, please check write format"); + AW_DEV_LOGE(aw87xxx->dev, "string %s,count=%ld", + buf, (u_long)count); + return -EINVAL; + } + + AW_DEV_LOGI(aw87xxx->dev, "string:[%s],count=%ld", buf, (u_long)count); + ret = aw87xxx_awrw_parse_buf(aw87xxx, buf, count, &wr_status); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "can not parse string"); + return ret; + } + + if (wr_status == AWRW_FLAG_WRITE) { + ret = aw87xxx_awrw_write(aw87xxx, buf, count); + if (ret < 0) + return ret; + } else if (wr_status == AWRW_FLAG_READ) { + packet->status = AWRW_I2C_ST_READ; + AW_DEV_LOGI(aw87xxx->dev, "read_cmd:reg_addr[0x%02x], reg_num[%d]", + packet->reg_addr, packet->reg_num); + } else { + AW_DEV_LOGE(aw87xxx->dev, "please check str format, unsupport read_write_status: %d", + wr_status); + return -EINVAL; + } + + return count; +} + +static ssize_t aw87xxx_attr_awrw_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_i2c_packet *packet = &aw87xxx->i2c_packet; + int data_len = 0; + size_t len = 0; + int ret = -1, i = 0; + char *reg_data = NULL; + + if (packet->status != AWRW_I2C_ST_READ) { + AW_DEV_LOGE(aw87xxx->dev, "please write read cmd first"); + return -EINVAL; + } + + data_len = AWRW_DATA_BYTES * packet->reg_num; + reg_data = (char *)vmalloc(data_len); + if (reg_data == NULL) { + AW_DEV_LOGE(aw87xxx->dev, "memory alloc failed"); + ret = -EINVAL; + goto exit; + } + + mutex_lock(&aw87xxx->reg_lock); + ret = aw87xxx_dev_i2c_read_msg(&aw87xxx->aw_dev, packet->reg_addr, + (char *)reg_data, data_len); + if (ret < 0) { + ret = -EFAULT; + mutex_unlock(&aw87xxx->reg_lock); + goto exit; + } + mutex_unlock(&aw87xxx->reg_lock); + + AW_DEV_LOGI(aw87xxx->dev, "reg_addr 0x%02x, reg_num %d", + packet->reg_addr, packet->reg_num); + + for (i = 0; i < data_len; i++) { + len += snprintf(buf + len, PAGE_SIZE - len, + "0x%02x,", reg_data[i]); + AW_DEV_LOGI(aw87xxx->dev, "0x%02x", reg_data[i]); + } + + ret = len; + +exit: + if (reg_data) { + vfree(reg_data); + reg_data = NULL; + } + packet->status = AWRW_I2C_ST_NONE; + return ret; +} + +static ssize_t aw87xxx_drv_ver_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, + "driver_ver: %s \n", AW87XXX_DRIVER_VERSION); + + return len; +} + +static DEVICE_ATTR(reg, S_IWUSR | S_IRUGO, + aw87xxx_attr_get_reg, aw87xxx_attr_set_reg); +static DEVICE_ATTR(profile, S_IWUSR | S_IRUGO, + aw87xxx_attr_get_profile, aw87xxx_attr_set_profile); +static DEVICE_ATTR(hwen, S_IWUSR | S_IRUGO, + aw87xxx_attr_get_hwen, aw87xxx_attr_set_hwen); +static DEVICE_ATTR(awrw, S_IWUSR | S_IRUGO, + aw87xxx_attr_awrw_show, aw87xxx_attr_awrw_store); +static DEVICE_ATTR(drv_ver, S_IRUGO, aw87xxx_drv_ver_show, NULL); + +static struct attribute *aw87xxx_attributes[] = { + &dev_attr_reg.attr, + &dev_attr_profile.attr, + &dev_attr_hwen.attr, + &dev_attr_awrw.attr, + &dev_attr_drv_ver.attr, + NULL +}; + +static struct attribute_group aw87xxx_attribute_group = { + .attrs = aw87xxx_attributes +}; + +/**************************************************************************** + * + *aw87xxx device probe + * + ****************************************************************************/ +static const struct acpi_gpio_params reset_gpio = { 0, 0, false }; +static const struct acpi_gpio_mapping reset_acpi_gpios[] = { + { "reset-gpios", &reset_gpio, 1 }, + { } +}; + +static struct aw87xxx *aw87xxx_malloc_init(struct i2c_client *client) +{ + struct aw87xxx *aw87xxx = NULL; + + aw87xxx = devm_kzalloc(&client->dev, sizeof(struct aw87xxx), + GFP_KERNEL); + if (aw87xxx == NULL) { + AW_DEV_LOGE(&client->dev, "failed to devm_kzalloc aw87xxx"); + return NULL; + } + memset(aw87xxx, 0, sizeof(struct aw87xxx)); + + aw87xxx->dev = &client->dev; + aw87xxx->aw_dev.dev = &client->dev; + aw87xxx->aw_dev.i2c_bus = client->adapter->nr; + aw87xxx->aw_dev.i2c_addr = client->addr; + aw87xxx->aw_dev.i2c = client; + aw87xxx->aw_dev.hwen_status = false; + aw87xxx->aw_dev.reg_access = NULL; + aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_INVALID; + aw87xxx->off_bin_status = AW87XXX_NO_OFF_BIN; + aw87xxx->codec = NULL; + aw87xxx->current_profile = aw87xxx->prof_off_name; + + mutex_init(&aw87xxx->reg_lock); + + AW_DEV_LOGI(&client->dev, "Driver struct alloc and mutex init done, devinfo: i2c_bus=%u, i2c_addr=%x", client->adapter->nr, client->addr); + return aw87xxx; +} + +static int aw87xxx_i2c_probe(struct i2c_client *client) +{ + struct device_node *dev_node = client->dev.of_node; + const struct smi_node *node; + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct aw87xxx *aw87xxx = NULL; + struct gpio_desc *gpiod = NULL; + struct i2c_board_info board_info = {}; + char i2c_name[32]; + int ret = -1; + int acpi_dev_count = 0; + + /* aw87xxx Get APCI I2C device count */ + if(g_aw87xxx_dev_cnt == 0){ + acpi_dev_count = i2c_acpi_client_count(adev); + AW_DEV_LOGI(&client->dev, "I2C_ACPI_CLIENT_COUNT returned [%d]", acpi_dev_count); + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + AW_DEV_LOGE(&client->dev, "check_functionality failed"); + ret = -ENODEV; + goto exit_check_functionality_failed; + } + + /* aw87xxx i2c_dev struct init */ + aw87xxx = aw87xxx_malloc_init(client); + if (aw87xxx == NULL) + goto exit_malloc_init_failed; + + i2c_set_clientdata(client, aw87xxx); + + aw87xxx_device_parse_port_id_dt(&aw87xxx->aw_dev); + aw87xxx_device_parse_topo_id_dt(&aw87xxx->aw_dev); + + /* aw87xxx Get ACPI GPIO */ + + if (g_aw87xxx_dev_cnt == 0){ + ret = devm_acpi_dev_add_driver_gpios(aw87xxx->dev, reset_acpi_gpios); + if(ret){ + AW_DEV_LOGE(aw87xxx->dev, "Unable to add GPIO mapping table"); + goto exit_device_init_failed; + } + + gpiod = devm_gpiod_get(aw87xxx->dev, "reset", GPIOD_OUT_LOW); + if (gpiod == NULL){ + AW_DEV_LOGE(aw87xxx->dev, "Gpiod returned NULL failing gracefully."); + goto exit_device_init_failed; + } + + if (IS_ERR(gpiod)){ + AW_DEV_LOGE(aw87xxx->dev, "Get gpiod failed."); + goto exit_device_init_failed; + } + + aw87xxx->aw_dev.rst_gpio = desc_to_gpio(gpiod); + aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_OFF; + AW_DEV_LOGI(aw87xxx->dev, "reset gpio[%x] parse succeed", aw87xxx->aw_dev.rst_gpio); + + if (gpio_is_valid(aw87xxx->aw_dev.rst_gpio)) { + ret = devm_gpio_request_one(aw87xxx->dev, aw87xxx->aw_dev.rst_gpio, GPIOF_OUT_INIT_LOW, "aw87xxx_reset"); + if ((ret < 0) && (ret != -EBUSY)) { + AW_DEV_LOGE(aw87xxx->dev, "reset request failed, returned [%d]", ret); + goto exit_device_init_failed; + } + }else{ + /*Disabling RESET GPIO*/ + AW_DEV_LOGI(aw87xxx->dev, "no reset gpio provided, hardware reset unavailable"); + aw87xxx->aw_dev.rst_gpio = AW_NO_RESET_GPIO; + aw87xxx->aw_dev.hwen_status = AW_DEV_HWEN_INVALID; + } + + } + + /*hw power on PA*/ + if(g_aw87xxx_dev_cnt == 0) { + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true); + } + + /* aw87xxx devices private attributes init */ + ret = aw87xxx_dev_init(&aw87xxx->aw_dev); + if (ret < 0) + goto exit_device_init_failed; + + /*product register reset */ + aw87xxx_dev_soft_reset(&aw87xxx->aw_dev); + + /*hw power off */ + if(g_aw87xxx_dev_cnt == 0) { + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, false); + } + + /* create debug attrbute nodes */ + ret = sysfs_create_group(&aw87xxx->dev->kobj, &aw87xxx_attribute_group); + if (ret < 0) + AW_DEV_LOGE(aw87xxx->dev, "failed to create sysfs nodes, will not allowed to use"); + + /* cfg_load init */ + aw87xxx_fw_load_init(aw87xxx); + + /*monitor init*/ + aw87xxx_monitor_init(aw87xxx->dev, &aw87xxx->monitor, dev_node); + + /*add device to total list */ + mutex_lock(&g_aw87xxx_mutex_lock); + g_aw87xxx_dev_cnt++; + list_add(&aw87xxx->list, &g_aw87xxx_list); + aw87xxx->dev_index = g_aw87xxx_dev_cnt; + + mutex_unlock(&g_aw87xxx_mutex_lock); + AW_DEV_LOGI(aw87xxx->dev, "succeed, dev_index=[%d], g_aw87xxx_dev_cnt= [%d]", + aw87xxx->dev_index, g_aw87xxx_dev_cnt); + + AW_DEV_LOGI(aw87xxx->dev, "acpi_c=[%d] dev_c=[%d]", acpi_dev_count, g_aw87xxx_dev_cnt); + + /* Attempt to add other I2C AMPs */ + if ((acpi_dev_count > 1) && (g_aw87xxx_dev_cnt == 1)){ + /* power on the chip */ + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true); + + node = device_get_match_data(aw87xxx->dev); + memset(&board_info, 0, sizeof(board_info)); + strscpy(board_info.type, client->name, I2C_NAME_SIZE); + snprintf(i2c_name, sizeof(i2c_name), "%s.%d", client->name, 1); + board_info.dev_name = i2c_name; + + aw87xxx_i2c_probe(i2c_acpi_new_device_by_fwnode(acpi_fwnode_handle(adev), 1, &board_info)); + } + + return 0; + +exit_device_init_failed: + AW_DEV_LOGE(aw87xxx->dev, "pa init failed"); + + devm_kfree(&client->dev, aw87xxx); + aw87xxx = NULL; +exit_malloc_init_failed: +exit_check_functionality_failed: + return ret; +} + +static void aw87xxx_i2c_remove(struct i2c_client *client) +{ + struct aw87xxx *aw87xxx = i2c_get_clientdata(client); + + aw87xxx_monitor_exit(&aw87xxx->monitor); + + /*rm attr node*/ + sysfs_remove_group(&aw87xxx->dev->kobj, &aw87xxx_attribute_group); + + aw87xxx_fw_cfg_free(aw87xxx); + + mutex_lock(&g_aw87xxx_mutex_lock); + g_aw87xxx_dev_cnt--; + list_del(&aw87xxx->list); + mutex_unlock(&g_aw87xxx_mutex_lock); + + devm_kfree(&client->dev, aw87xxx); + aw87xxx = NULL; + +// return 0; +} + +static void aw87xxx_i2c_shutdown(struct i2c_client *client) +{ + struct aw87xxx *aw87xxx = i2c_get_clientdata(client); + + AW_DEV_LOGI(&client->dev, "enter"); + + /*soft and hw power off*/ + aw87xxx_update_profile(aw87xxx, aw87xxx->prof_off_name); +} + +static int aw87xxx_runtime_suspend(struct device *dev) +{ + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + + AW_DEV_LOGI(aw87xxx->dev, "Suspending..."); + + // soft and hw power off + aw87xxx_update_profile(aw87xxx, aw87xxx->prof_off_name); + + return 0; +} + +static int aw87xxx_runtime_resume(struct device *dev) +{ + struct list_head *pos = NULL; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + + // Power on PA + if (aw87xxx->dev_index == 1) + aw87xxx_dev_hw_pwr_ctrl(&aw87xxx->aw_dev, true); + + // Set profile to Music + list_for_each_prev(pos, &g_aw87xxx_list) { + aw87xxx = list_entry(pos, struct aw87xxx, list); + AW_DEV_LOGI(aw87xxx->dev, "Resuming..."); + + mutex_lock(&aw87xxx->reg_lock); + aw87xxx_power_on(aw87xxx, AW87XXX_PROF_MUSIC); + mutex_unlock(&aw87xxx->reg_lock); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(aw87xxx_pm_ops, aw87xxx_runtime_suspend, aw87xxx_runtime_resume); + +static const struct acpi_device_id aw87xxx_acpi_match[] = { + { "AWDZ8830", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, aw87xxx_acpi_match); + +// This is not necessary if the acpi match probes correctly. This is needed for userspace `new_device() functionality +static const struct i2c_device_id aw87xxx_i2c_id[] = { + {AW87XXX_I2C_NAME, 0}, + {}, +}; + +static struct i2c_driver aw87xxx_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = AW87XXX_I2C_NAME, + .acpi_match_table = aw87xxx_acpi_match, + .pm = &aw87xxx_pm_ops, + }, + .probe = aw87xxx_i2c_probe, + .remove = aw87xxx_i2c_remove, + .shutdown = aw87xxx_i2c_shutdown, + .id_table = aw87xxx_i2c_id, +}; + +module_i2c_driver(aw87xxx_i2c_driver) + +MODULE_AUTHOR(""); +MODULE_DESCRIPTION("awinic aw87xxx pa driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/aw87xxx/aw87xxx.h b/sound/soc/codecs/aw87xxx/aw87xxx.h new file mode 100644 index 000000000000..9638819cdc7e --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx.h @@ -0,0 +1,130 @@ +#ifndef __AW87XXX_H__ +#define __AW87XXX_H__ +#include +#include +#include +#include + +#include "aw87xxx_device.h" +#include "aw87xxx_monitor.h" +#include "aw87xxx_acf_bin.h" + +#define AW_CFG_UPDATE_DELAY +#define AW_CFG_UPDATE_DELAY_TIMER (3000) + +#define AW87XXX_NO_OFF_BIN (0) +#define AW87XXX_OFF_BIN_OK (1) + +#define AW87XXX_PRIVATE_KCONTROL_NUM (3) +#define AW87XXX_PUBLIC_KCONTROL_NUM (1) + +#define AW_I2C_RETRIES (5) +#define AW_I2C_RETRY_DELAY (2) +#define AW_I2C_READ_MSG_NUM (2) + +#define AW87XXX_FW_NAME_MAX (64) +#define AW_NAME_BUF_MAX (64) +#define AW_LOAD_FW_RETRIES (3) + +#define AW_DEV_REG_RD_ACCESS (1 << 0) +#define AW_DEV_REG_WR_ACCESS (1 << 1) + +#define AWRW_ADDR_BYTES (1) +#define AWRW_DATA_BYTES (1) +#define AWRW_HDR_LEN (24) + +/*********************************************************** + * + * aw87xxx codec control compatible with kernel 4.19 + * + ***********************************************************/ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 1) +#define AW_KERNEL_VER_OVER_6_16_1 +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 1) +#define AW_KERNEL_VER_OVER_4_19_1 +#endif + +#ifdef AW_KERNEL_VER_OVER_4_19_1 +typedef struct snd_soc_component aw_snd_soc_codec_t; +#else +typedef struct snd_soc_codec aw_snd_soc_codec_t; +#endif + +struct aw_componet_codec_ops { + int (*add_codec_controls)(aw_snd_soc_codec_t *codec, + const struct snd_kcontrol_new *controls, unsigned int num_controls); + void (*unregister_codec)(struct device *dev); +}; + + +/******************************************** + * + * aw87xxx devices attributes + * + *******************************************/ +enum { + AWRW_FLAG_WRITE = 0, + AWRW_FLAG_READ, +}; + +enum { + AWRW_I2C_ST_NONE = 0, + AWRW_I2C_ST_READ, + AWRW_I2C_ST_WRITE, +}; + +enum { + AWRW_HDR_WR_FLAG = 0, + AWRW_HDR_ADDR_BYTES, + AWRW_HDR_DATA_BYTES, + AWRW_HDR_REG_NUM, + AWRW_HDR_REG_ADDR, + AWRW_HDR_MAX, +}; + +struct aw_i2c_packet { + char status; + unsigned int reg_num; + unsigned int reg_addr; + char *reg_data; +}; + + +/******************************************** + * + * aw87xxx device struct + * + *******************************************/ +struct aw87xxx { + char fw_name[AW87XXX_FW_NAME_MAX]; + int32_t dev_index; + char *current_profile; + char prof_off_name[AW_PROFILE_STR_MAX]; + uint32_t off_bin_status; + struct device *dev; + + struct mutex reg_lock; + struct aw_device aw_dev; + struct aw_i2c_packet i2c_packet; + + struct delayed_work fw_load_work; + struct acf_bin_info acf_info; + + aw_snd_soc_codec_t *codec; + + struct list_head list; + + struct aw_monitor monitor; +}; + +int aw87xxx_update_profile(struct aw87xxx *aw87xxx, char *profile); +int aw87xxx_update_profile_esd(struct aw87xxx *aw87xxx, char *profile); + +char *aw87xxx_show_current_profile(int dev_index); +int aw87xxx_set_profile(int dev_index, char *profile); +int aw87xxx_set_profile_by_id(int dev_index, int profile_id); +int aw87xxx_add_codec_controls(void *codec); +int aw87xxx_awrw_write(struct aw87xxx *aw87xxx, const char *buf, size_t count); +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.c b/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.c new file mode 100644 index 000000000000..0de7085cb345 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.c @@ -0,0 +1,1558 @@ +/* + * aw87xxx_acf_bin.c + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * Author: Barry + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx.h" +#include "aw87xxx_acf_bin.h" +#include "aw87xxx_monitor.h" +#include "aw87xxx_log.h" +#include "aw87xxx_bin_parse.h" + +/************************************************************************* + * + *Table corresponding to customized profile ids to profile names + * + *************************************************************************/ +enum aw_customers_profile_id { + AW_CTOS_PROFILE_OFF = 0, + AW_CTOS_PROFILE_MUSIC, + AW_CTOS_PROFILE_VOICE, + AW_CTOS_PROFILE_VOIP, + AW_CTOS_PROFILE_RINGTONE, + AW_CTOS_PROFILE_RINGTONE_HS, + AW_CTOS_PROFILE_LOWPOWER, + AW_CTOS_PROFILE_BYPASS, + AW_CTOS_PROFILE_MMI, + AW_CTOS_PROFILE_FM, + AW_CTOS_PROFILE_NOTIFICATION, + AW_CTOS_PROFILE_RECEIVER, + AW_CTOS_PROFILE_MAX, +}; + +static char *g_ctos_profile_name[AW_PROFILE_MAX] = { + [AW_CTOS_PROFILE_OFF] = "Off", + [AW_CTOS_PROFILE_MUSIC] = "Music", + [AW_CTOS_PROFILE_VOICE] = "Voice", + [AW_CTOS_PROFILE_VOIP] = "Voip", + [AW_CTOS_PROFILE_RINGTONE] = "Ringtone", + [AW_CTOS_PROFILE_RINGTONE_HS] = "Ringtone_hs", + [AW_CTOS_PROFILE_LOWPOWER] = "Lowpower", + [AW_CTOS_PROFILE_BYPASS] = "Bypass", + [AW_CTOS_PROFILE_MMI] = "Mmi", + [AW_CTOS_PROFILE_FM] = "Fm", + [AW_CTOS_PROFILE_NOTIFICATION] = "Notification", + [AW_CTOS_PROFILE_RECEIVER] = "Receiver", +}; + + +char *aw87xxx_ctos_get_prof_name(int profile_id) +{ + if (profile_id < 0 || profile_id >= AW_CTOS_PROFILE_MAX) + return NULL; + else + return g_ctos_profile_name[profile_id]; +} + + +static char *g_profile_name[] = {"Music", "Voice", "Voip", + "Ringtone", "Ringtone_hs", "Lowpower", "Bypass", "Mmi", + "Fm", "Notification", "Receiver", "Off"}; + +static char *g_power_off_name[] = {"Off", "OFF", "off", "oFF", "power_down"}; + +static char *aw_get_prof_name(int profile) +{ + if (profile < 0 || profile >= AW_PROFILE_MAX) + return "NULL"; + else + return g_profile_name[profile]; +} + +/************************************************************************* + * + *acf check + * + *************************************************************************/ +static int aw_crc8_check(const unsigned char *data, unsigned int data_size) + +{ + unsigned char crc_value = 0x00; + unsigned char *pdata; + int i; + unsigned char pdatabuf = 0; + + pdata = (unsigned char *)data; + + while (data_size--) { + pdatabuf = *pdata++; + for (i = 0; i < 8; i++) { + if ((crc_value ^ (pdatabuf)) & 0x01) { + crc_value ^= 0x18; + crc_value >>= 1; + crc_value |= 0x80; + } else { + crc_value >>= 1; + } + pdatabuf >>= 1; + } + } + + return (int)crc_value; +} + +static int aw_check_file_id(struct device *dev, + char *fw_data, int32_t file_id) +{ + int32_t *acf_file_id = NULL; + + acf_file_id = (int32_t *)fw_data; + if (*acf_file_id != file_id) { + AW_DEV_LOGE(dev, "file id [%x] check failed", *acf_file_id); + return -ENFILE; + } + + return 0; +} + +static int aw_check_header_size(struct device *dev, + char *fw_data, size_t fw_size) +{ + if (fw_size < sizeof(struct aw_acf_hdr)) { + AW_DEV_LOGE(dev, "acf size check failed,size less-than aw_acf_hdr"); + return -ENOEXEC; + } + + return 0; +} + +/*************************************************************************** + * V0.0.0.1 version acf check + **************************************************************************/ +static int aw_check_ddt_size_v_0_0_0_1(struct device *dev, char *fw_data) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)fw_data; + struct aw_acf_dde *acf_dde = NULL; + + acf_dde = (struct aw_acf_dde *)(fw_data + acf_hdr->ddt_offset); + + /* check ddt_size in acf_header is aqual to ddt_num multiply by dde_size */ + if (acf_hdr->ddt_size != acf_hdr->dde_num * sizeof(struct aw_acf_dde)) { + AW_DEV_LOGE(dev, "acf ddt size check failed"); + return -EINVAL; + } + + return 0; +} + +static int aw_check_data_size_v_0_0_0_1(struct device *dev, + char *fw_data, size_t fw_size) +{ + int i = 0; + size_t data_size = 0; + struct aw_acf_hdr *acf_hdr = NULL; + struct aw_acf_dde *acf_dde = NULL; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + acf_dde = (struct aw_acf_dde *)(fw_data + acf_hdr->ddt_offset); + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (acf_dde[i].data_size % 2) { + AW_DEV_LOGE(dev, "acf dde[%d].data_size[%d],dev_name[%s],data_type[%d], data_size check failed", + i, acf_dde[i].data_size, acf_dde[i].dev_name, + acf_dde[i].data_type); + return -EINVAL; + } + data_size += acf_dde[i].data_size; + } + + /* Verify that the file size is equal to the header size plus */ + /* the table size and data size */ + if (fw_size != data_size + sizeof(struct aw_acf_hdr) + acf_hdr->ddt_size) { + AW_DEV_LOGE(dev, "acf size check failed"); + AW_DEV_LOGE(dev, "fw_size=%ld,hdr_size and ddt size and data size =%ld", + (u_long)fw_size, (u_long)(data_size + sizeof(struct aw_acf_hdr) + + acf_hdr->ddt_size)); + return -EINVAL; + } + + return 0; +} + +static int aw_check_data_crc_v_0_0_0_1(struct device *dev, char *fw_data) +{ + int i = 0; + size_t crc_val = 0; + char *data = NULL; + struct aw_acf_hdr *acf_hdr = NULL; + struct aw_acf_dde *acf_dde = NULL; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + acf_dde = (struct aw_acf_dde *)(fw_data + acf_hdr->ddt_offset); + + for (i = 0; i < acf_hdr->dde_num; ++i) { + data = fw_data + acf_dde[i].data_offset; + crc_val = aw_crc8_check(data, acf_dde[i].data_size); + if (crc_val != acf_dde[i].data_crc) { + AW_DEV_LOGE(dev, "acf dde_crc check failed"); + return -EINVAL; + } + } + + return 0; +} + +static int aw_check_profile_id_v_0_0_0_1(struct device *dev, char *fw_data) +{ + int i = 0; + struct aw_acf_hdr *acf_hdr = NULL; + struct aw_acf_dde *acf_dde = NULL; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + acf_dde = (struct aw_acf_dde *)(fw_data + acf_hdr->ddt_offset); + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (acf_dde[i].data_type == AW_MONITOR) + continue; + if (acf_dde[i].dev_profile > AW_PROFILE_MAX) { + AW_DEV_LOGE(dev, "parse profile_id[%d] failed", acf_dde[i].dev_profile); + return -EINVAL; + } + } + + return 0; +} +static int aw_check_data_v_0_0_0_1(struct device *dev, + char *fw_data, size_t size) +{ + int ret = -1; + + /* check file type id is awinic acf file */ + ret = aw_check_file_id(dev, fw_data, AW_ACF_FILE_ID); + if (ret < 0) + return ret; + + /* check ddt_size in header is equal to all ddt aize */ + ret = aw_check_ddt_size_v_0_0_0_1(dev, fw_data); + if (ret < 0) + return ret; + + /* Verify that the file size is equal to the header size plus */ + /* the table size and data size */ + ret = aw_check_data_size_v_0_0_0_1(dev, fw_data, size); + if (ret < 0) + return ret; + + /* check crc in is equal to dde data crc */ + ret = aw_check_data_crc_v_0_0_0_1(dev, fw_data); + if (ret < 0) + return ret; + + /* check profile id is in profile_id_max */ + ret = aw_check_profile_id_v_0_0_0_1(dev, fw_data); + if (ret < 0) + return ret; + + AW_DEV_LOGI(dev, "acf fimware check succeed"); + + return 0; +} + +/*************************************************************************** + * V1.0.0.0 version acf chack + **************************************************************************/ +static int aw_check_ddt_size_v_1_0_0_0(struct device *dev, char *fw_data) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)fw_data; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = NULL; + + acf_dde = (struct aw_acf_dde_v_1_0_0_0 *)(fw_data + acf_hdr->ddt_offset); + + /* check ddt_size in acf_header is aqual to ddt_num multiply by dde_size */ + if (acf_hdr->ddt_size != acf_hdr->dde_num * sizeof(struct aw_acf_dde_v_1_0_0_0)) { + AW_DEV_LOGE(dev, "acf ddt size check failed"); + return -EINVAL; + } + + return 0; +} + +static int aw_check_data_size_v_1_0_0_0(struct device *dev, + char *fw_data, size_t fw_size) +{ + int i = 0; + size_t data_size = 0; + struct aw_acf_hdr *acf_hdr = NULL; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = NULL; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + acf_dde = (struct aw_acf_dde_v_1_0_0_0 *)(fw_data + acf_hdr->ddt_offset); + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (acf_dde[i].data_size % 2) { + AW_DEV_LOGE(dev, "acf dde[%d].data_size[%d],dev_name[%s],data_type[%d], data_size check failed", + i, acf_dde[i].data_size, acf_dde[i].dev_name, + acf_dde[i].data_type); + return -EINVAL; + } + data_size += acf_dde[i].data_size; + } + + /* Verify that the file size is equal to the header size plus */ + /* the table size and data size */ + if (fw_size != data_size + sizeof(struct aw_acf_hdr) + acf_hdr->ddt_size) { + AW_DEV_LOGE(dev, "acf size check failed"); + AW_DEV_LOGE(dev, "fw_size=%ld,hdr_size and ddt size and data size =%ld", + (u_long)fw_size, (u_long)(data_size + sizeof(struct aw_acf_hdr) + + acf_hdr->ddt_size)); + return -EINVAL; + } + + return 0; +} + +static int aw_check_data_crc_v_1_0_0_0(struct device *dev, char *fw_data) +{ + int i = 0; + size_t crc_val = 0; + char *data = NULL; + struct aw_acf_hdr *acf_hdr = NULL; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = NULL; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + acf_dde = (struct aw_acf_dde_v_1_0_0_0 *)(fw_data + acf_hdr->ddt_offset); + + for (i = 0; i < acf_hdr->dde_num; ++i) { + data = fw_data + acf_dde[i].data_offset; + crc_val = aw_crc8_check(data, acf_dde[i].data_size); + if (crc_val != acf_dde[i].data_crc) { + AW_DEV_LOGE(dev, "acf dde_crc check failed"); + return -EINVAL; + } + } + + return 0; +} + +static int aw_check_data_v_1_0_0_0(struct device *dev, + char *fw_data, size_t size) +{ + int ret = -1; + + /* check file type id is awinic acf file */ + ret = aw_check_file_id(dev, fw_data, AW_ACF_FILE_ID); + if (ret < 0) + return ret; + + /* check ddt_size in header is equal to all ddt aize */ + ret = aw_check_ddt_size_v_1_0_0_0(dev, fw_data); + if (ret < 0) + return ret; + + /* Verify that the file size is equal to the header size plus */ + /* the table size and data size */ + ret = aw_check_data_size_v_1_0_0_0(dev, fw_data, size); + if (ret < 0) + return ret; + + /* check crc in is equal to dde data crc */ + ret = aw_check_data_crc_v_1_0_0_0(dev, fw_data); + if (ret < 0) + return ret; + + AW_DEV_LOGI(dev, "acf fimware check succeed"); + + return 0; +} + +/*************************************************************************** + * acf chack API + **************************************************************************/ +static int aw_check_acf_firmware(struct device *dev, + char *fw_data, size_t size) +{ + int ret = -1; + struct aw_acf_hdr *acf_hdr = NULL; + + if (fw_data == NULL) { + AW_DEV_LOGE(dev, "fw_data is NULL,fw_data check failed"); + return -ENODATA; + } + + /* check file size is less-than header size */ + ret = aw_check_header_size(dev, fw_data, size); + if (ret < 0) + return ret; + + acf_hdr = (struct aw_acf_hdr *)fw_data; + AW_DEV_LOGI(dev, "project name: [%s]", acf_hdr->project); + AW_DEV_LOGI(dev, "custom name: [%s]", acf_hdr->custom); + AW_DEV_LOGI(dev, "version name: [%s]", acf_hdr->version); + AW_DEV_LOGI(dev, "author_id: [%d]", acf_hdr->author_id); + + switch (acf_hdr->hdr_version) { + case AW_ACF_HDR_VER_0_0_0_1: + return aw_check_data_v_0_0_0_1(dev, fw_data, size); + case AW_ACF_HDR_VER_1_0_0_0: + return aw_check_data_v_1_0_0_0(dev, fw_data, size); + default: + AW_DEV_LOGE(dev, "unsupported hdr_version [0x%x]", + acf_hdr->hdr_version); + return -EINVAL; + } + + return ret; +} + + + +/************************************************************************* + * + *acf parse + * + *************************************************************************/ +static int aw_parse_raw_reg(struct device *dev, uint8_t *data, + uint32_t data_len, struct aw_prof_desc *prof_desc) +{ + AW_DEV_LOGD(dev, "data_size:%d enter", data_len); + + prof_desc->data_container.data = data; + prof_desc->data_container.len = data_len; + + prof_desc->prof_st = AW_PROFILE_OK; + + return 0; +} + +static int aw_parse_reg_with_hdr(struct device *dev, uint8_t *data, + uint32_t data_len, struct aw_prof_desc *prof_desc) +{ + struct aw_bin *aw_bin = NULL; + int ret = -1; + + AW_DEV_LOGD(dev, "data_size:%d enter", data_len); + + aw_bin = kzalloc(data_len + sizeof(struct aw_bin), GFP_KERNEL); + if (aw_bin == NULL) { + AW_DEV_LOGE(dev, "devm_kzalloc aw_bin failed"); + return -ENOMEM; + } + + aw_bin->info.len = data_len; + memcpy(aw_bin->info.data, data, data_len); + + ret = aw87xxx_parsing_bin_file(aw_bin); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse bin failed"); + goto parse_bin_failed; + } + + if ((aw_bin->all_bin_parse_num != 1) || + (aw_bin->header_info[0].bin_data_type != DATA_TYPE_REGISTER)) { + AW_DEV_LOGE(dev, "bin num or type error"); + goto parse_bin_failed; + } + + prof_desc->data_container.data = + data + aw_bin->header_info[0].valid_data_addr; + prof_desc->data_container.len = aw_bin->header_info[0].valid_data_len; + prof_desc->prof_st = AW_PROFILE_OK; + + kfree(aw_bin); + aw_bin = NULL; + + return 0; + +parse_bin_failed: + kfree(aw_bin); + aw_bin = NULL; + return ret; +} + +static int aw_parse_monitor_config(struct device *dev, + char *monitor_data, uint32_t data_len) +{ + int ret = -1; + + if (monitor_data == NULL || data_len == 0) { + AW_DEV_LOGE(dev, "no data to parse"); + return -EBFONT; + } + + ret = aw87xxx_monitor_bin_parse(dev, monitor_data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "monitor_config parse failed"); + return ret; + } + + AW_DEV_LOGI(dev, "monitor_bin parse succeed"); + + return 0; +} + +static int aw_check_prof_str_is_off(char *profile_name) +{ + int i = 0; + + for (i = 0; i < AW_POWER_OFF_NAME_SUPPORT_COUNT; i++) { + if (strnstr(profile_name, g_power_off_name[i], + strlen(profile_name) + 1)) + return 0; + } + + return -EINVAL; +} + +/*************************************************************************** + * V0.0.0.1 version acf paese + **************************************************************************/ +static int aw_check_product_name_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_acf_dde *prof_hdr) +{ + int i = 0; + + for (i = 0; i < acf_info->product_cnt; i++) { + if (0 == strcmp(acf_info->product_tab[i], prof_hdr->dev_name)) { + AW_DEV_LOGD(dev, "bin_dev_name:%s", + prof_hdr->dev_name); + return 0; + } + } + + return -ENXIO; +} + +static int aw_check_data_type_is_monitor_v_0_0_0_1(struct device *dev, + struct aw_acf_dde *prof_hdr) +{ + if (prof_hdr->data_type == AW_MONITOR) { + AW_DEV_LOGD(dev, "bin data is monitor"); + return 0; + } + + return -ENXIO; +} + +static int aw_parse_data_by_sec_type_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_acf_dde *prof_hdr, + struct aw_prof_desc *profile_prof_desc) +{ + int ret = -1; + char *cfg_data = acf_info->fw_data + prof_hdr->data_offset; + + switch (prof_hdr->data_type) { + case AW_BIN_TYPE_REG: + snprintf(profile_prof_desc->dev_name, sizeof(prof_hdr->dev_name), + "%s", prof_hdr->dev_name); + profile_prof_desc->prof_name = aw_get_prof_name(prof_hdr->dev_profile); + AW_DEV_LOGD(dev, "parse reg type data enter,profile=%s", + aw_get_prof_name(prof_hdr->dev_profile)); + ret = aw_parse_raw_reg(dev, cfg_data, prof_hdr->data_size, + profile_prof_desc); + break; + case AW_BIN_TYPE_HDR_REG: + snprintf(profile_prof_desc->dev_name, sizeof(prof_hdr->dev_name), + "%s", prof_hdr->dev_name); + profile_prof_desc->prof_name = aw_get_prof_name(prof_hdr->dev_profile); + AW_DEV_LOGD(dev, "parse hdr_reg type data enter,profile=%s", + aw_get_prof_name(prof_hdr->dev_profile)); + ret = aw_parse_reg_with_hdr(dev, cfg_data, + prof_hdr->data_size, + profile_prof_desc); + break; + } + + return ret; +} + +static int aw_parse_dev_type_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, struct aw_all_prof_info *all_prof_info) +{ + int i = 0; + int ret = -1; + int sec_num = 0; + char *cfg_data = NULL; + struct aw_prof_desc *prof_desc = NULL; + struct aw_acf_dde *acf_dde = + (struct aw_acf_dde *)(acf_info->fw_data + acf_info->acf_hdr.ddt_offset); + + AW_DEV_LOGD(dev, "enter"); + + for (i = 0; i < acf_info->acf_hdr.dde_num; i++) { + if ((acf_info->aw_dev->i2c_bus == acf_dde[i].dev_bus) && + (acf_info->aw_dev->i2c_addr == acf_dde[i].dev_addr) && + (acf_dde[i].type == AW_DDE_DEV_TYPE_ID)) { + + ret = aw_check_product_name_v_0_0_0_1(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + ret = aw_check_data_type_is_monitor_v_0_0_0_1(dev, &acf_dde[i]); + if (ret == 0) { + cfg_data = acf_info->fw_data + acf_dde[i].data_offset; + ret = aw_parse_monitor_config(dev, cfg_data, acf_dde[i].data_size); + if (ret < 0) + return ret; + continue; + } + + prof_desc = &all_prof_info->prof_desc[acf_dde[i].dev_profile]; + ret = aw_parse_data_by_sec_type_v_0_0_0_1(dev, acf_info, &acf_dde[i], + prof_desc); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse dev type data failed"); + return ret; + } + sec_num++; + } + } + + if (sec_num == 0) { + AW_DEV_LOGD(dev, "get dev type num is %d, please use default", + sec_num); + return AW_DEV_TYPE_NONE; + } + + return AW_DEV_TYPE_OK; +} + +static int aw_parse_default_type_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, struct aw_all_prof_info *all_prof_info) +{ + int i = 0; + int ret = -1; + int sec_num = 0; + char *cfg_data = NULL; + struct aw_prof_desc *prof_desc = NULL; + struct aw_acf_dde *acf_dde = + (struct aw_acf_dde *)(acf_info->fw_data + acf_info->acf_hdr.ddt_offset); + + AW_DEV_LOGD(dev, "enter"); + + for (i = 0; i < acf_info->acf_hdr.dde_num; i++) { + if ((acf_info->dev_index == acf_dde[i].dev_index) && + (acf_dde[i].type == AW_DDE_DEV_DEFAULT_TYPE_ID)) { + + ret = aw_check_product_name_v_0_0_0_1(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + ret = aw_check_data_type_is_monitor_v_0_0_0_1(dev, &acf_dde[i]); + if (ret == 0) { + cfg_data = acf_info->fw_data + acf_dde[i].data_offset; + ret = aw_parse_monitor_config(dev, cfg_data, acf_dde[i].data_size); + if (ret < 0) + return ret; + continue; + } + + prof_desc = &all_prof_info->prof_desc[acf_dde[i].dev_profile]; + ret = aw_parse_data_by_sec_type_v_0_0_0_1(dev, acf_info, &acf_dde[i], + prof_desc); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse default type data failed"); + return ret; + } + sec_num++; + } + } + + if (sec_num == 0) { + AW_DEV_LOGE(dev, "get dev default type failed, get num[%d]", + sec_num); + return -EINVAL; + } + + return 0; +} + +static int aw_get_prof_count_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_all_prof_info *all_prof_info) +{ + int i = 0; + int prof_count = 0; + struct aw_prof_desc *prof_desc = all_prof_info->prof_desc; + + for (i = 0; i < AW_PROFILE_MAX; i++) { + if (prof_desc[i].prof_st == AW_PROFILE_OK) { + prof_count++; + } else if (i == AW_PROFILE_OFF) { + prof_count++; + AW_DEV_LOGI(dev, "not found profile [Off], set default"); + } + } + + AW_DEV_LOGI(dev, "get profile count=[%d]", prof_count); + return prof_count; +} + +static int aw_set_prof_off_info_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_all_prof_info *all_prof_info, + int index) +{ + struct aw_prof_desc *prof_desc = all_prof_info->prof_desc; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + if (index >= prof_info->count) { + AW_DEV_LOGE(dev, "index[%d] is out of table,profile count[%d]", + index, prof_info->count); + return -EINVAL; + } + + if (prof_desc[AW_PROFILE_OFF].prof_st == AW_PROFILE_OK) { + prof_info->prof_desc[index] = prof_desc[AW_PROFILE_OFF]; + AW_DEV_LOGI(dev, "product=[%s]----profile=[%s]", + prof_info->prof_desc[index].dev_name, + aw_get_prof_name(AW_PROFILE_OFF)); + } else { + memset(&prof_info->prof_desc[index].data_container, 0, + sizeof(struct aw_data_container)); + prof_info->prof_desc[index].prof_st = AW_PROFILE_WAIT; + prof_info->prof_desc[index].prof_name = aw_get_prof_name(AW_PROFILE_OFF); + AW_DEV_LOGI(dev, "set default power_off with no data to profile"); + } + + return 0; +} + + +static int aw_get_vaild_prof_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_all_prof_info *all_prof_info) +{ + int i = 0; + int ret = 0; + int index = 0; + struct aw_prof_desc *prof_desc = all_prof_info->prof_desc; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + prof_info->count = 0; + ret = aw_get_prof_count_v_0_0_0_1(dev, acf_info, all_prof_info); + if (ret < 0) + return ret; + prof_info->count = ret; + prof_info->prof_desc = devm_kzalloc(dev, + prof_info->count * sizeof(struct aw_prof_desc), + GFP_KERNEL); + if (prof_info->prof_desc == NULL) { + AW_DEV_LOGE(dev, "prof_desc kzalloc failed"); + return -ENOMEM; + } + + for (i = 0; i < AW_PROFILE_MAX; i++) { + if (i != AW_PROFILE_OFF && prof_desc[i].prof_st == AW_PROFILE_OK) { + if (index >= prof_info->count) { + AW_DEV_LOGE(dev, "get profile index[%d] overflow count[%d]", + index, prof_info->count); + return -ENOMEM; + } + prof_info->prof_desc[index] = prof_desc[i]; + AW_DEV_LOGI(dev, "product=[%s]----profile=[%s]", + prof_info->prof_desc[index].dev_name, + aw_get_prof_name(i)); + index++; + } + } + + ret = aw_set_prof_off_info_v_0_0_0_1(dev, acf_info, all_prof_info, index); + if (ret < 0) + return ret; + + AW_DEV_LOGD(dev, "get vaild profile succeed"); + return 0; +} + +static int aw_set_prof_name_list_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info) +{ + int i = 0; + int count = acf_info->prof_info.count; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + prof_info->prof_name_list = (char (*)[AW_PROFILE_STR_MAX])devm_kzalloc(dev, + count * (AW_PROFILE_STR_MAX), GFP_KERNEL); + if (prof_info->prof_name_list == NULL) { + AW_DEV_LOGE(dev, "prof_name_list devm_kzalloc failed"); + return -ENOMEM; + } + + for (i = 0; i < count; ++i) { + snprintf(prof_info->prof_name_list[i], AW_PROFILE_STR_MAX, "%s", + prof_info->prof_desc[i].prof_name); + AW_DEV_LOGI(dev, "index=[%d], profile_name=[%s]", + i, prof_info->prof_name_list[i]); + } + + return 0; +} + +static int aw_parse_acf_v_0_0_0_1(struct device *dev, + struct acf_bin_info *acf_info) + +{ + int ret = 0; + struct aw_all_prof_info all_prof_info; + + AW_DEV_LOGD(dev, "enter"); + acf_info->prof_info.status = AW_ACF_WAIT; + + memset(&all_prof_info, 0, sizeof(struct aw_all_prof_info)); + + ret = aw_parse_dev_type_v_0_0_0_1(dev, acf_info, &all_prof_info); + if (ret < 0) { + return ret; + } else if (ret == AW_DEV_TYPE_NONE) { + AW_DEV_LOGD(dev, "get dev type num is 0, parse default dev type"); + ret = aw_parse_default_type_v_0_0_0_1(dev, acf_info, &all_prof_info); + if (ret < 0) + return ret; + } + + ret = aw_get_vaild_prof_v_0_0_0_1(dev, acf_info, &all_prof_info); + if (ret < 0) { + aw87xxx_acf_profile_free(dev, acf_info); + AW_DEV_LOGE(dev, "hdr_cersion[0x%x] parse failed", + acf_info->acf_hdr.hdr_version); + return ret; + } + + ret = aw_set_prof_name_list_v_0_0_0_1(dev, acf_info); + if (ret < 0) { + aw87xxx_acf_profile_free(dev, acf_info); + AW_DEV_LOGE(dev, "creat prof_id_and_name_list failed"); + return ret; + } + + acf_info->prof_info.status = AW_ACF_UPDATE; + AW_DEV_LOGI(dev, "acf parse success"); + return 0; +} + +/*************************************************************************** + * V1.0.0.0 version acf paese + **************************************************************************/ +static int aw_check_product_name_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_acf_dde_v_1_0_0_0 *prof_hdr) +{ + int i = 0; + + for (i = 0; i < acf_info->product_cnt; i++) { + if (0 == strcmp(acf_info->product_tab[i], prof_hdr->dev_name)) { + AW_DEV_LOGI(dev, "bin_dev_name:%s", prof_hdr->dev_name); + return 0; + } + } + + return -ENXIO; +} + +static int aw_get_dde_type_info_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int i; + int dev_num = 0; + int default_num = 0; + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)acf_info->fw_data; + struct aw_prof_info *prof_info = &acf_info->prof_info; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_hdr->ddt_offset); + + prof_info->prof_type = AW_DEV_NONE_TYPE_ID; + for (i = 0; i < acf_hdr->dde_num; i++) { + if (acf_dde[i].type == AW_DDE_DEV_TYPE_ID) + dev_num++; + if (acf_dde[i].type == AW_DDE_DEV_DEFAULT_TYPE_ID) + default_num++; + } + + if (!(dev_num || default_num)) { + AW_DEV_LOGE(dev, "can't find scene"); + return -EINVAL; + } + + if (dev_num != 0) + prof_info->prof_type = AW_DDE_DEV_TYPE_ID; + else if (default_num != 0) + prof_info->prof_type = AW_DDE_DEV_DEFAULT_TYPE_ID; + + return 0; +} + + +static int aw_parse_get_dev_type_prof_count_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)acf_info->fw_data; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_hdr->ddt_offset); + int i = 0; + int ret = 0; + int found_off_prof_flag = 0; + int count = acf_info->prof_info.count; + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (((acf_dde[i].data_type == AW_BIN_TYPE_REG) || + (acf_dde[i].data_type == AW_BIN_TYPE_HDR_REG)) && + ((acf_info->aw_dev->i2c_bus == acf_dde[i].dev_bus) && + (acf_info->aw_dev->i2c_addr == acf_dde[i].dev_addr)) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + ret = aw_check_prof_str_is_off(acf_dde[i].dev_profile_str); + if (ret == 0) { + found_off_prof_flag = AW_PROFILE_OK; + } + count++; + } + } + + if (count == 0) { + AW_DEV_LOGE(dev, "can't find profile"); + return -EINVAL; + } + + if (!found_off_prof_flag) { + count++; + AW_DEV_LOGD(dev, "set no config power off profile in count"); + } + + acf_info->prof_info.count = count; + AW_DEV_LOGI(dev, "profile dev_type profile count is %d", acf_info->prof_info.count); + return 0; +} + +static int aw_parse_get_default_type_prof_count_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)acf_info->fw_data; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_hdr->ddt_offset); + int i = 0; + int ret = 0; + int found_off_prof_flag = 0; + int count = acf_info->prof_info.count; + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (((acf_dde[i].data_type == AW_BIN_TYPE_REG) || + (acf_dde[i].data_type == AW_BIN_TYPE_HDR_REG)) && + (acf_info->dev_index == acf_dde[i].dev_index) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + ret = aw_check_prof_str_is_off(acf_dde[i].dev_profile_str); + if (ret == 0) { + found_off_prof_flag = AW_PROFILE_OK; + } + count++; + } + } + + if (count == 0) { + AW_DEV_LOGE(dev, "can't find profile"); + return -EINVAL; + } + + if (!found_off_prof_flag) { + count++; + AW_DEV_LOGD(dev, "set no config power off profile in count"); + } + + acf_info->prof_info.count = count; + AW_DEV_LOGI(dev, "profile default_type profile count is %d", acf_info->prof_info.count); + return 0; +} + +static int aw_parse_get_profile_count_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int ret = 0; + + ret = aw_get_dde_type_info_v_1_0_0_0(dev, acf_info); + if (ret < 0) + return ret; + + if (acf_info->prof_info.prof_type == AW_DDE_DEV_TYPE_ID) { + ret = aw_parse_get_dev_type_prof_count_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse dev_type profile count failed"); + return ret; + } + } else if (acf_info->prof_info.prof_type == AW_DDE_DEV_DEFAULT_TYPE_ID) { + ret = aw_parse_get_default_type_prof_count_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse default_type profile count failed"); + return ret; + } + } else { + AW_DEV_LOGE(dev, "unsupport prof_type[0x%x]", + acf_info->prof_info.prof_type); + return -EINVAL; + } + + AW_DEV_LOGI(dev, "profile count is %d", acf_info->prof_info.count); + return 0; +} + +static int aw_parse_dev_type_prof_name_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)acf_info->fw_data; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_hdr->ddt_offset); + struct aw_prof_info *prof_info = &acf_info->prof_info; + int i, ret, list_index = 0; + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (((acf_dde[i].data_type == AW_BIN_TYPE_REG) || + (acf_dde[i].data_type == AW_BIN_TYPE_HDR_REG)) && + (acf_info->aw_dev->i2c_bus == acf_dde[i].dev_bus) && + (acf_info->aw_dev->i2c_addr == acf_dde[i].dev_addr) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + if (list_index > prof_info->count) { + AW_DEV_LOGE(dev, "%s:Alrealdy set list_index [%d], redundant profile [%s]exist\n", + __func__, list_index, + acf_dde[i].dev_profile_str); + return -EINVAL; + } + + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + snprintf(prof_info->prof_name_list[list_index], AW_PROFILE_STR_MAX, "%s", + acf_dde[i].dev_profile_str); + AW_DEV_LOGI(dev, "profile_name=[%s]", + prof_info->prof_name_list[list_index]); + list_index++; + } + } + + return 0; +} + +static int aw_parse_default_type_prof_name_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_acf_hdr *acf_hdr = (struct aw_acf_hdr *)acf_info->fw_data; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_hdr->ddt_offset); + struct aw_prof_info *prof_info = &acf_info->prof_info; + int i, ret, list_index = 0; + + for (i = 0; i < acf_hdr->dde_num; ++i) { + if (((acf_dde[i].data_type == AW_BIN_TYPE_REG) || + (acf_dde[i].data_type == AW_BIN_TYPE_HDR_REG)) && + (acf_info->dev_index == acf_dde[i].dev_index) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + if (list_index > prof_info->count) { + AW_DEV_LOGE(dev, "%s:Alrealdy set list_index [%d], redundant profile [%s]exist\n", + __func__, list_index, + acf_dde[i].dev_profile_str); + return -EINVAL; + } + + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + snprintf(prof_info->prof_name_list[list_index], AW_PROFILE_STR_MAX, "%s", + acf_dde[i].dev_profile_str); + AW_DEV_LOGI(dev, "profile_name=[%s]", + prof_info->prof_name_list[list_index]); + list_index++; + } + } + + return 0; +} + +static int aw_parse_prof_name_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int ret = 0; + int count = acf_info->prof_info.count; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + prof_info->prof_name_list = (char (*)[AW_PROFILE_STR_MAX])devm_kzalloc(dev, + count * (AW_PROFILE_STR_MAX), GFP_KERNEL); + if (prof_info->prof_name_list == NULL) { + AW_DEV_LOGE(dev, "prof_name_list devm_kzalloc failed"); + return -ENOMEM; + } + + if (acf_info->prof_info.prof_type == AW_DDE_DEV_TYPE_ID) { + ret = aw_parse_dev_type_prof_name_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse dev_type profile count failed"); + return ret; + } + } else if (acf_info->prof_info.prof_type == AW_DDE_DEV_DEFAULT_TYPE_ID) { + ret = aw_parse_default_type_prof_name_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse default_type profile count failed"); + return ret; + } + } else { + AW_DEV_LOGE(dev, "unsupport prof_type[0x%x]", + acf_info->prof_info.prof_type); + return -EINVAL; + } + + AW_DEV_LOGI(dev, "profile name parse succeed"); + return 0; +} + + +static int aw_search_prof_index_from_list_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_prof_desc **prof_desc, + struct aw_acf_dde_v_1_0_0_0 *prof_hdr) +{ + int i = 0; + int count = acf_info->prof_info.count; + char (*prof_name_list)[AW_PROFILE_STR_MAX] = acf_info->prof_info.prof_name_list; + + for (i = 0; i < count; i++) { + if (!strncmp(prof_name_list[i], prof_hdr->dev_profile_str, AW_PROFILE_STR_MAX)) { + *prof_desc = &(acf_info->prof_info.prof_desc[i]); + return 0; + } + } + + if (i == count) + AW_DEV_LOGE(dev, "not find prof_id and prof_name in list"); + + return -EINVAL; +} + +static int aw_parse_data_by_sec_type_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info, + struct aw_acf_dde_v_1_0_0_0 *prof_hdr) +{ + int ret = -1; + char *cfg_data = acf_info->fw_data + prof_hdr->data_offset; + struct aw_prof_desc *prof_desc = NULL; + + ret = aw_search_prof_index_from_list_v_1_0_0_0(dev, acf_info, &prof_desc, prof_hdr); + if (ret < 0) + return ret; + + switch (prof_hdr->data_type) { + case AW_BIN_TYPE_REG: + snprintf(prof_desc->dev_name, sizeof(prof_hdr->dev_name), + "%s", prof_hdr->dev_name); + AW_DEV_LOGI(dev, "parse reg type data enter,product=[%s],prof_id=[%d],prof_name=[%s]", + prof_hdr->dev_name, prof_hdr->dev_profile, + prof_hdr->dev_profile_str); + prof_desc->prof_name = prof_hdr->dev_profile_str; + ret = aw_parse_raw_reg(dev, cfg_data, prof_hdr->data_size, + prof_desc); + break; + case AW_BIN_TYPE_HDR_REG: + snprintf(prof_desc->dev_name, sizeof(prof_hdr->dev_name), + "%s", prof_hdr->dev_name); + AW_DEV_LOGI(dev, "parse hdr_reg type data enter,product=[%s],prof_id=[%d],prof_name=[%s]", + prof_hdr->dev_name, prof_hdr->dev_profile, + prof_hdr->dev_profile_str); + prof_desc->prof_name = prof_hdr->dev_profile_str; + ret = aw_parse_reg_with_hdr(dev, cfg_data, + prof_hdr->data_size, prof_desc); + break; + } + + return ret; +} + +static int aw_parse_dev_type_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int i = 0; + int ret; + int parse_prof_count = 0; + char *cfg_data = NULL; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_info->acf_hdr.ddt_offset); + + AW_DEV_LOGD(dev, "enter"); + + for (i = 0; i < acf_info->acf_hdr.dde_num; i++) { + if ((acf_dde[i].type == AW_DDE_DEV_TYPE_ID) && + (acf_info->aw_dev->i2c_bus == acf_dde[i].dev_bus) && + (acf_info->aw_dev->i2c_addr == acf_dde[i].dev_addr) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + if (acf_dde[i].data_type == AW_MONITOR) { + cfg_data = acf_info->fw_data + acf_dde[i].data_offset; + AW_DEV_LOGD(dev, "parse monitor type data enter"); + ret = aw_parse_monitor_config(dev, cfg_data, + acf_dde[i].data_size); + } else { + ret = aw_parse_data_by_sec_type_v_1_0_0_0(dev, acf_info, + &acf_dde[i]); + if (ret < 0) + AW_DEV_LOGE(dev, "parse dev type data failed"); + else + parse_prof_count++; + } + } + } + + if (parse_prof_count == 0) { + AW_DEV_LOGE(dev, "get dev type num is %d, parse failed", parse_prof_count); + return -EINVAL; + } + + return AW_DEV_TYPE_OK; +} + +static int aw_parse_default_type_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int i = 0; + int ret; + int parse_prof_count = 0; + char *cfg_data = NULL; + struct aw_acf_dde_v_1_0_0_0 *acf_dde = + (struct aw_acf_dde_v_1_0_0_0 *)(acf_info->fw_data + acf_info->acf_hdr.ddt_offset); + + AW_DEV_LOGD(dev, "enter"); + + for (i = 0; i < acf_info->acf_hdr.dde_num; i++) { + if ((acf_dde[i].type == AW_DDE_DEV_DEFAULT_TYPE_ID) && + (acf_info->dev_index == acf_dde[i].dev_index) && + (acf_info->aw_dev->chipid == acf_dde[i].chip_id)) { + ret = aw_check_product_name_v_1_0_0_0(dev, acf_info, &acf_dde[i]); + if (ret < 0) + continue; + + if (acf_dde[i].data_type == AW_MONITOR) { + cfg_data = acf_info->fw_data + acf_dde[i].data_offset; + AW_DEV_LOGD(dev, "parse monitor type data enter"); + ret = aw_parse_monitor_config(dev, cfg_data, + acf_dde[i].data_size); + } else { + ret = aw_parse_data_by_sec_type_v_1_0_0_0(dev, acf_info, + &acf_dde[i]); + if (ret < 0) + AW_DEV_LOGE(dev, "parse default type data failed"); + else + parse_prof_count++; + } + } + } + + if (parse_prof_count == 0) { + AW_DEV_LOGE(dev, "get default type num is %d,parse failed", parse_prof_count); + return -EINVAL; + } + + return AW_DEV_TYPE_OK; +} + +static int aw_parse_by_hdr_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + int ret; + + if (acf_info->prof_info.prof_type == AW_DDE_DEV_TYPE_ID) { + ret = aw_parse_dev_type_v_1_0_0_0(dev, acf_info); + if (ret < 0) + return ret; + } else if (acf_info->prof_info.prof_type == AW_DDE_DEV_DEFAULT_TYPE_ID) { + ret = aw_parse_default_type_v_1_0_0_0(dev, acf_info); + if (ret < 0) + return ret; + } + + return 0; +} + +static int aw_set_prof_off_info_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_prof_info *prof_info = &acf_info->prof_info; + int i = 0; + int ret = 0; + + for (i = 0; i < prof_info->count; ++i) { + if (!(prof_info->prof_desc[i].prof_st)) { + snprintf(prof_info->prof_name_list[i], AW_PROFILE_STR_MAX, "%s", + g_power_off_name[0]); + prof_info->prof_desc[i].prof_name = prof_info->prof_name_list[i]; + prof_info->prof_desc[i].prof_st = AW_PROFILE_WAIT; + memset(&prof_info->prof_desc[i].data_container, 0, + sizeof(struct aw_data_container)); + return 0; + } + + ret = aw_check_prof_str_is_off(prof_info->prof_name_list[i]); + if (ret == 0) { + AW_DEV_LOGD(dev, "found profile off,data_len=[%d]", + prof_info->prof_desc[i].data_container.len); + return 0; + } + } + + AW_DEV_LOGE(dev, "index[%d] is out of table,profile count[%d]", + i, prof_info->count); + return -EINVAL; +} + +static int aw_parse_acf_v_1_0_0_0(struct device *dev, + struct acf_bin_info *acf_info) + +{ + struct aw_prof_info *prof_info = &acf_info->prof_info; + int ret; + + ret = aw_parse_get_profile_count_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "get profile count failed"); + return ret; + } + + ret = aw_parse_prof_name_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "get profile count failed"); + return ret; + } + + acf_info->prof_info.prof_desc = devm_kzalloc(dev, + prof_info->count * sizeof(struct aw_prof_desc), GFP_KERNEL); + if (acf_info->prof_info.prof_desc == NULL) { + AW_DEV_LOGE(dev, "prof_desc devm_kzalloc failed"); + return -ENOMEM; + } + + ret = aw_parse_by_hdr_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "parse data failed"); + return ret; + } + + ret = aw_set_prof_off_info_v_1_0_0_0(dev, acf_info); + if (ret < 0) { + AW_DEV_LOGE(dev, "set profile off info failed"); + return ret; + } + + prof_info->status = AW_ACF_UPDATE; + AW_DEV_LOGI(dev, "acf paese succeed"); + return 0; +} + + +/************************************************************************* + * + *acf parse API + * + *************************************************************************/ +void aw87xxx_acf_profile_free(struct device *dev, struct acf_bin_info *acf_info) +{ + struct aw_prof_info *prof_info = &acf_info->prof_info; + + prof_info->count = 0; + prof_info->status = AW_ACF_WAIT; + memset(&acf_info->acf_hdr, 0, sizeof(struct aw_acf_hdr)); + + if (prof_info->prof_desc) { + devm_kfree(dev, prof_info->prof_desc); + prof_info->prof_desc = NULL; + } + + if (prof_info->prof_name_list) { + devm_kfree(dev, prof_info->prof_name_list); + prof_info->prof_name_list = NULL; + } + + if (acf_info->fw_data) { + vfree(acf_info->fw_data); + acf_info->fw_data = NULL; + } +} + +int aw87xxx_acf_parse(struct device *dev, struct acf_bin_info *acf_info) +{ + int ret = 0; + + AW_DEV_LOGD(dev, "enter"); + acf_info->prof_info.status = AW_ACF_WAIT; + ret = aw_check_acf_firmware(dev, acf_info->fw_data, + acf_info->fw_size); + if (ret < 0) { + AW_DEV_LOGE(dev, "load firmware check failed"); + return -EINVAL; + } + + memcpy(&acf_info->acf_hdr, acf_info->fw_data, + sizeof(struct aw_acf_hdr)); + + switch (acf_info->acf_hdr.hdr_version) { + case AW_ACF_HDR_VER_0_0_0_1: + return aw_parse_acf_v_0_0_0_1(dev, acf_info); + case AW_ACF_HDR_VER_1_0_0_0: + return aw_parse_acf_v_1_0_0_0(dev, acf_info); + default: + AW_DEV_LOGE(dev, "unsupported hdr_version [0x%x]", + acf_info->acf_hdr.hdr_version); + return -EINVAL; + } + + return ret; +} + +struct aw_prof_desc *aw87xxx_acf_get_prof_desc_form_name(struct device *dev, + struct acf_bin_info *acf_info, char *profile_name) +{ + int i = 0; + struct aw_prof_desc *prof_desc = NULL; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + AW_DEV_LOGD(dev, "enter"); + + if (!acf_info->prof_info.status) { + AW_DEV_LOGE(dev, "profile_cfg not load"); + return NULL; + } + + for (i = 0; i < prof_info->count; i++) { + if (!strncmp(profile_name, prof_info->prof_desc[i].prof_name, + AW_PROFILE_STR_MAX)) { + prof_desc = &prof_info->prof_desc[i]; + break; + } + } + + if (i == prof_info->count) { + AW_DEV_LOGE(dev, "profile not found"); + return NULL; + } + + AW_DEV_LOGI(dev, "get prof desc down"); + return prof_desc; +} + +int aw87xxx_acf_get_prof_index_form_name(struct device *dev, + struct acf_bin_info *acf_info, char *profile_name) +{ + int i = 0; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + if (!acf_info->prof_info.status) { + AW_DEV_LOGE(dev, "profile_cfg not load"); + return -EINVAL; + } + + for (i = 0; i < prof_info->count; i++) { + if (!strncmp(profile_name, prof_info->prof_name_list[i], + AW_PROFILE_STR_MAX)) { + return i; + } + } + + AW_DEV_LOGE(dev, "profile_index not found"); + return -EINVAL; +} + +char *aw87xxx_acf_get_prof_name_form_index(struct device *dev, + struct acf_bin_info *acf_info, int index) +{ + struct aw_prof_info *prof_info = &acf_info->prof_info; + + if (!acf_info->prof_info.status) { + AW_DEV_LOGE(dev, "profile_cfg not load"); + return NULL; + } + + if (index >= prof_info->count || index < 0) { + AW_DEV_LOGE(dev, "profile_index out of table"); + return NULL; + } + + return prof_info->prof_desc[index].prof_name; +} + + +int aw87xxx_acf_get_profile_count(struct device *dev, + struct acf_bin_info *acf_info) +{ + struct aw_prof_info *prof_info = &acf_info->prof_info; + + if (!acf_info->prof_info.status) { + AW_DEV_LOGE(dev, "profile_cfg not load"); + return -EINVAL; + } + + if (prof_info->count > 0) { + return prof_info->count; + } + + return -EINVAL; +} + +char *aw87xxx_acf_get_prof_off_name(struct device *dev, + struct acf_bin_info *acf_info) +{ + int i = 0; + int ret = 0; + struct aw_prof_info *prof_info = &acf_info->prof_info; + + if (!acf_info->prof_info.status) { + AW_DEV_LOGE(dev, "profile_cfg not load"); + return NULL; + } + + for (i = 0; i < prof_info->count; i++) { + ret = aw_check_prof_str_is_off(prof_info->prof_name_list[i]); + if (ret == 0) + return prof_info->prof_name_list[i]; + } + + return NULL; +} + +void aw87xxx_acf_init(struct aw_device *aw_dev, struct acf_bin_info *acf_info, int index) +{ + + acf_info->load_count = 0; + acf_info->prof_info.status = AW_ACF_WAIT; + acf_info->dev_index = index; + acf_info->aw_dev = aw_dev; + acf_info->product_cnt = aw_dev->product_cnt; + acf_info->product_tab = aw_dev->product_tab; + acf_info->prof_info.prof_desc = NULL; + acf_info->fw_data = NULL; + acf_info->fw_size = 0; +} + diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.h b/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.h new file mode 100644 index 000000000000..3a14d9b8af9b --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_acf_bin.h @@ -0,0 +1,191 @@ +#ifndef __AW87XXX_ACF_BIN_H__ +#define __AW87XXX_ACF_BIN_H__ + +#include "aw87xxx_device.h" + +#define AW_PROJECT_NAME_MAX (24) +#define AW_CUSTOMER_NAME_MAX (16) +#define AW_CFG_VERSION_MAX (4) +#define AW_TBL_VERSION_MAX (4) +#define AW_DDE_DEVICE_TYPE (0) +#define AW_DDE_SKT_TYPE (1) +#define AW_DDE_DEFAULT_TYPE (2) + +#define AW_REG_ADDR_BYTE (1) +#define AW_REG_DATA_BYTE (1) + +#define AW_ACF_FILE_ID (0xa15f908) +#define AW_PROFILE_STR_MAX (32) +#define AW_POWER_OFF_NAME_SUPPORT_COUNT (5) + +enum aw_cfg_hdr_version { + AW_ACF_HDR_VER_0_0_0_1 = 0x00000001, + AW_ACF_HDR_VER_1_0_0_0 = 0x01000000, +}; + +enum aw_acf_dde_type_id { + AW_DEV_NONE_TYPE_ID = 0xFFFFFFFF, + AW_DDE_DEV_TYPE_ID = 0x00000000, + AW_DDE_SKT_TYPE_ID = 0x00000001, + AW_DDE_DEV_DEFAULT_TYPE_ID = 0x00000002, + AW_DDE_TYPE_MAX, +}; + +enum aw_raw_data_type_id { + AW_BIN_TYPE_REG = 0x00000000, + AW_BIN_TYPE_DSP, + AW_BIN_TYPE_DSP_CFG, + AW_BIN_TYPE_DSP_FW, + AW_BIN_TYPE_HDR_REG, + AW_BIN_TYPE_HDR_DSP_CFG, + AW_BIN_TYPE_HDR_DSP_FW, + AW_BIN_TYPE_MUTLBIN, + AW_SKT_UI_PROJECT, + AW_DSP_CFG, + AW_MONITOR, + AW_BIN_TYPE_MAX, +}; + +enum { + AW_DEV_TYPE_OK = 0, + AW_DEV_TYPE_NONE = 1, +}; + +enum aw_profile_status { + AW_PROFILE_WAIT = 0, + AW_PROFILE_OK, +}; + +enum aw_acf_load_status { + AW_ACF_WAIT = 0, + AW_ACF_UPDATE, +}; + +enum aw_bin_dev_profile_id { + AW_PROFILE_MUSIC = 0x0000, + AW_PROFILE_VOICE, + AW_PROFILE_VOIP, + AW_PROFILE_RINGTONE, + AW_PROFILE_RINGTONE_HS, + AW_PROFILE_LOWPOWER, + AW_PROFILE_BYPASS, + AW_PROFILE_MMI, + AW_PROFILE_FM, + AW_PROFILE_NOTIFICATION, + AW_PROFILE_RECEIVER, + AW_PROFILE_OFF, + AW_PROFILE_MAX, +}; + +struct aw_acf_hdr { + int32_t a_id; /* acf file ID 0xa15f908 */ + char project[AW_PROJECT_NAME_MAX]; /* project name */ + char custom[AW_CUSTOMER_NAME_MAX]; /* custom name :huawei xiaomi vivo oppo */ + uint8_t version[AW_CFG_VERSION_MAX]; /* author update version */ + int32_t author_id; /* author id */ + int32_t ddt_size; /* sub section table entry size */ + int32_t dde_num; /* sub section table entry num */ + int32_t ddt_offset; /* sub section table offset in file */ + int32_t hdr_version; /* sub section table version */ + int32_t reserve[3]; /* Reserved Bits */ +}; + +struct aw_acf_dde { + int32_t type; /* dde type id */ + char dev_name[AW_CUSTOMER_NAME_MAX]; /* customer dev name */ + int16_t dev_index; /* dev id */ + int16_t dev_bus; /* dev bus id */ + int16_t dev_addr; /* dev addr id */ + int16_t dev_profile; /* dev profile id */ + int32_t data_type; /* data type id */ + int32_t data_size; /* dde data size in block */ + int32_t data_offset; /* dde data offset in block */ + int32_t data_crc; /* dde data crc checkout */ + int32_t reserve[5]; /* Reserved Bits */ +}; + +struct aw_acf_dde_v_1_0_0_0 { + uint32_t type; /* DDE type id */ + char dev_name[AW_CUSTOMER_NAME_MAX]; /* customer dev name */ + uint16_t dev_index; /* dev id */ + uint16_t dev_bus; /* dev bus id */ + uint16_t dev_addr; /* dev addr id */ + uint16_t dev_profile; /* dev profile id*/ + uint32_t data_type; /* data type id */ + uint32_t data_size; /* dde data size in block */ + uint32_t data_offset; /* dde data offset in block */ + uint32_t data_crc; /* dde data crc checkout */ + char dev_profile_str[AW_PROFILE_STR_MAX]; /* dde custom profile name */ + uint32_t chip_id; /* dde custom product chip id */ + uint32_t reserve[4]; +}; + +struct aw_data_with_header { + uint32_t check_sum; + uint32_t header_ver; + uint32_t bin_data_type; + uint32_t bin_data_ver; + uint32_t bin_data_size; + uint32_t ui_ver; + char product[8]; + uint32_t addr_byte_len; + uint32_t data_byte_len; + uint32_t device_addr; + uint32_t reserve[4]; +}; + +struct aw_data_container { + uint32_t len; + uint8_t *data; +}; + +struct aw_prof_desc { + uint32_t prof_st; + char *prof_name; + char dev_name[AW_CUSTOMER_NAME_MAX]; + struct aw_data_container data_container; +}; + +struct aw_all_prof_info { + struct aw_prof_desc prof_desc[AW_PROFILE_MAX]; +}; + +struct aw_prof_info { + int count; + int status; + int prof_type; + char (*prof_name_list)[AW_PROFILE_STR_MAX]; + struct aw_prof_desc *prof_desc; +}; + +struct acf_bin_info { + int load_count; + int fw_size; + int16_t dev_index; + char *fw_data; + int product_cnt; + const char **product_tab; + struct aw_device *aw_dev; + + struct aw_acf_hdr acf_hdr; + struct aw_prof_info prof_info; +}; + +char *aw87xxx_ctos_get_prof_name(int profile_id); +void aw87xxx_acf_profile_free(struct device *dev, + struct acf_bin_info *acf_info); +int aw87xxx_acf_parse(struct device *dev, struct acf_bin_info *acf_info); +struct aw_prof_desc *aw87xxx_acf_get_prof_desc_form_name(struct device *dev, + struct acf_bin_info *acf_info, char *profile_name); +int aw87xxx_acf_get_prof_index_form_name(struct device *dev, + struct acf_bin_info *acf_info, char *profile_name); +char *aw87xxx_acf_get_prof_name_form_index(struct device *dev, + struct acf_bin_info *acf_info, int index); +int aw87xxx_acf_get_profile_count(struct device *dev, + struct acf_bin_info *acf_info); +char *aw87xxx_acf_get_prof_off_name(struct device *dev, + struct acf_bin_info *acf_info); +void aw87xxx_acf_init(struct aw_device *aw_dev, struct acf_bin_info *acf_info, int index); + + +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.c b/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.c new file mode 100644 index 000000000000..89984a6076b2 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.c @@ -0,0 +1,514 @@ +/* +* aw87xxx_bin_parse.c +* +* Copyright (c) 2020 AWINIC Technology CO., LTD +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx_bin_parse.h" + +#define AWINIC_CODE_VERSION "V0.0.7-V1.0.4" /* "code version"-"excel version" */ + +#define DEBUG_LOG_LEVEL +#ifdef DEBUG_LOG_LEVEL +#define DBG(fmt, arg...) do {\ +printk("AWINIC_BIN %s,line= %d,"fmt, __func__, __LINE__, ##arg);\ +} while (0) +#define DBG_ERR(fmt, arg...) do {\ +printk("AWINIC_BIN_ERR %s,line= %d,"fmt, __func__, __LINE__, ##arg);\ +} while (0) +#else +#define DBG(fmt, arg...) do {} while (0) +#define DBG_ERR(fmt, arg...) do {} while (0) +#endif + +#define printing_data_code + +typedef unsigned short int aw_uint16; +typedef unsigned long int aw_uint32; + +#define BigLittleSwap16(A) ((((aw_uint16)(A) & 0xff00) >> 8) | \ + (((aw_uint16)(A) & 0x00ff) << 8)) + +#define BigLittleSwap32(A) ((((aw_uint32)(A) & 0xff000000) >> 24) | \ + (((aw_uint32)(A) & 0x00ff0000) >> 8) | \ + (((aw_uint32)(A) & 0x0000ff00) << 8) | \ + (((aw_uint32)(A) & 0x000000ff) << 24)) + + +static int aw_parse_bin_header_1_0_0(struct aw_bin *bin); + +/** +* +* Interface function +* +* return value: +* value = 0 :success; +* value = -1 :check bin header version +* value = -2 :check bin data type +* value = -3 :check sum or check bin data len error +* value = -4 :check data version +* value = -5 :check register num +* value = -6 :check dsp reg num +* value = -7 :check soc app num +* value = -8 :bin is NULL point +* +**/ + +/******************************************************** +* +* check sum data +* +********************************************************/ +static int aw_check_sum(struct aw_bin *bin, int bin_num) +{ + unsigned int i = 0; + unsigned int sum_data = 0; + unsigned int check_sum = 0; + unsigned char *p_check_sum = NULL; + + DBG("enter\n"); + + p_check_sum = + &(bin->info.data[(bin->header_info[bin_num].valid_data_addr - + bin->header_info[bin_num].header_len)]); + DBG("aw_bin_parse p_check_sum = %p\n", p_check_sum); + check_sum = GET_32_DATA(*(p_check_sum + 3), + *(p_check_sum + 2), + *(p_check_sum + 1), *(p_check_sum)); + + for (i = 4; + i < + bin->header_info[bin_num].bin_data_len + + bin->header_info[bin_num].header_len; i++) { + sum_data += *(p_check_sum + i); + } + DBG("aw_bin_parse bin_num=%d, check_sum = 0x%x, sum_data = 0x%x\n", + bin_num, check_sum, sum_data); + if (sum_data != check_sum) { + p_check_sum = NULL; + DBG_ERR("aw_bin_parse check sum or check bin data len error\n"); + DBG_ERR("aw_bin_parse bin_num=%d, check_sum = 0x%x, sum_data = 0x%x\n", bin_num, check_sum, sum_data); + return -3; + } + p_check_sum = NULL; + + return 0; +} + +static int aw_check_data_version(struct aw_bin *bin, int bin_num) +{ + int i = 0; + DBG("enter\n"); + + for (i = DATA_VERSION_V1; i < DATA_VERSION_MAX; i++) { + if (bin->header_info[bin_num].bin_data_ver == i) { + return 0; + } + } + DBG_ERR("aw_bin_parse Unrecognized this bin data version\n"); + return -4; +} + +static int aw_check_register_num_v1(struct aw_bin *bin, int bin_num) +{ + unsigned int check_register_num = 0; + unsigned int parse_register_num = 0; + unsigned char *p_check_sum = NULL; + + DBG("enter\n"); + + p_check_sum = + &(bin->info.data[(bin->header_info[bin_num].valid_data_addr)]); + DBG("aw_bin_parse p_check_sum = %p\n", p_check_sum); + parse_register_num = GET_32_DATA(*(p_check_sum + 3), + *(p_check_sum + 2), + *(p_check_sum + 1), *(p_check_sum)); + check_register_num = (bin->header_info[bin_num].bin_data_len - 4) / + (bin->header_info[bin_num].reg_byte_len + + bin->header_info[bin_num].data_byte_len); + DBG + ("aw_bin_parse bin_num=%d, parse_register_num = 0x%x, check_register_num = 0x%x\n", + bin_num, parse_register_num, check_register_num); + if (parse_register_num != check_register_num) { + p_check_sum = NULL; + DBG_ERR("aw_bin_parse register num is error\n"); + DBG_ERR("aw_bin_parse bin_num=%d, parse_register_num = 0x%x, check_register_num = 0x%x\n", bin_num, parse_register_num, check_register_num); + return -5; + } + bin->header_info[bin_num].reg_num = parse_register_num; + bin->header_info[bin_num].valid_data_len = + bin->header_info[bin_num].bin_data_len - 4; + p_check_sum = NULL; + bin->header_info[bin_num].valid_data_addr = + bin->header_info[bin_num].valid_data_addr + 4; + return 0; +} + +static int aw_check_dsp_reg_num_v1(struct aw_bin *bin, int bin_num) +{ + unsigned int check_dsp_reg_num = 0; + unsigned int parse_dsp_reg_num = 0; + unsigned char *p_check_sum = NULL; + + DBG("enter\n"); + + p_check_sum = + &(bin->info.data[(bin->header_info[bin_num].valid_data_addr)]); + DBG("aw_bin_parse p_check_sum = %p\n", p_check_sum); + parse_dsp_reg_num = GET_32_DATA(*(p_check_sum + 7), + *(p_check_sum + 6), + *(p_check_sum + 5), *(p_check_sum + 4)); + bin->header_info[bin_num].reg_data_byte_len = + GET_32_DATA(*(p_check_sum + 11), *(p_check_sum + 10), + *(p_check_sum + 9), *(p_check_sum + 8)); + check_dsp_reg_num = + (bin->header_info[bin_num].bin_data_len - + 12) / bin->header_info[bin_num].reg_data_byte_len; + DBG + ("aw_bin_parse bin_num=%d, parse_dsp_reg_num = 0x%x, check_dsp_reg_num = 0x%x\n", + bin_num, parse_dsp_reg_num, check_dsp_reg_num); + if (parse_dsp_reg_num != check_dsp_reg_num) { + p_check_sum = NULL; + DBG_ERR("aw_bin_parse dsp reg num is error\n"); + DBG_ERR("aw_bin_parse bin_num=%d, parse_dsp_reg_num = 0x%x, check_dsp_reg_num = 0x%x\n", bin_num, parse_dsp_reg_num, check_dsp_reg_num); + return -6; + } + bin->header_info[bin_num].download_addr = + GET_32_DATA(*(p_check_sum + 3), *(p_check_sum + 2), + *(p_check_sum + 1), *(p_check_sum)); + bin->header_info[bin_num].reg_num = parse_dsp_reg_num; + bin->header_info[bin_num].valid_data_len = + bin->header_info[bin_num].bin_data_len - 12; + p_check_sum = NULL; + bin->header_info[bin_num].valid_data_addr = + bin->header_info[bin_num].valid_data_addr + 12; + return 0; +} + +static int aw_check_soc_app_num_v1(struct aw_bin *bin, int bin_num) +{ + unsigned int check_soc_app_num = 0; + unsigned int parse_soc_app_num = 0; + unsigned char *p_check_sum = NULL; + + DBG("enter\n"); + + p_check_sum = + &(bin->info.data[(bin->header_info[bin_num].valid_data_addr)]); + DBG("aw_bin_parse p_check_sum = %p\n", p_check_sum); + bin->header_info[bin_num].app_version = GET_32_DATA(*(p_check_sum + 3), + *(p_check_sum + 2), + *(p_check_sum + 1), + *(p_check_sum)); + parse_soc_app_num = GET_32_DATA(*(p_check_sum + 11), + *(p_check_sum + 10), + *(p_check_sum + 9), *(p_check_sum + 8)); + check_soc_app_num = bin->header_info[bin_num].bin_data_len - 12; + DBG + ("aw_bin_parse bin_num=%d, parse_soc_app_num = 0x%x, check_soc_app_num = 0x%x\n", + bin_num, parse_soc_app_num, check_soc_app_num); + if (parse_soc_app_num != check_soc_app_num) { + p_check_sum = NULL; + DBG_ERR("aw_bin_parse soc app num is error\n"); + DBG_ERR("aw_bin_parse bin_num=%d, parse_soc_app_num = 0x%x, check_soc_app_num = 0x%x\n", bin_num, parse_soc_app_num, check_soc_app_num); + return -7; + } + bin->header_info[bin_num].reg_num = parse_soc_app_num; + bin->header_info[bin_num].download_addr = + GET_32_DATA(*(p_check_sum + 7), *(p_check_sum + 6), + *(p_check_sum + 5), *(p_check_sum + 4)); + bin->header_info[bin_num].valid_data_len = + bin->header_info[bin_num].bin_data_len - 12; + p_check_sum = NULL; + bin->header_info[bin_num].valid_data_addr = + bin->header_info[bin_num].valid_data_addr + 12; + return 0; +} + +/************************ +*** +***bin header 1_0_0 +*** +************************/ +static void aw_get_single_bin_header_1_0_0(struct aw_bin *bin) +{ + int i; + DBG("enter %s\n", __func__); + bin->header_info[bin->all_bin_parse_num].header_len = 60; + bin->header_info[bin->all_bin_parse_num].check_sum = + GET_32_DATA(*(bin->p_addr + 3), *(bin->p_addr + 2), + *(bin->p_addr + 1), *(bin->p_addr)); + bin->header_info[bin->all_bin_parse_num].header_ver = + GET_32_DATA(*(bin->p_addr + 7), *(bin->p_addr + 6), + *(bin->p_addr + 5), *(bin->p_addr + 4)); + bin->header_info[bin->all_bin_parse_num].bin_data_type = + GET_32_DATA(*(bin->p_addr + 11), *(bin->p_addr + 10), + *(bin->p_addr + 9), *(bin->p_addr + 8)); + bin->header_info[bin->all_bin_parse_num].bin_data_ver = + GET_32_DATA(*(bin->p_addr + 15), *(bin->p_addr + 14), + *(bin->p_addr + 13), *(bin->p_addr + 12)); + bin->header_info[bin->all_bin_parse_num].bin_data_len = + GET_32_DATA(*(bin->p_addr + 19), *(bin->p_addr + 18), + *(bin->p_addr + 17), *(bin->p_addr + 16)); + bin->header_info[bin->all_bin_parse_num].ui_ver = + GET_32_DATA(*(bin->p_addr + 23), *(bin->p_addr + 22), + *(bin->p_addr + 21), *(bin->p_addr + 20)); + bin->header_info[bin->all_bin_parse_num].reg_byte_len = + GET_32_DATA(*(bin->p_addr + 35), *(bin->p_addr + 34), + *(bin->p_addr + 33), *(bin->p_addr + 32)); + bin->header_info[bin->all_bin_parse_num].data_byte_len = + GET_32_DATA(*(bin->p_addr + 39), *(bin->p_addr + 38), + *(bin->p_addr + 37), *(bin->p_addr + 36)); + bin->header_info[bin->all_bin_parse_num].device_addr = + GET_32_DATA(*(bin->p_addr + 43), *(bin->p_addr + 42), + *(bin->p_addr + 41), *(bin->p_addr + 40)); + for (i = 0; i < 8; i++) { + bin->header_info[bin->all_bin_parse_num].chip_type[i] = + *(bin->p_addr + 24 + i); + } + bin->header_info[bin->all_bin_parse_num].reg_num = 0x00000000; + bin->header_info[bin->all_bin_parse_num].reg_data_byte_len = 0x00000000; + bin->header_info[bin->all_bin_parse_num].download_addr = 0x00000000; + bin->header_info[bin->all_bin_parse_num].app_version = 0x00000000; + bin->header_info[bin->all_bin_parse_num].valid_data_len = 0x00000000; + bin->all_bin_parse_num += 1; +} + +static int aw_parse_each_of_multi_bins_1_0_0(unsigned int bin_num, int bin_serial_num, + struct aw_bin *bin) +{ + int ret = 0; + unsigned int bin_start_addr = 0; + unsigned int valid_data_len = 0; + DBG("aw_bin_parse enter multi bin branch -- %s\n", __func__); + if (!bin_serial_num) { + bin_start_addr = GET_32_DATA(*(bin->p_addr + 67), + *(bin->p_addr + 66), + *(bin->p_addr + 65), + *(bin->p_addr + 64)); + bin->p_addr += (60 + bin_start_addr); + bin->header_info[bin->all_bin_parse_num].valid_data_addr = + bin->header_info[bin->all_bin_parse_num - + 1].valid_data_addr + 4 + 8 * bin_num + 60; + } else { + valid_data_len = + bin->header_info[bin->all_bin_parse_num - 1].bin_data_len; + bin->p_addr += (60 + valid_data_len); + bin->header_info[bin->all_bin_parse_num].valid_data_addr = + bin->header_info[bin->all_bin_parse_num - + 1].valid_data_addr + + bin->header_info[bin->all_bin_parse_num - 1].bin_data_len + + 60; + } + + ret = aw_parse_bin_header_1_0_0(bin); + return ret; +} + +/* Get the number of bins in multi bins, and set a for loop, loop processing each bin data */ +static int aw_get_multi_bin_header_1_0_0(struct aw_bin *bin) +{ + int i = 0; + int ret = 0; + unsigned int bin_num = 0; + DBG("aw_bin_parse enter multi bin branch -- %s\n", __func__); + bin_num = GET_32_DATA(*(bin->p_addr + 63), + *(bin->p_addr + 62), + *(bin->p_addr + 61), *(bin->p_addr + 60)); + if (bin->multi_bin_parse_num == 1) { + bin->header_info[bin->all_bin_parse_num].valid_data_addr = 60; + } + aw_get_single_bin_header_1_0_0(bin); + + for (i = 0; i < bin_num; i++) { + DBG("aw_bin_parse enter multi bin for is %d\n", i); + ret = aw_parse_each_of_multi_bins_1_0_0(bin_num, i, bin); + if (ret < 0) { + return ret; + } + } + return 0; +} + +/******************************************************** +* +* If the bin framework header version is 1.0.0, + determine the data type of bin, and then perform different processing + according to the data type + If it is a single bin data type, write the data directly into the structure array + If it is a multi-bin data type, first obtain the number of bins, + and then recursively call the bin frame header processing function + according to the bin number to process the frame header information of each bin separately +* +********************************************************/ +static int aw_parse_bin_header_1_0_0(struct aw_bin *bin) +{ + int ret = 0; + unsigned int bin_data_type; + DBG("enter %s\n", __func__); + bin_data_type = GET_32_DATA(*(bin->p_addr + 11), + *(bin->p_addr + 10), + *(bin->p_addr + 9), *(bin->p_addr + 8)); + DBG("aw_bin_parse bin_data_type 0x%x\n", bin_data_type); + switch (bin_data_type) { + case DATA_TYPE_REGISTER: + case DATA_TYPE_DSP_REG: + case DATA_TYPE_SOC_APP: + /* Divided into two processing methods, + one is single bin processing, + and the other is single bin processing in multi bin */ + DBG("aw_bin_parse enter single bin branch\n"); + bin->single_bin_parse_num += 1; + DBG("%s bin->single_bin_parse_num is %d\n", __func__, + bin->single_bin_parse_num); + if (!bin->multi_bin_parse_num) { + bin->header_info[bin-> + all_bin_parse_num].valid_data_addr = + 60; + } + aw_get_single_bin_header_1_0_0(bin); + break; + case DATA_TYPE_MULTI_BINS: + /* Get the number of times to enter multi bins */ + DBG("aw_bin_parse enter multi bin branch\n"); + bin->multi_bin_parse_num += 1; + DBG("%s bin->multi_bin_parse_num is %d\n", __func__, + bin->multi_bin_parse_num); + ret = aw_get_multi_bin_header_1_0_0(bin); + if (ret < 0) { + return ret; + } + break; + default: + DBG_ERR("aw_bin_parse Unrecognized this bin data type\n"); + return -2; + } + return 0; +} + +/* get the bin's header version */ +static int aw_check_bin_header_version(struct aw_bin *bin) +{ + int ret = 0; + unsigned int header_version = 0; + + header_version = GET_32_DATA(*(bin->p_addr + 7), + *(bin->p_addr + 6), + *(bin->p_addr + 5), *(bin->p_addr + 4)); + + DBG("aw_bin_parse header_version 0x%x\n", header_version); + + /* Write data to the corresponding structure array + according to different formats of the bin frame header version */ + switch (header_version) { + case HEADER_VERSION_1_0_0: + ret = aw_parse_bin_header_1_0_0(bin); + return ret; + default: + DBG_ERR("aw_bin_parse Unrecognized this bin header version \n"); + return -1; + } +} + +int aw87xxx_parsing_bin_file(struct aw_bin *bin) +{ + int i = 0; + int ret = 0; + + DBG("aw_bin_parse code version:%s\n", AWINIC_CODE_VERSION); + if (!bin) { + DBG_ERR("aw_bin_parse bin is NULL\n"); + return -8; + } + bin->p_addr = bin->info.data; + bin->all_bin_parse_num = 0; + bin->multi_bin_parse_num = 0; + bin->single_bin_parse_num = 0; + + /* filling bins header info */ + ret = aw_check_bin_header_version(bin); + if (ret < 0) { + DBG_ERR("aw_bin_parse check bin header version error\n"); + return ret; + } + bin->p_addr = NULL; + + /* check bin header info */ + for (i = 0; i < bin->all_bin_parse_num; i++) { + /* check sum */ + ret = aw_check_sum(bin, i); + if (ret < 0) { + DBG_ERR("aw_bin_parse check sum data error\n"); + return ret; + } + /* check bin data version */ + ret = aw_check_data_version(bin, i); + if (ret < 0) { + DBG_ERR("aw_bin_parse check data version error\n"); + return ret; + } + /* check valid data */ + if (bin->header_info[i].bin_data_ver == DATA_VERSION_V1) { + /* check register num */ + if (bin->header_info[i].bin_data_type == + DATA_TYPE_REGISTER) { + ret = aw_check_register_num_v1(bin, i); + if (ret < 0) { + DBG_ERR + ("aw_bin_parse check register num error\n"); + return ret; + } + /* check dsp reg num */ + } else if (bin->header_info[i].bin_data_type == + DATA_TYPE_DSP_REG) { + ret = aw_check_dsp_reg_num_v1(bin, i); + if (ret < 0) { + DBG_ERR + ("aw_bin_parse check dsp reg num error\n"); + return ret; + } + /* check soc app num */ + } else if (bin->header_info[i].bin_data_type == + DATA_TYPE_SOC_APP) { + ret = aw_check_soc_app_num_v1(bin, i); + if (ret < 0) { + DBG_ERR + ("aw_bin_parse check soc app num error\n"); + return ret; + } + } else { + bin->header_info[i].valid_data_len = + bin->header_info[i].bin_data_len; + } + } + } + DBG("aw_bin_parse parsing success\n"); + + return 0; +} diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.h b/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.h new file mode 100644 index 000000000000..a99c2409e613 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_bin_parse.h @@ -0,0 +1,73 @@ +#ifndef __AW87XXX_BIN_PARSE_H__ +#define __AW87XXX_BIN_PARSE_H__ + +#define NULL ((void *)0) +#define GET_32_DATA(w, x, y, z) ((unsigned int)(((w) << 24) | ((x) << 16) | ((y) << 8) | (z))) +#define BIN_NUM_MAX 100 +#define HEADER_LEN 60 +/********************************************************* + * + * header information + * + ********************************************************/ +enum bin_header_version_enum { + HEADER_VERSION_1_0_0 = 0x01000000, +}; + +enum data_type_enum { + DATA_TYPE_REGISTER = 0x00000000, + DATA_TYPE_DSP_REG = 0x00000010, + DATA_TYPE_DSP_CFG = 0x00000011, + DATA_TYPE_SOC_REG = 0x00000020, + DATA_TYPE_SOC_APP = 0x00000021, + DATA_TYPE_MULTI_BINS = 0x00002000, + DATA_TYPE_MONITOR_ANALOG = 0x00020000, +}; + +enum data_version_enum { + DATA_VERSION_V1 = 0X00000001, /*default little edian */ + DATA_VERSION_MAX, +}; + +struct bin_header_info { + unsigned int header_len; /* Frame header length */ + unsigned int check_sum; /* Frame header information-Checksum */ + unsigned int header_ver; /* Frame header information-Frame header version */ + unsigned int bin_data_type; /* Frame header information-Data type */ + unsigned int bin_data_ver; /* Frame header information-Data version */ + unsigned int bin_data_len; /* Frame header information-Data length */ + unsigned int ui_ver; /* Frame header information-ui version */ + unsigned char chip_type[8]; /* Frame header information-chip type */ + unsigned int reg_byte_len; /* Frame header information-reg byte len */ + unsigned int data_byte_len; /* Frame header information-data byte len */ + unsigned int device_addr; /* Frame header information-device addr */ + unsigned int valid_data_len; /* Length of valid data obtained after parsing */ + unsigned int valid_data_addr; /* The offset address of the valid data obtained after parsing relative to info */ + + unsigned int reg_num; /* The number of registers obtained after parsing */ + unsigned int reg_data_byte_len; /* The byte length of the register obtained after parsing */ + unsigned int download_addr; /* The starting address or download address obtained after parsing */ + unsigned int app_version; /* The software version number obtained after parsing */ +}; + +/************************************************************ +* +* function define +* +************************************************************/ +struct bin_container { + unsigned int len; /* The size of the bin file obtained from the firmware */ + unsigned char data[]; /* Store the bin file obtained from the firmware */ +}; + +struct aw_bin { + unsigned char *p_addr; /* Offset pointer (backward offset pointer to obtain frame header information and important information) */ + unsigned int all_bin_parse_num; /* The number of all bin files */ + unsigned int multi_bin_parse_num; /* The number of single bin files */ + unsigned int single_bin_parse_num; /* The number of multiple bin files */ + struct bin_header_info header_info[BIN_NUM_MAX]; /* Frame header information and other important data obtained after parsing */ + struct bin_container info; /* Obtained bin file data that needs to be parsed */ +}; + +extern int aw87xxx_parsing_bin_file(struct aw_bin *bin); +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_device.c b/sound/soc/codecs/aw87xxx/aw87xxx_device.c new file mode 100644 index 000000000000..8619aacc2ccf --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_device.c @@ -0,0 +1,976 @@ +/* + * aw87xxx_device.c aw87xxx pa module + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * Author: Barry + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx.h" +#include "aw87xxx_device.h" +#include "aw87xxx_log.h" +#include "aw87xxx_pid_9b_reg.h" +#include "aw87xxx_pid_18_reg.h" +#include "aw87xxx_pid_39_reg.h" +#include "aw87xxx_pid_59_3x9_reg.h" +#include "aw87xxx_pid_59_5x9_reg.h" +#include "aw87xxx_pid_5a_reg.h" +#include "aw87xxx_pid_76_reg.h" +#include "aw87xxx_pid_60_reg.h" + +/************************************************************************* + * aw87xxx variable + ************************************************************************/ +const char *g_aw_pid_9b_product[] = { + "aw87319", +}; +const char *g_aw_pid_18_product[] = { + "aw87418", +}; + +const char *g_aw_pid_39_product[] = { + "aw87329", + "aw87339", + "aw87349", +}; + +const char *g_aw_pid_59_3x9_product[] = { + "aw87359", + "aw87389", +}; + +const char *g_aw_pid_59_5x9_product[] = { + "aw87509", + "aw87519", + "aw87529", + "aw87539", +}; + +const char *g_aw_pid_5a_product[] = { + "aw87549", + "aw87559", + "aw87569", + "aw87579", + "aw81509", +}; + +const char *g_aw_pid_76_product[] = { + "aw87390", + "aw87320", + "aw87401", + "aw87360", +}; + +const char *g_aw_pid_60_product[] = { + "aw87560", + "aw87561", + "aw87562", + "aw87501", + "aw87550", +}; + +static int aw87xxx_dev_get_chipid(struct aw_device *aw_dev); + +/*************************************************************************** + * + * reading and writing of I2C bus + * + ***************************************************************************/ +int aw87xxx_dev_i2c_write_byte(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_smbus_write_byte_data(aw_dev->i2c, reg_addr, reg_data); + if (ret < 0) + AW_DEV_LOGE(aw_dev->dev, "i2c_write cnt=%d error=%d i2c_bus=%u i2c_addr=%X chipid=%X", + cnt, ret, aw_dev->i2c_bus, aw_dev->i2c_addr, aw_dev->chipid); + else + break; + + cnt++; + msleep(AW_I2C_RETRY_DELAY); + } + + return ret; +} + +int aw87xxx_dev_i2c_read_byte(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t *reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_smbus_read_byte_data(aw_dev->i2c, reg_addr); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "i2c_read cnt=%d error=%d i2c_bus=%u i2c_addr=%X chipid=%X", + cnt, ret, aw_dev->i2c_bus, aw_dev->i2c_addr, aw_dev->chipid); + } else { + *reg_data = ret; + break; + } + cnt++; + msleep(AW_I2C_RETRY_DELAY); + } + + return ret; +} + +int aw87xxx_dev_i2c_read_msg(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t *data_buf, uint32_t data_len) +{ + int ret = -1; + + struct i2c_msg msg[] = { + [0] = { + .addr = aw_dev->i2c_addr, + .flags = 0, + .len = sizeof(uint8_t), + .buf = ®_addr, + }, + [1] = { + .addr = aw_dev->i2c_addr, + .flags = I2C_M_RD, + .len = data_len, + .buf = data_buf, + }, + }; + + ret = i2c_transfer(aw_dev->i2c->adapter, msg, ARRAY_SIZE(msg)); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "transfer failed"); + return ret; + } else if (ret != AW_I2C_READ_MSG_NUM) { + AW_DEV_LOGE(aw_dev->dev, "transfer failed(size error)"); + return -ENXIO; + } + + return 0; +} + +int aw87xxx_dev_i2c_write_bits(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t mask, uint8_t reg_data) +{ + int ret = -1; + unsigned char reg_val = 0; + + ret = aw87xxx_dev_i2c_read_byte(aw_dev, reg_addr, ®_val); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "i2c read error, ret=%d", ret); + return ret; + } + reg_val &= mask; + reg_val |= (reg_data & (~mask)); + ret = aw87xxx_dev_i2c_write_byte(aw_dev, reg_addr, reg_val); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "i2c write error, ret=%d", ret); + return ret; + } + + return 0; +} + +/************************************************************************ + * + * aw87xxx device update profile data to registers + * + ************************************************************************/ +static int aw87xxx_dev_reg_update(struct aw_device *aw_dev, + struct aw_data_container *profile_data) +{ + int i = 0; + int ret = -1; + + if (profile_data == NULL) + return -EINVAL; + + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGE(aw_dev->dev, "dev is pwr_off,can not update reg"); + return -EINVAL; + } + + for (i = 0; i < profile_data->len; i = i + 2) { + AW_DEV_LOGI(aw_dev->dev, "reg=0x%02x, val = 0x%02x", + profile_data->data[i], profile_data->data[i + 1]); + + ret = aw87xxx_dev_i2c_write_byte(aw_dev, profile_data->data[i], + profile_data->data[i + 1]); + if (ret < 0) + return ret; + } + + return 0; +} + +static void aw87xxx_dev_reg_mute_bits_set(struct aw_device *aw_dev, + uint8_t *reg_val, bool enable) +{ + if (enable) { + *reg_val &= aw_dev->mute_desc.mask; + *reg_val |= aw_dev->mute_desc.enable; + } else { + *reg_val &= aw_dev->mute_desc.mask; + *reg_val |= aw_dev->mute_desc.disable; + } +} + +static int aw87xxx_dev_reg_update_mute(struct aw_device *aw_dev, + struct aw_data_container *profile_data) +{ + int i = 0; + int ret = -1; + uint8_t reg_val = 0; + + if (profile_data == NULL) + return -EINVAL; + + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGE(aw_dev->dev, "hwen is off,can not update reg"); + return -EINVAL; + } + + if (aw_dev->mute_desc.mask == AW_DEV_REG_INVALID_MASK) { + AW_DEV_LOGE(aw_dev->dev, "mute ctrl mask invalid"); + return -EINVAL; + } + + for (i = 0; i < profile_data->len; i = i + 2) { + AW_DEV_LOGI(aw_dev->dev, "reg=0x%02x, val = 0x%02x", + profile_data->data[i], profile_data->data[i + 1]); + + reg_val = profile_data->data[i + 1]; + if (profile_data->data[i] == aw_dev->mute_desc.addr) { + aw87xxx_dev_reg_mute_bits_set(aw_dev, ®_val, true); + AW_DEV_LOGD(aw_dev->dev, "change mute_mask, val = 0x%02x", + reg_val); + } + + ret = aw87xxx_dev_i2c_write_byte(aw_dev, profile_data->data[i], reg_val); + if (ret < 0) + return ret; + } + + return 0; +} + +/************************************************************************ + * + * aw87xxx device hadware and soft contols + * + ************************************************************************/ +static bool aw87xxx_dev_gpio_is_valid(struct aw_device *aw_dev) +{ + if (gpio_is_valid(aw_dev->rst_gpio)) + return true; + else + return false; +} + +void aw87xxx_dev_hw_pwr_ctrl(struct aw_device *aw_dev, bool enable) +{ + if (aw_dev->hwen_status == AW_DEV_HWEN_INVALID) { + AW_DEV_LOGD(aw_dev->dev, "product not have reset-pin,hardware pwd control invalid"); + return; + } + if (enable) { + if (aw87xxx_dev_gpio_is_valid(aw_dev)) { + gpio_set_value_cansleep(aw_dev->rst_gpio, AW_GPIO_LOW_LEVEL); + mdelay(2); + gpio_set_value_cansleep(aw_dev->rst_gpio, AW_GPIO_HIGHT_LEVEL); + mdelay(2); + aw_dev->hwen_status = AW_DEV_HWEN_ON; + AW_DEV_LOGI(aw_dev->dev, "hw power on"); + } else { + AW_DEV_LOGI(aw_dev->dev, "hw already power on"); + } + } else { + if (aw87xxx_dev_gpio_is_valid(aw_dev)) { + gpio_set_value_cansleep(aw_dev->rst_gpio, AW_GPIO_LOW_LEVEL); + mdelay(2); + aw_dev->hwen_status = AW_DEV_HWEN_OFF; + AW_DEV_LOGI(aw_dev->dev, "hw power off"); + } else { + AW_DEV_LOGI(aw_dev->dev, "hw already power off"); + } + } +} + +static int aw87xxx_dev_mute_ctrl(struct aw_device *aw_dev, bool enable) +{ + int ret = 0; + + if (enable) { + ret = aw87xxx_dev_i2c_write_bits(aw_dev, aw_dev->mute_desc.addr, + aw_dev->mute_desc.mask, aw_dev->mute_desc.enable); + if (ret < 0) + return ret; + AW_DEV_LOGI(aw_dev->dev, "set mute down"); + } else { + ret = aw87xxx_dev_i2c_write_bits(aw_dev, aw_dev->mute_desc.addr, + aw_dev->mute_desc.mask, aw_dev->mute_desc.disable); + if (ret < 0) + return ret; + AW_DEV_LOGI(aw_dev->dev, "close mute down"); + } + + return 0; +} + +void aw87xxx_dev_soft_reset(struct aw_device *aw_dev) +{ + int i = 0; + int ret = -1; + struct aw_soft_rst_desc *soft_rst = &aw_dev->soft_rst_desc; + + AW_DEV_LOGD(aw_dev->dev, "enter"); + + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGE(aw_dev->dev, "hw is off,can not softrst"); + return; + } + + if (aw_dev->soft_rst_enable == AW_DEV_SOFT_RST_DISENABLE) { + AW_DEV_LOGD(aw_dev->dev, "softrst is disenable"); + return; + } + + if (soft_rst->access == NULL || soft_rst->len == 0) { + AW_DEV_LOGE(aw_dev->dev, "softrst_info not init"); + return; + } + + if (soft_rst->len % 2) { + AW_DEV_LOGE(aw_dev->dev, "softrst data_len[%d] is odd number,data not available", + aw_dev->soft_rst_desc.len); + return; + } + + for (i = 0; i < soft_rst->len; i += 2) { + AW_DEV_LOGD(aw_dev->dev, "softrst_reg=0x%02x, val = 0x%02x", + soft_rst->access[i], soft_rst->access[i + 1]); + + ret = aw87xxx_dev_i2c_write_byte(aw_dev, soft_rst->access[i], + soft_rst->access[i + 1]); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "write failed,ret = %d,cnt=%d", + ret, i); + return; + } + } + AW_DEV_LOGD(aw_dev->dev, "down"); +} + + +int aw87xxx_dev_default_pwr_off(struct aw_device *aw_dev, + struct aw_data_container *profile_data) +{ + int ret = 0; + + AW_DEV_LOGD(aw_dev->dev, "enter"); + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGE(aw_dev->dev, "hwen is already off"); + return 0; + } + + if (aw_dev->soft_off_enable && profile_data) { + ret = aw87xxx_dev_reg_update(aw_dev, profile_data); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "update profile[Off] fw config failed"); + goto reg_off_update_failed; + } + } + + aw87xxx_dev_hw_pwr_ctrl(aw_dev, false); + AW_DEV_LOGD(aw_dev->dev, "down"); + return 0; + +reg_off_update_failed: + aw87xxx_dev_hw_pwr_ctrl(aw_dev, false); + return ret; +} + + +/************************************************************************ + * + * aw87xxx device power on process function + * + ************************************************************************/ + +int aw87xxx_dev_default_pwr_on(struct aw_device *aw_dev, + struct aw_data_container *profile_data) +{ + int ret = 0; + + /*hw power on*/ + aw87xxx_dev_hw_pwr_ctrl(aw_dev, true); + + ret = aw87xxx_dev_reg_update(aw_dev, profile_data); + if (ret < 0) + return ret; + + return 0; +} + +/**************************************************************************** + * + * aw87xxx chip esd status check + * + ****************************************************************************/ +int aw87xxx_dev_esd_reg_status_check(struct aw_device *aw_dev) +{ + int ret; + unsigned char reg_val = 0; + struct aw_esd_check_desc *esd_desc = &aw_dev->esd_desc; + + AW_DEV_LOGD(aw_dev->dev, "enter"); + + if (!esd_desc->first_update_reg_addr) { + AW_DEV_LOGE(aw_dev->dev, "esd check info if not init,please check"); + return -EINVAL; + } + + ret = aw87xxx_dev_i2c_read_byte(aw_dev, esd_desc->first_update_reg_addr, + ®_val); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "read reg 0x%02x failed", + esd_desc->first_update_reg_addr); + return ret; + } + + AW_DEV_LOGD(aw_dev->dev, "0x%02x:default val=0x%02x real val=0x%02x", + esd_desc->first_update_reg_addr, + esd_desc->first_update_reg_val, reg_val); + + if (reg_val == esd_desc->first_update_reg_val) { + AW_DEV_LOGE(aw_dev->dev, "reg status check failed"); + return -EINVAL; + } + return 0; +} + +int aw87xxx_dev_check_reg_is_rec_mode(struct aw_device *aw_dev) +{ + int ret; + unsigned char reg_val = 0; + struct aw_rec_mode_desc *rec_desc = &aw_dev->rec_desc; + + if (!rec_desc->addr) { + AW_DEV_LOGE(aw_dev->dev, "rec check info if not init,please check"); + return -EINVAL; + } + + ret = aw87xxx_dev_i2c_read_byte(aw_dev, rec_desc->addr, ®_val); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "read reg 0x%02x failed", + rec_desc->addr); + return ret; + } + + if (rec_desc->enable) { + if (reg_val & ~(rec_desc->mask)) { + AW_DEV_LOGI(aw_dev->dev, "reg status is receiver mode"); + aw_dev->is_rec_mode = AW_IS_REC_MODE; + } else { + aw_dev->is_rec_mode = AW_NOT_REC_MODE; + } + } else { + if (!(reg_val & ~(rec_desc->mask))) { + AW_DEV_LOGI(aw_dev->dev, "reg status is receiver mode"); + aw_dev->is_rec_mode = AW_IS_REC_MODE; + } else { + aw_dev->is_rec_mode = AW_NOT_REC_MODE; + } + } + return 0; +} + + +/**************************************************************************** + * + * aw87xxx product attributes init info + * + ****************************************************************************/ + +/********************** aw87xxx_pid_9A attributes ***************************/ + +static int aw_dev_pid_9b_reg_update(struct aw_device *aw_dev, + struct aw_data_container *profile_data) +{ + int i = 0; + int ret = -1; + uint8_t reg_val = 0; + + if (profile_data == NULL) + return -EINVAL; + + if (aw_dev->hwen_status == AW_DEV_HWEN_OFF) { + AW_DEV_LOGE(aw_dev->dev, "dev is pwr_off,can not update reg"); + return -EINVAL; + } + + if (profile_data->len != AW_PID_9B_BIN_REG_CFG_COUNT) { + AW_DEV_LOGE(aw_dev->dev, "reg_config count of bin is error,can not update reg"); + return -EINVAL; + } + ret = aw87xxx_dev_i2c_write_byte(aw_dev, AW87XXX_PID_9B_ENCRYPTION_REG, + AW87XXX_PID_9B_ENCRYPTION_BOOST_OUTPUT_SET); + if (ret < 0) + return ret; + + for (i = 1; i < AW_PID_9B_BIN_REG_CFG_COUNT; i++) { + AW_DEV_LOGI(aw_dev->dev, "reg=0x%02x, val = 0x%02x", + i, profile_data->data[i]); + reg_val = profile_data->data[i]; + if (i == AW87XXX_PID_9B_SYSCTRL_REG) { + aw87xxx_dev_reg_mute_bits_set(aw_dev, ®_val, true); + AW_DEV_LOGD(aw_dev->dev, "change mute_mask, val = 0x%02x", + reg_val); + } + + ret = aw87xxx_dev_i2c_write_byte(aw_dev, i, reg_val); + if (ret < 0) + return ret; + } + + return 0; +} + +static int aw_dev_pid_9b_pwr_on(struct aw_device *aw_dev, struct aw_data_container *data) +{ + int ret = 0; + + /*hw power on*/ + aw87xxx_dev_hw_pwr_ctrl(aw_dev, true); + + /* open the mute */ + ret = aw87xxx_dev_mute_ctrl(aw_dev, true); + if (ret < 0) + return ret; + + /* Update scene parameters in mute mode */ + ret = aw_dev_pid_9b_reg_update(aw_dev, data); + if (ret < 0) + return ret; + + /* close the mute */ + ret = aw87xxx_dev_mute_ctrl(aw_dev, false); + if (ret < 0) + return ret; + + return 0; +} + +static void aw_dev_pid_9b_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_9B_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_9b_reg_access; + + aw_dev->mute_desc.addr = AW87XXX_PID_9B_SYSCTRL_REG; + aw_dev->mute_desc.mask = AW87XXX_PID_9B_REG_EN_SW_MASK; + aw_dev->mute_desc.enable = AW87XXX_PID_9B_REG_EN_SW_DISABLE_VALUE; + aw_dev->mute_desc.disable = AW87XXX_PID_9B_REG_EN_SW_ENABLE_VALUE; + aw_dev->ops.pwr_on_func = aw_dev_pid_9b_pwr_on; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_9b_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_9b_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_DISENABLE; + + aw_dev->product_tab = g_aw_pid_9b_product; + aw_dev->product_cnt = AW87XXX_PID_9B_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_9B_SYSCTRL_REG; + aw_dev->rec_desc.disable = AW87XXX_PID_9B_SPK_MODE_ENABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_9B_SPK_MODE_DISABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_9B_SPK_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_9B_SYSCTRL_REG; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_9B_SYSCTRL_DEFAULT; +} + +static int aw_dev_pid_9a_init(struct aw_device *aw_dev) +{ + int ret = 0; + + ret = aw87xxx_dev_i2c_write_byte(aw_dev, AW87XXX_PID_9B_ENCRYPTION_REG, + AW87XXX_PID_9B_ENCRYPTION_BOOST_OUTPUT_SET); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "write 0x64=0x2C error"); + return -EINVAL; + } + + ret = aw87xxx_dev_get_chipid(aw_dev); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "read chipid is failed,ret=%d", ret); + return ret; + } + + if (aw_dev->chipid == AW_DEV_CHIPID_9B) { + AW_DEV_LOGI(aw_dev->dev, "product is pid_9B class"); + aw_dev_pid_9b_init(aw_dev); + } else { + AW_DEV_LOGE(aw_dev->dev, "product is not pid_9B class,not support"); + return -EINVAL; + } + + return 0; +} + +/********************** aw87xxx_pid_9b attributes end ***********************/ + +/********************** aw87xxx_pid_18 attributes ***************************/ +static int aw_dev_pid_18_pwr_on(struct aw_device *aw_dev, struct aw_data_container *data) +{ + int ret = 0; + + /*hw power on*/ + aw87xxx_dev_hw_pwr_ctrl(aw_dev, true); + + /* open the mute */ + ret = aw87xxx_dev_mute_ctrl(aw_dev, true); + if (ret < 0) + return ret; + + /* Update scene parameters in mute mode */ + ret = aw87xxx_dev_reg_update_mute(aw_dev, data); + if (ret < 0) + return ret; + + /* close the mute */ + ret = aw87xxx_dev_mute_ctrl(aw_dev, false); + if (ret < 0) + return ret; + + return 0; +} + +static void aw_dev_chipid_18_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_18_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_18_reg_access; + + aw_dev->mute_desc.addr = AW87XXX_PID_18_SYSCTRL_REG; + aw_dev->mute_desc.mask = AW87XXX_PID_18_REG_EN_SW_MASK; + aw_dev->mute_desc.enable = AW87XXX_PID_18_REG_EN_SW_DISABLE_VALUE; + aw_dev->mute_desc.disable = AW87XXX_PID_18_REG_EN_SW_ENABLE_VALUE; + aw_dev->ops.pwr_on_func = aw_dev_pid_18_pwr_on; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_18_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_18_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_18_product; + aw_dev->product_cnt = AW87XXX_PID_18_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_18_SYSCTRL_REG; + aw_dev->rec_desc.disable = AW87XXX_PID_18_REG_REC_MODE_DISABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_18_REG_REC_MODE_ENABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_18_REG_REC_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_18_CLASSD_REG; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_18_CLASSD_DEFAULT; +} +/********************** aw87xxx_pid_18 attributes end ***********************/ + +/********************** aw87xxx_pid_39 attributes ***************************/ +static void aw_dev_chipid_39_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_39_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_39_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_39_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_39_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_39_product; + aw_dev->product_cnt = AW87XXX_PID_39_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_39_REG_MODECTRL; + aw_dev->rec_desc.disable = AW87XXX_PID_39_REC_MODE_DISABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_39_REC_MODE_ENABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_39_REC_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_39_REG_MODECTRL; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_39_MODECTRL_DEFAULT; +} +/********************* aw87xxx_pid_39 attributes end *************************/ + + +/********************* aw87xxx_pid_59_5x9 attributes *************************/ +static void aw_dev_chipid_59_5x9_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_59_5X9_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_59_5x9_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_59_5x9_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_59_5x9_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_59_5x9_product; + aw_dev->product_cnt = AW87XXX_PID_59_5X9_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_59_5X9_REG_SYSCTRL; + aw_dev->rec_desc.disable = AW87XXX_PID_59_5X9_REC_MODE_DISABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_59_5X9_REC_MODE_ENABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_59_5X9_REC_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_59_5X9_REG_ENCR; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_59_5X9_ENCRY_DEFAULT; +} +/******************* aw87xxx_pid_59_5x9 attributes end ***********************/ + +/********************* aw87xxx_pid_59_3x9 attributes *************************/ +static void aw_dev_chipid_59_3x9_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_59_3X9_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_59_3x9_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_59_3x9_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_59_3x9_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_59_3x9_product; + aw_dev->product_cnt = AW87XXX_PID_59_3X9_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_59_3X9_REG_MDCRTL; + aw_dev->rec_desc.disable = AW87XXX_PID_59_3X9_SPK_MODE_ENABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_59_3X9_SPK_MODE_DISABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_59_3X9_SPK_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_59_3X9_REG_ENCR; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_59_3X9_ENCR_DEFAULT; +} +/******************* aw87xxx_pid_59_3x9 attributes end ***********************/ + +/********************** aw87xxx_pid_5a attributes ****************************/ +static void aw_dev_chipid_5a_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_5A_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_5a_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_5a_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_5a_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* Whether to allow register operation to power off */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_5a_product; + aw_dev->product_cnt = AW87XXX_PID_5A_PRODUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_5A_REG_SYSCTRL_REG; + aw_dev->rec_desc.disable = AW87XXX_PID_5A_REG_RCV_MODE_DISABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_5A_REG_RCV_MODE_ENABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_5A_REG_RCV_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_5A_REG_DFT3R_REG; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_5A_DFT3R_DEFAULT; +} +/********************** aw87xxx_pid_5a attributes end ************************/ + +/********************** aw87xxx_pid_76 attributes ****************************/ +static void aw_dev_chipid_76_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_76_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_76_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_76_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_76_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* software power off control info */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_76_product; + aw_dev->product_cnt = AW87XXX_PID_76_PROFUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_76_MDCTRL_REG; + aw_dev->rec_desc.disable = AW87XXX_PID_76_EN_SPK_ENABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_76_EN_SPK_DISABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_76_EN_SPK_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_76_DFT_ADP1_REG; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_76_DFT_ADP1_CHECK; +} +/********************** aw87xxx_pid_76 attributes end ************************/ + +/********************** aw87xxx_pid_60 attributes ****************************/ +static void aw_dev_chipid_60_init(struct aw_device *aw_dev) +{ + /* Product register permission info */ + aw_dev->reg_max_addr = AW87XXX_PID_60_REG_MAX; + aw_dev->reg_access = aw87xxx_pid_60_reg_access; + + /* software reset control info */ + aw_dev->soft_rst_desc.len = sizeof(aw87xxx_pid_60_softrst_access); + aw_dev->soft_rst_desc.access = aw87xxx_pid_60_softrst_access; + aw_dev->soft_rst_enable = AW_DEV_SOFT_RST_ENABLE; + + /* software power off control info */ + aw_dev->soft_off_enable = AW_DEV_SOFT_OFF_ENABLE; + + aw_dev->product_tab = g_aw_pid_60_product; + aw_dev->product_cnt = AW87XXX_PID_60_PROFUCT_MAX; + + aw_dev->rec_desc.addr = AW87XXX_PID_60_SYSCTRL_REG; + aw_dev->rec_desc.disable = AW87XXX_PID_60_RCV_MODE_DISABLE; + aw_dev->rec_desc.enable = AW87XXX_PID_60_RCV_MODE_ENABLE; + aw_dev->rec_desc.mask = AW87XXX_PID_60_RCV_MODE_MASK; + + /* esd reg info */ + aw_dev->esd_desc.first_update_reg_addr = AW87XXX_PID_60_NG3_REG; + aw_dev->esd_desc.first_update_reg_val = AW87XXX_PID_60_ESD_REG_VAL; +} +/********************** aw87xxx_pid_60 attributes end ************************/ + +static int aw_dev_chip_init(struct aw_device *aw_dev) +{ + int ret = 0; + + /*get info by chipid*/ + switch (aw_dev->chipid) { + case AW_DEV_CHIPID_9A: + ret = aw_dev_pid_9a_init(aw_dev); + if (ret < 0) + AW_DEV_LOGE(aw_dev->dev, "product is pid_9B init failed"); + break; + case AW_DEV_CHIPID_9B: + aw_dev_pid_9b_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_9B class"); + break; + case AW_DEV_CHIPID_18: + aw_dev_chipid_18_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_18 class"); + break; + case AW_DEV_CHIPID_39: + aw_dev_chipid_39_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_39 class"); + break; + case AW_DEV_CHIPID_59: + if (aw87xxx_dev_gpio_is_valid(aw_dev)) { + aw_dev_chipid_59_5x9_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_59_5x9 class"); + } else { + aw_dev_chipid_59_3x9_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_59_3x9 class"); + } + break; + case AW_DEV_CHIPID_5A: + aw_dev_chipid_5a_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_5A class"); + break; + case AW_DEV_CHIPID_76: + aw_dev_chipid_76_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_76 class"); + break; + case AW_DEV_CHIPID_60: + aw_dev_chipid_60_init(aw_dev); + AW_DEV_LOGI(aw_dev->dev, "product is pid_60 class"); + break; + default: + AW_DEV_LOGE(aw_dev->dev, "unsupported device revision [0x%x]", + aw_dev->chipid); + return -EINVAL; + } + + return 0; +} + +static int aw87xxx_dev_get_chipid(struct aw_device *aw_dev) +{ + int ret = -1; + unsigned int cnt = 0; + unsigned char reg_val = 0; + + for (cnt = 0; cnt < AW_READ_CHIPID_RETRIES; cnt++) { + ret = aw87xxx_dev_i2c_read_byte(aw_dev, AW_DEV_REG_CHIPID, ®_val); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "[%d] read chip is failed, ret=%d", + cnt, ret); + continue; + } + break; + } + + + if (cnt == AW_READ_CHIPID_RETRIES) { + AW_DEV_LOGE(aw_dev->dev, "read chip is failed,cnt=%d", cnt); + return -EINVAL; + } + + AW_DEV_LOGI(aw_dev->dev, "read chipid[0x%x] succeed", reg_val); + aw_dev->chipid = reg_val; + + return 0; +} + +int aw87xxx_dev_init(struct aw_device *aw_dev) +{ + int ret = -1; + + ret = aw87xxx_dev_get_chipid(aw_dev); + if (ret < 0) { + AW_DEV_LOGE(aw_dev->dev, "read chipid is failed,ret=%d", ret); + return ret; + } + + ret = aw_dev_chip_init(aw_dev); + + return ret; +} + + diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_device.h b/sound/soc/codecs/aw87xxx/aw87xxx_device.h new file mode 100644 index 000000000000..7c85f80a958e --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_device.h @@ -0,0 +1,149 @@ +#ifndef __AW87XXX_DEVICE_H__ +#define __AW87XXX_DEVICE_H__ +#include +#include +#include +#include +#include "aw87xxx_acf_bin.h" + +#define AW87XXX_PID_9B_PRODUCT_MAX (1) +#define AW87XXX_PID_18_PRODUCT_MAX (1) +#define AW87XXX_PID_39_PRODUCT_MAX (3) +#define AW87XXX_PID_59_3X9_PRODUCT_MAX (2) +#define AW87XXX_PID_59_5X9_PRODUCT_MAX (4) +#define AW87XXX_PID_5A_PRODUCT_MAX (5) +#define AW87XXX_PID_76_PROFUCT_MAX (4) +#define AW87XXX_PID_60_PROFUCT_MAX (5) +#define AW_PRODUCT_NAME_LEN (8) + +#define AW_GPIO_HIGHT_LEVEL (1) +#define AW_GPIO_LOW_LEVEL (0) + +#define AW_I2C_RETRIES (5) +#define AW_I2C_RETRY_DELAY (2) +#define AW_I2C_READ_MSG_NUM (2) + +#define AW_READ_CHIPID_RETRIES (5) +#define AW_READ_CHIPID_RETRY_DELAY (2) +#define AW_DEV_REG_CHIPID (0x00) + +#define AW_DEV_REG_INVALID_MASK (0xff) + +#define AW_NO_RESET_GPIO (-1) + +#define AW_PID_9B_BIN_REG_CFG_COUNT (10) + +/******************************************** + * + * aw87xxx devices attributes + * + *******************************************/ +struct aw_device; + +struct aw_device_ops { + int (*pwr_on_func)(struct aw_device *aw_dev, struct aw_data_container *data); + int (*pwr_off_func)(struct aw_device *aw_dev, struct aw_data_container *data); +}; + +enum aw_dev_chipid { + AW_DEV_CHIPID_18 = 0x18, + AW_DEV_CHIPID_39 = 0x39, + AW_DEV_CHIPID_59 = 0x59, + AW_DEV_CHIPID_69 = 0x69, + AW_DEV_CHIPID_5A = 0x5A, + AW_DEV_CHIPID_9A = 0x9A, + AW_DEV_CHIPID_9B = 0x9B, + AW_DEV_CHIPID_76 = 0x76, + AW_DEV_CHIPID_60 = 0x60, +}; + +enum aw_dev_hw_status { + AW_DEV_HWEN_OFF = 0, + AW_DEV_HWEN_ON, + AW_DEV_HWEN_INVALID, + AW_DEV_HWEN_STATUS_MAX, +}; + +enum aw_dev_soft_off_enable { + AW_DEV_SOFT_OFF_DISENABLE = 0, + AW_DEV_SOFT_OFF_ENABLE = 1, +}; + +enum aw_dev_soft_rst_enable { + AW_DEV_SOFT_RST_DISENABLE = 0, + AW_DEV_SOFT_RST_ENABLE = 1, +}; + +enum aw_reg_receiver_mode { + AW_NOT_REC_MODE = 0, + AW_IS_REC_MODE = 1, +}; + +struct aw_mute_desc { + uint8_t addr; + uint8_t enable; + uint8_t disable; + uint16_t mask; +}; + +struct aw_soft_rst_desc { + int len; + unsigned char *access; +}; + +struct aw_esd_check_desc { + uint8_t first_update_reg_addr; + uint8_t first_update_reg_val; +}; + +struct aw_rec_mode_desc { + uint8_t addr; + uint8_t enable; + uint8_t disable; + uint8_t mask; +}; + +struct aw_device { + uint8_t i2c_addr; + uint8_t chipid; + uint8_t soft_rst_enable; + uint8_t soft_off_enable; + uint8_t is_rec_mode; + int hwen_status; + int i2c_bus; + int rst_gpio; + int reg_max_addr; + int product_cnt; + const char **product_tab; + const unsigned char *reg_access; + + struct device *dev; + struct i2c_client *i2c; + struct aw_mute_desc mute_desc; + struct aw_soft_rst_desc soft_rst_desc; + struct aw_esd_check_desc esd_desc; + struct aw_rec_mode_desc rec_desc; + + struct aw_device_ops ops; +}; + + +int aw87xxx_dev_i2c_write_byte(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t reg_data); +int aw87xxx_dev_i2c_read_byte(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t *reg_data); +int aw87xxx_dev_i2c_read_msg(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t *data_buf, uint32_t data_len); +int aw87xxx_dev_i2c_write_bits(struct aw_device *aw_dev, + uint8_t reg_addr, uint8_t mask, uint8_t reg_data); +void aw87xxx_dev_soft_reset(struct aw_device *aw_dev); +void aw87xxx_dev_hw_pwr_ctrl(struct aw_device *aw_dev, bool enable); +int aw87xxx_dev_default_pwr_on(struct aw_device *aw_dev, + struct aw_data_container *profile_data); +int aw87xxx_dev_default_pwr_off(struct aw_device *aw_dev, + struct aw_data_container *profile_data); +int aw87xxx_dev_esd_reg_status_check(struct aw_device *aw_dev); +int aw87xxx_dev_check_reg_is_rec_mode(struct aw_device *aw_dev); +int aw87xxx_dev_init(struct aw_device *aw_dev); + +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_dsp.c b/sound/soc/codecs/aw87xxx/aw87xxx_dsp.c new file mode 100644 index 000000000000..93b02e30122d --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_dsp.c @@ -0,0 +1,355 @@ +/* + * aw87xxx_dsp.c + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * Author: Barry + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx_log.h" +#include "aw87xxx_dsp.h" + +static DEFINE_MUTEX(g_dsp_lock); +static unsigned int g_spin_value = 0; + +static int g_rx_topo_id = AW_RX_DEFAULT_TOPO_ID; +static int g_rx_port_id = AW_RX_DEFAULT_PORT_ID; + +#ifdef AW_MTK_OPEN_DSP_PLATFORM +extern int mtk_spk_send_ipi_buf_to_dsp(void *data_buffer, + uint32_t data_size); +extern int mtk_spk_recv_ipi_buf_from_dsp(int8_t *buffer, + int16_t size, uint32_t *buf_len); +/* +static int mtk_spk_send_ipi_buf_to_dsp(void *data_buffer, + uint32_t data_size) +{ + AW_LOGI("enter"); + return 0; +} + +static int mtk_spk_recv_ipi_buf_from_dsp(int8_t *buffer, + int16_t size, uint32_t *buf_len) +{ + AW_LOGI("enter"); + return 0; +} +*/ +#elif defined AW_QCOM_OPEN_DSP_PLATFORM +extern int afe_get_topology(int port_id); +extern int aw_send_afe_cal_apr(uint32_t param_id, + void *buf, int cmd_size, bool write); +/* +static int afe_get_topology(int port_id) +{ + return -EPERM; +} + +static int aw_send_afe_cal_apr(uint32_t param_id, + void *buf, int cmd_size, bool write) +{ + AW_LOGI("enter, no define AWINIC_ADSP_ENABLE", __func__); + return 0; +} +*/ +#endif + +#ifdef AW_QCOM_OPEN_DSP_PLATFORM +extern void aw_set_port_id(int rx_port_id); +#else +static void aw_set_port_id(int rx_port_id) +{ + return; +} +#endif + +uint8_t aw87xxx_dsp_isEnable(void) +{ +#if (defined AW_QCOM_OPEN_DSP_PLATFORM) || (defined AW_MTK_OPEN_DSP_PLATFORM) + return true; +#else + return false; +#endif +} + +/*****************mtk dsp communication function start**********************/ +#ifdef AW_MTK_OPEN_DSP_PLATFORM +static int aw_mtk_write_data_to_dsp(int32_t param_id, + void *data, int size) +{ + int32_t *dsp_data = NULL; + mtk_dsp_hdr_t *hdr = NULL; + int ret; + + dsp_data = kzalloc(sizeof(mtk_dsp_hdr_t) + size, GFP_KERNEL); + if (!dsp_data) { + AW_LOGE("kzalloc dsp_msg error"); + return -ENOMEM; + } + + hdr = (mtk_dsp_hdr_t *)dsp_data; + hdr->type = DSP_MSG_TYPE_DATA; + hdr->opcode_id = param_id; + hdr->version = AW_DSP_MSG_HDR_VER; + + memcpy(((char *)dsp_data) + sizeof(mtk_dsp_hdr_t), + data, size); + + ret = mtk_spk_send_ipi_buf_to_dsp(dsp_data, + sizeof(mtk_dsp_hdr_t) + size); + if (ret < 0) { + AW_LOGE("write data failed"); + kfree(dsp_data); + dsp_data = NULL; + return ret; + } + + kfree(dsp_data); + dsp_data = NULL; + return 0; +} + +static int aw_mtk_read_data_from_dsp(int32_t param_id, void *data, + int data_size) +{ + int ret; + mtk_dsp_hdr_t hdr; + + mutex_lock(&g_dsp_lock); + hdr.type = DSP_MSG_TYPE_CMD; + hdr.opcode_id = param_id; + hdr.version = AW_DSP_MSG_HDR_VER; + + ret = mtk_spk_send_ipi_buf_to_dsp(&hdr, sizeof(mtk_dsp_hdr_t)); + if (ret < 0) + goto failed; + + ret = mtk_spk_recv_ipi_buf_from_dsp(data, data_size, &data_size); + if (ret < 0) + goto failed; + + mutex_unlock(&g_dsp_lock); + return 0; + +failed: + mutex_unlock(&g_dsp_lock); + return ret; +} + +#endif +/********************mtk dsp communication function end***********************/ + +/******************qcom dsp communication function start**********************/ +#ifdef AW_QCOM_OPEN_DSP_PLATFORM +static void aw_check_dsp_ready(void) +{ + int ret; + + ret = afe_get_topology(g_rx_port_id); + AW_LOGD("topo_id 0x%x", ret); + + if (ret != g_rx_topo_id) + AW_LOGE("topo id 0x%x", ret); + +} + +static int aw_qcom_write_data_to_dsp(int32_t param_id, + void *data, int data_size) +{ + int ret = 0; + + AW_LOGI("enter"); + mutex_lock(&g_dsp_lock); + aw_check_dsp_ready(); + ret = aw_send_afe_cal_apr(param_id, data, + data_size, true); + mutex_unlock(&g_dsp_lock); + return ret; +} + +static int aw_qcom_read_data_from_dsp(int32_t param_id, + void *data, int data_size) +{ + int ret = 0; + + AW_LOGI("enter"); + mutex_lock(&g_dsp_lock); + aw_check_dsp_ready(); + ret = aw_send_afe_cal_apr(param_id, data, + data_size, false); + mutex_unlock(&g_dsp_lock); + return ret; +} + +#endif +/*****************qcom dsp communication function end*********************/ + +/*****************read/write msg communication function*********************/ +static int aw_write_data_to_dsp(int32_t param_id, void *data, int data_size) +{ +#if defined AW_QCOM_OPEN_DSP_PLATFORM + return aw_qcom_write_data_to_dsp(param_id, data, data_size); +#elif defined AW_MTK_OPEN_DSP_PLATFORM + return aw_mtk_write_data_to_dsp(param_id, data, data_size); +#else + return -EINVAL; +#endif +} + +static int aw_read_data_from_dsp(int32_t param_id, void *data, int data_size) +{ +#if defined AW_QCOM_OPEN_DSP_PLATFORM + return aw_qcom_read_data_from_dsp(param_id, data, data_size); +#elif defined AW_MTK_OPEN_DSP_PLATFORM + return aw_mtk_read_data_from_dsp(param_id, data, data_size); +#else + return -EINVAL; +#endif +} + +/***************read/write msg communication function end*******************/ + +int aw87xxx_dsp_get_rx_module_enable(int *enable) +{ + if (!enable) { + AW_LOGE("enable is NULL"); + return -EINVAL; + } + + return aw_read_data_from_dsp(AWDSP_RX_SET_ENABLE, + (void *)enable, sizeof(uint32_t)); +} + +int aw87xxx_dsp_set_rx_module_enable(int enable) +{ + switch (enable) { + case AW_RX_MODULE_DISENABLE: + AW_LOGD("set enable=%d", enable); + break; + case AW_RX_MODULE_ENABLE: + AW_LOGD("set enable=%d", enable); + break; + default: + AW_LOGE("unsupport enable=%d", enable); + return -EINVAL; + } + + return aw_write_data_to_dsp(AWDSP_RX_SET_ENABLE, + &enable, sizeof(uint32_t)); +} + + +int aw87xxx_dsp_get_vmax(uint32_t *vmax, int dev_index) +{ + int32_t param_id = 0; + + switch (dev_index % AW_DSP_CHANNEL_MAX) { + case AW_DSP_CHANNEL_0: + param_id = AWDSP_RX_VMAX_0; + break; + case AW_DSP_CHANNEL_1: + param_id = AWDSP_RX_VMAX_1; + break; + default: + AW_LOGE("algo only support double PA channel:%d unsupport", + dev_index); + return -EINVAL; + } + + return aw_read_data_from_dsp(param_id, + (void *)vmax, sizeof(uint32_t)); +} + +int aw87xxx_dsp_set_vmax(uint32_t vmax, int dev_index) +{ + int32_t param_id = 0; + + switch (dev_index % AW_DSP_CHANNEL_MAX) { + case AW_DSP_CHANNEL_0: + param_id = AWDSP_RX_VMAX_0; + break; + case AW_DSP_CHANNEL_1: + param_id = AWDSP_RX_VMAX_1; + break; + default: + AW_LOGE("algo only support double PA channel:%d unsupport", + dev_index); + return -EINVAL; + } + + return aw_write_data_to_dsp(param_id, &vmax, sizeof(uint32_t)); +} + +int aw87xxx_dsp_set_spin(uint32_t ctrl_value) +{ + int ret = 0; + + if (ctrl_value >= AW_SPIN_MAX) { + AW_LOGE("spin [%d] unsupported ", ctrl_value); + return -EINVAL; + } + ret = aw_write_data_to_dsp(AW_MSG_ID_SPIN, &ctrl_value, + sizeof(uint32_t)); + if (ret) { + AW_LOGE("spin [%d] set failed ", ctrl_value); + return ret; + } + + g_spin_value = ctrl_value; + return 0; +} + +int aw87xxx_dsp_get_spin(void) +{ + return g_spin_value; +} + +int aw87xxx_spin_set_record_val(void) +{ + AW_LOGD("record write spin enter"); + + return aw87xxx_dsp_set_spin(g_spin_value); +} +EXPORT_SYMBOL(aw87xxx_spin_set_record_val); + +void aw87xxx_device_parse_topo_id_dt(struct aw_device *aw_dev) +{ + int ret; + + ret = of_property_read_u32(aw_dev->dev->of_node, "aw-rx-topo-id", &g_rx_topo_id); + if (ret < 0) { + g_rx_topo_id = AW_RX_DEFAULT_TOPO_ID; + AW_DEV_LOGI(aw_dev->dev, "read aw-rx-topo-id failed,use default"); + } + + AW_DEV_LOGI(aw_dev->dev, "rx-topo-id: 0x%x", g_rx_topo_id); +} + +void aw87xxx_device_parse_port_id_dt(struct aw_device *aw_dev) +{ + int ret; + + ret = of_property_read_u32(aw_dev->dev->of_node, "aw-rx-port-id", &g_rx_port_id); + if (ret < 0) { + g_rx_port_id = AW_RX_DEFAULT_PORT_ID; + AW_DEV_LOGI(aw_dev->dev, "read aw-rx-port-id failed,use default"); + } + + aw_set_port_id(g_rx_port_id); + AW_DEV_LOGI(aw_dev->dev, "rx-port-id: 0x%x", g_rx_port_id); +} + diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_dsp.h b/sound/soc/codecs/aw87xxx/aw87xxx_dsp.h new file mode 100644 index 000000000000..7acc4dc0dfd9 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_dsp.h @@ -0,0 +1,65 @@ +#ifndef __AW87XXX_DSP_H__ +#define __AW87XXX_DSP_H__ + +#include "aw87xxx_device.h" + +/*#define AW_MTK_OPEN_DSP_PLATFORM*/ +/*#define AW_QCOM_OPEN_DSP_PLATFORM*/ + +/*Note: The pord_ID is configured according to different platforms*/ +#define AW_DSP_SLEEP_TIME (10) + +#define AW_DSP_MSG_HDR_VER (1) + +#define AW_RX_DEFAULT_TOPO_ID (0x1000FF01) +#define AW_RX_DEFAULT_PORT_ID (0x4000) + +#define AWDSP_RX_SET_ENABLE (0x10013D11) +#define AWDSP_RX_PARAMS (0x10013D12) +#define AWDSP_RX_VMAX_0 (0X10013D17) +#define AWDSP_RX_VMAX_1 (0X10013D18) +#define AW_MSG_ID_SPIN (0x10013D2E) + +enum { + AW_SPIN_0 = 0, + AW_SPIN_90, + AW_SPIN_180, + AW_SPIN_270, + AW_SPIN_MAX, +}; + +typedef struct mtk_dsp_msg_header { + int32_t type; + int32_t opcode_id; + int32_t version; + int32_t reserver[3]; +} mtk_dsp_hdr_t; + +enum aw_rx_module_enable { + AW_RX_MODULE_DISENABLE = 0, + AW_RX_MODULE_ENABLE, +}; + +enum aw_dsp_msg_type { + DSP_MSG_TYPE_DATA = 0, + DSP_MSG_TYPE_CMD = 1, +}; + +enum aw_dsp_channel { + AW_DSP_CHANNEL_0 = 0, + AW_DSP_CHANNEL_1, + AW_DSP_CHANNEL_MAX, +}; + +uint8_t aw87xxx_dsp_isEnable(void); +int aw87xxx_dsp_get_rx_module_enable(int *enable); +int aw87xxx_dsp_set_rx_module_enable(int enable); +int aw87xxx_dsp_get_vmax(uint32_t *vmax, int channel); +int aw87xxx_dsp_set_vmax(uint32_t vmax, int channel); +int aw87xxx_dsp_set_spin(uint32_t ctrl_value); +int aw87xxx_dsp_get_spin(void); +int aw87xxx_spin_set_record_val(void); +void aw87xxx_device_parse_port_id_dt(struct aw_device *aw_dev); +void aw87xxx_device_parse_topo_id_dt(struct aw_device *aw_dev); + +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_log.h b/sound/soc/codecs/aw87xxx/aw87xxx_log.h new file mode 100644 index 000000000000..b3bde38a23c6 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_log.h @@ -0,0 +1,33 @@ +#ifndef __AW87XXX_LOG_H__ +#define __AW87XXX_LOG_H__ + +#include + + +/******************************************** + * + * print information control + * + *******************************************/ +#define AW_LOGI(fmt, ...)\ + pr_info("[Awinic] %s:" fmt "\n", __func__, ##__VA_ARGS__) + +#define AW_LOGD(fmt, ...)\ + pr_debug("[Awinic] %s:" fmt "\n", __func__, ##__VA_ARGS__) + +#define AW_LOGE(fmt, ...)\ + pr_err("[Awinic] %s:" fmt "\n", __func__, ##__VA_ARGS__) + + +#define AW_DEV_LOGI(dev, fmt, ...)\ + pr_info("[Awinic] [%s]%s: " fmt "\n", dev_name(dev), __func__, ##__VA_ARGS__) + +#define AW_DEV_LOGD(dev, fmt, ...)\ + pr_debug("[Awinic] [%s]%s: " fmt "\n", dev_name(dev), __func__, ##__VA_ARGS__) + +#define AW_DEV_LOGE(dev, fmt, ...)\ + pr_err("[Awinic] [%s]%s: " fmt "\n", dev_name(dev), __func__, ##__VA_ARGS__) + + + +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_monitor.c b/sound/soc/codecs/aw87xxx/aw87xxx_monitor.c new file mode 100644 index 000000000000..61adba6b6169 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_monitor.c @@ -0,0 +1,1207 @@ +/* + * aw87xxx_monitor.c + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * Author: Barry + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "aw87xxx.h" +#include "aw87xxx_log.h" +#include "aw87xxx_monitor.h" +#include "aw87xxx_dsp.h" +#include "aw87xxx_bin_parse.h" +#include "aw87xxx_device.h" + +#define AW_MONITOT_BIN_PARSE_VERSION "V0.1.0" + +#define AW_GET_32_DATA(w, x, y, z) \ + ((uint32_t)((((uint8_t)w) << 24) | (((uint8_t)x) << 16) | \ + (((uint8_t)y) << 8) | ((uint8_t)z))) + +/**************************************************************************** + * + * aw87xxx monitor bin check + * + ****************************************************************************/ +static int aw_monitor_check_header_v_1_0_0(struct device *dev, + char *data, uint32_t data_len) +{ + int i = 0; + struct aw_bin_header *header = (struct aw_bin_header *)data; + + if (header->bin_data_type != DATA_TYPE_MONITOR_ANALOG) { + AW_DEV_LOGE(dev, "monitor data_type check error!"); + return -EINVAL; + } + + if (header->bin_data_size != AW_MONITOR_HDR_DATA_SIZE) { + AW_DEV_LOGE(dev, "monitor data_size error!"); + return -EINVAL; + } + + if (header->data_byte_len != AW_MONITOR_HDR_DATA_BYTE_LEN) { + AW_DEV_LOGE(dev, "monitor data_byte_len error!"); + return -EINVAL; + } + + for (i = 0; i < AW_MONITOR_DATA_VER_MAX; i++) { + if (header->bin_data_ver == i) { + AW_LOGD("monitor bin_data_ver[0x%x]", i); + break; + } + } + if (i == AW_MONITOR_DATA_VER_MAX) + return -EINVAL; + + return 0; +} + +static int aw_monitor_check_data_v1_size(struct device *dev, + char *data, int32_t data_len) +{ + int32_t bin_header_len = sizeof(struct aw_bin_header); + int32_t monitor_header_len = sizeof(struct aw_monitor_header); + int32_t monitor_data_len = sizeof(struct vmax_step_config); + int32_t len = 0; + struct aw_monitor_header *monitor_header = NULL; + + AW_DEV_LOGD(dev, "enter"); + + if (data_len < bin_header_len + monitor_header_len) { + AW_DEV_LOGE(dev, "bin len is less than aw_bin_header and monitoor_header,check failed"); + return -EINVAL; + } + + monitor_header = (struct aw_monitor_header *)(data + bin_header_len); + len = data_len - bin_header_len - monitor_header_len; + if (len < monitor_header->step_count * monitor_data_len) { + AW_DEV_LOGE(dev, "bin data len is not enough,check failed"); + return -EINVAL; + } + + AW_DEV_LOGD(dev, "succeed"); + + return 0; +} + +static int aw_monitor_check_data_size(struct device *dev, + char *data, int32_t data_len) +{ + int ret = -1; + struct aw_bin_header *header = (struct aw_bin_header *)data; + + switch (header->bin_data_ver) { + case AW_MONITOR_DATA_VER: + ret = aw_monitor_check_data_v1_size(dev, data, data_len); + if (ret < 0) + return ret; + break; + default: + AW_DEV_LOGE(dev, "bin data_ver[0x%x] non support", + header->bin_data_ver); + return -EINVAL; + } + + return 0; +} + + +static int aw_monitor_check_bin_header(struct device *dev, + char *data, int32_t data_len) +{ + int ret = -1; + struct aw_bin_header *header = NULL; + + if (data_len < sizeof(struct aw_bin_header)) { + AW_DEV_LOGE(dev, "bin len is less than aw_bin_header,check failed"); + return -EINVAL; + } + header = (struct aw_bin_header *)data; + + switch (header->header_ver) { + case HEADER_VERSION_1_0_0: + ret = aw_monitor_check_header_v_1_0_0(dev, data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "monitor bin haeder info check error!"); + return ret; + } + break; + default: + AW_DEV_LOGE(dev, "bin version[0x%x] non support", + header->header_ver); + return -EINVAL; + } + + return 0; +} + +static int aw_monitor_bin_check_sum(struct device *dev, + char *data, int32_t data_len) +{ + int i, data_sum = 0; + uint32_t *check_sum = (uint32_t *)data; + + for (i = 4; i < data_len; i++) + data_sum += data[i]; + + if (*check_sum != data_sum) { + AW_DEV_LOGE(dev, "check_sum[%d] is not equal to data_sum[%d]", + *check_sum, data_sum); + return -ENOMEM; + } + + AW_DEV_LOGD(dev, "succeed"); + + return 0; +} + +static int aw_monitor_bin_check(struct device *dev, + char *monitor_data, uint32_t data_len) +{ + int ret = -1; + + if (monitor_data == NULL || data_len == 0) { + AW_DEV_LOGE(dev, "none data to parse"); + return -EINVAL; + } + + ret = aw_monitor_bin_check_sum(dev, monitor_data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "bin data check sum failed"); + return ret; + } + + ret = aw_monitor_check_bin_header(dev, monitor_data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "bin data len check failed"); + return ret; + } + + ret = aw_monitor_check_data_size(dev, monitor_data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "bin header info check failed"); + return ret; + } + + return 0; +} + +/***************************************************************************** + * + * aw87xxx monitor header bin parse + * + *****************************************************************************/ +static void aw_monitor_write_to_table_v1(struct device *dev, + struct vmax_step_config *vmax_step, + char *vmax_data, uint32_t step_count) +{ + int i = 0; + int index = 0; + int vmax_step_size = (int)sizeof(struct vmax_step_config); + + for (i = 0; i < step_count; i++) { + index = vmax_step_size * i; + vmax_step[i].vbat_min = + AW_GET_32_DATA(vmax_data[index + 3], + vmax_data[index + 2], + vmax_data[index + 1], + vmax_data[index + 0]); + vmax_step[i].vbat_max = + AW_GET_32_DATA(vmax_data[index + 7], + vmax_data[index + 6], + vmax_data[index + 5], + vmax_data[index + 4]); + vmax_step[i].vmax_vol = + AW_GET_32_DATA(vmax_data[index + 11], + vmax_data[index + 10], + vmax_data[index + 9], + vmax_data[index + 8]); + } + + for (i = 0; i < step_count; i++) + AW_DEV_LOGI(dev, "vbat_min:%d, vbat_max%d, vmax_vol:0x%x", + vmax_step[i].vbat_min, + vmax_step[i].vbat_max, + vmax_step[i].vmax_vol); +} + +static int aw_monitor_parse_vol_data_v1(struct device *dev, + struct aw_monitor *monitor, char *monitor_data) +{ + uint32_t step_count = 0; + char *vmax_data = NULL; + struct vmax_step_config *vmax_step = NULL; + + AW_DEV_LOGD(dev, "enter"); + + step_count = monitor->monitor_hdr.step_count; + if (step_count) { + vmax_step = devm_kzalloc(dev, sizeof(struct vmax_step_config) * step_count, + GFP_KERNEL); + if (vmax_step == NULL) { + AW_DEV_LOGE(dev, "vmax_cfg vmalloc failed"); + return -ENOMEM; + } + memset(vmax_step, 0, + sizeof(struct vmax_step_config) * step_count); + } + + vmax_data = monitor_data + sizeof(struct aw_bin_header) + + sizeof(struct aw_monitor_header); + aw_monitor_write_to_table_v1(dev, vmax_step, vmax_data, step_count); + monitor->vmax_cfg = vmax_step; + + AW_DEV_LOGI(dev, "vmax_data parse succeed"); + + return 0; +} + +static int aw_monitor_parse_data_v1(struct device *dev, + struct aw_monitor *monitor, char *monitor_data) +{ + int ret = -1; + int header_len = 0; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + header_len = sizeof(struct aw_bin_header); + memcpy(monitor_hdr, monitor_data + header_len, + sizeof(struct aw_monitor_header)); + + AW_DEV_LOGI(dev, "monitor_switch:%d, monitor_time:%d (ms), monitor_count:%d, step_count:%d", + monitor_hdr->monitor_switch, monitor_hdr->monitor_time, + monitor_hdr->monitor_count, monitor_hdr->step_count); + + ret = aw_monitor_parse_vol_data_v1(dev, monitor, monitor_data); + if (ret < 0) { + AW_DEV_LOGE(dev, "vmax_data parse failed"); + return ret; + } + + monitor->bin_status = AW_MONITOR_CFG_OK; + + return 0; +} + + +static int aw_monitor_parse_v_1_0_0(struct device *dev, + struct aw_monitor *monitor, char *monitor_data) +{ + int ret = -1; + struct aw_bin_header *header = (struct aw_bin_header *)monitor_data; + + switch (header->bin_data_ver) { + case AW_MONITOR_DATA_VER: + ret = aw_monitor_parse_data_v1(dev, monitor, monitor_data); + if (ret < 0) + return ret; + break; + default: + return -EINVAL; + } + + return 0; +} + +void aw87xxx_monitor_cfg_free(struct aw_monitor *monitor) +{ + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + + monitor->bin_status = AW_MONITOR_CFG_WAIT; + memset(&monitor->monitor_hdr, 0, + sizeof(struct aw_monitor_header)); + if (monitor->vmax_cfg) { + devm_kfree(aw87xxx->dev, monitor->vmax_cfg); + monitor->vmax_cfg = NULL; + } +} + +int aw87xxx_monitor_bin_parse(struct device *dev, + char *monitor_data, uint32_t data_len) +{ + int ret = -1; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = NULL; + struct aw_bin_header *bin_header = NULL; + + if (aw87xxx == NULL) { + AW_DEV_LOGE(dev, "get struct aw87xxx failed"); + return -EINVAL; + } + + monitor = &aw87xxx->monitor; + monitor->bin_status = AW_MONITOR_CFG_WAIT; + + AW_DEV_LOGI(dev, "monitor bin parse version: %s", + AW_MONITOT_BIN_PARSE_VERSION); + + ret = aw_monitor_bin_check(dev, monitor_data, data_len); + if (ret < 0) { + AW_DEV_LOGE(dev, "monitor bin check failed"); + return ret; + } + + bin_header = (struct aw_bin_header *)monitor_data; + switch (bin_header->bin_data_ver) { + case DATA_VERSION_V1: + ret = aw_monitor_parse_v_1_0_0(dev, monitor, + monitor_data); + if (ret < 0) { + aw87xxx_monitor_cfg_free(monitor); + return ret; + } + break; + default: + AW_DEV_LOGE(dev, "Unrecognized this bin data version[0x%x]", + bin_header->bin_data_ver); + } + + return 0; +} + +/*************************************************************************** + * + * aw87xxx monitor get adjustment vmax of power + * + ***************************************************************************/ +static int aw_monitor_get_battery_capacity(struct device *dev, + struct aw_monitor *monitor, int *vbat_capacity) +{ + char name[] = "battery"; + int ret = -1; + union power_supply_propval prop = { 0 }; + struct power_supply *psy = NULL; + + psy = power_supply_get_by_name(name); + if (psy == NULL) { + AW_DEV_LOGE(dev, "no struct power supply name:%s", name); + return -EINVAL; + } + + ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CAPACITY, &prop); + if (ret < 0) { + AW_DEV_LOGE(dev, "get vbat capacity failed"); + return -EINVAL; + } + *vbat_capacity = prop.intval; + AW_DEV_LOGI(dev, "The percentage is %d", + *vbat_capacity); + + return 0; +} + +static int aw_search_vmax_from_table(struct device *dev, + struct aw_monitor *monitor, + const int vbat_vol, int *vmax_vol) +{ + int i = 0; + int vmax_set = 0; + uint32_t vmax_flag = 0; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + struct vmax_step_config *vmax_cfg = monitor->vmax_cfg; + + if (monitor->bin_status == AW_MONITOR_CFG_WAIT) { + AW_DEV_LOGE(dev, "vmax_cfg not loaded or parse failed"); + return -ENODATA; + } + + for (i = 0; i < monitor_hdr->step_count; i++) { + if (vbat_vol == AW_VBAT_MAX) { + vmax_set = AW_VMAX_MAX; + vmax_flag = 1; + AW_DEV_LOGD(dev, "vbat=%d, setting vmax=0x%x", + vbat_vol, vmax_set); + break; + } + + if (vbat_vol >= vmax_cfg[i].vbat_min && + vbat_vol < vmax_cfg[i].vbat_max) { + vmax_set = vmax_cfg[i].vmax_vol; + vmax_flag = 1; + AW_DEV_LOGD(dev, "read setting vmax=0x%x, step[%d]: vbat_min=%d,vbat_max=%d", + vmax_set, i, + vmax_cfg[i].vbat_min, + vmax_cfg[i].vbat_max); + break; + } + } + + if (!vmax_flag) { + AW_DEV_LOGE(dev, "vmax_cfg not found"); + return -ENODATA; + } + + *vmax_vol = vmax_set; + return 0; +} + + +/*************************************************************************** + * + *monitor_esd_func + * + ***************************************************************************/ +static int aw_chip_status_recover(struct aw87xxx *aw87xxx) +{ + int ret = -1; + struct aw_monitor *monitor = &aw87xxx->monitor; + char *profile = aw87xxx->current_profile; + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + + ret = aw87xxx_update_profile_esd(aw87xxx, profile); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "load profile[%s] failed ", + profile); + return ret; + } + + AW_DEV_LOGI(aw87xxx->dev, "current prof[%s], dev_index[%d] ", + profile, aw87xxx->dev_index); + + monitor->pre_vmax = AW_VMAX_INIT_VAL; + monitor->first_entry = AW_FIRST_ENTRY; + monitor->timer_cnt = 0; + monitor->vbat_sum = 0; + + return 0; +} + +static int aw_monitor_chip_esd_check_work(struct aw87xxx *aw87xxx) +{ + int ret = 0; + int i = 0; + + for (i = 0; i < REG_STATUS_CHECK_MAX; i++) { + AW_DEV_LOGD(aw87xxx->dev, "reg_status_check[%d]", i); + + ret = aw87xxx_dev_esd_reg_status_check(&aw87xxx->aw_dev); + if (ret < 0) { + aw_chip_status_recover(aw87xxx); + } else { + AW_DEV_LOGD(aw87xxx->dev, "chip status check succeed"); + break; + } + msleep(AW_ESD_CHECK_DELAY); + } + + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "chip status recover failed,chip off"); + aw87xxx_update_profile_esd(aw87xxx, aw87xxx->prof_off_name); + return ret; + } + + return 0; +} + + +/*************************************************************************** + * + * aw87xxx monitor work with dsp + * + ***************************************************************************/ +static int aw_monitor_update_vmax_to_dsp(struct device *dev, + struct aw_monitor *monitor, int vmax_set) +{ + int ret = -1; + uint32_t enable = 0; + + if (monitor->pre_vmax != vmax_set) { + ret = aw87xxx_dsp_get_rx_module_enable(&enable); + if (!enable || ret < 0) { + AW_DEV_LOGE(dev, "get rx failed or rx disable, ret=%d, enable=%d", + ret, enable); + return -EPERM; + } + + ret = aw87xxx_dsp_set_vmax(vmax_set, monitor->dev_index); + if (ret) { + AW_DEV_LOGE(dev, "set dsp msg fail, ret=%d", ret); + return ret; + } + + AW_DEV_LOGI(dev, "set dsp vmax=0x%x sucess", vmax_set); + monitor->pre_vmax = vmax_set; + } else { + AW_DEV_LOGI(dev, "vmax=0x%x no change", vmax_set); + } + + return 0; +} + +static void aw_monitor_with_dsp_vmax_work(struct device *dev, + struct aw_monitor *monitor) +{ + int ret = -1; + int vmax_set = 0; + int vbat_capacity = 0; + int ave_capacity = 0; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + AW_DEV_LOGD(dev, "enter with dsp monitor"); + + ret = aw_monitor_get_battery_capacity(dev, monitor, &vbat_capacity); + if (ret < 0) + return; + + if (monitor->timer_cnt < monitor_hdr->monitor_count) { + monitor->timer_cnt++; + monitor->vbat_sum += vbat_capacity; + AW_DEV_LOGI(dev, "timer_cnt = %d", + monitor->timer_cnt); + } + if ((monitor->timer_cnt >= monitor_hdr->monitor_count) || + (monitor->first_entry == AW_FIRST_ENTRY)) { + if (monitor->first_entry == AW_FIRST_ENTRY) + monitor->first_entry = AW_NOT_FIRST_ENTRY; + ave_capacity = monitor->vbat_sum / monitor->timer_cnt; + + if (monitor->custom_capacity) + ave_capacity = monitor->custom_capacity; + + AW_DEV_LOGI(dev, "get average capacity = %d", ave_capacity); + + ret = aw_search_vmax_from_table(dev, monitor, + ave_capacity, &vmax_set); + if (ret < 0) + AW_DEV_LOGE(dev, "not find vmax_vol"); + else + aw_monitor_update_vmax_to_dsp(dev, monitor, vmax_set); + + monitor->timer_cnt = 0; + monitor->vbat_sum = 0; + } +} + +static void aw_monitor_work_func(struct work_struct *work) +{ + int ret = 0; + struct aw87xxx *aw87xxx = container_of(work, + struct aw87xxx, monitor.with_dsp_work.work); + struct device *dev = aw87xxx->dev; + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + AW_DEV_LOGD(dev, "enter"); + + if (monitor->esd_enable) { + ret = aw_monitor_chip_esd_check_work(aw87xxx); + if (ret < 0) + return; + } + + if (monitor_hdr->monitor_switch && !(aw87xxx->aw_dev.is_rec_mode) && + monitor->open_dsp_en && monitor->bin_status == AW_ACF_UPDATE) { + AW_DEV_LOGD(dev, "start low power protection"); + aw_monitor_with_dsp_vmax_work(dev, monitor); + } + + if (monitor->esd_enable || (monitor_hdr->monitor_switch && + !(aw87xxx->aw_dev.is_rec_mode) && monitor->open_dsp_en && + monitor->bin_status == AW_ACF_UPDATE)) { + schedule_delayed_work(&monitor->with_dsp_work, + msecs_to_jiffies(monitor_hdr->monitor_time)); + } +} + +void aw87xxx_monitor_stop(struct aw_monitor *monitor) +{ + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + cancel_delayed_work_sync(&monitor->with_dsp_work); +} + +void aw87xxx_monitor_start(struct aw_monitor *monitor) +{ + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + int ret = 0; + + ret = aw87xxx_dev_check_reg_is_rec_mode(&aw87xxx->aw_dev); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "get reg current mode failed"); + return; + } + + if (monitor->esd_enable || (monitor->monitor_hdr.monitor_switch && + !(aw87xxx->aw_dev.is_rec_mode) && monitor->open_dsp_en + && monitor->bin_status == AW_ACF_UPDATE)) { + + AW_DEV_LOGD(aw87xxx->dev, "enter"); + monitor->pre_vmax = AW_VMAX_INIT_VAL; + monitor->first_entry = AW_FIRST_ENTRY; + monitor->timer_cnt = 0; + monitor->vbat_sum = 0; + + schedule_delayed_work(&monitor->with_dsp_work, + msecs_to_jiffies(monitor->monitor_hdr.monitor_time)); + } +} +/*************************************************************************** + * + * aw87xxx no dsp monitor func + * + ***************************************************************************/ +int aw87xxx_monitor_no_dsp_get_vmax(struct aw_monitor *monitor, int32_t *vmax) +{ + int vbat_capacity = 0; + int ret = -1; + int vmax_vol = 0; + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + struct device *dev = aw87xxx->dev; + + ret = aw_monitor_get_battery_capacity(dev, monitor, &vbat_capacity); + if (ret < 0) + return ret; + + if (monitor->custom_capacity) + vbat_capacity = monitor->custom_capacity; + AW_DEV_LOGI(dev, "get_battery_capacity is[%d]", vbat_capacity); + + ret = aw_search_vmax_from_table(dev, monitor, + vbat_capacity, &vmax_vol); + if (ret < 0) { + AW_DEV_LOGE(dev, "not find vmax_vol"); + return ret; + } + + *vmax = vmax_vol; + return 0; +} + + +/*************************************************************************** + * + * aw87xxx monitor sysfs nodes + * + ***************************************************************************/ +static ssize_t aw_attr_get_esd_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + if (monitor->esd_enable) { + AW_DEV_LOGI(aw87xxx->dev, "esd-enable=true"); + len += snprintf(buf + len, PAGE_SIZE - len, + "esd-enable=true\n"); + } else { + AW_DEV_LOGI(aw87xxx->dev, "esd-enable=false"); + len += snprintf(buf + len, PAGE_SIZE - len, + "esd-enable=false\n"); + } + + return len; +} + +static ssize_t aw_attr_set_esd_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + char esd_enable[AW_ESD_ENABLE_STRLEN] = {0}; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + if (strlen(buf) > AW_ESD_ENABLE_STRLEN) { + AW_DEV_LOGE(aw87xxx->dev, "input esd_enable_str_len is out of max[%d]", + AW_ESD_ENABLE_STRLEN); + return -EINVAL; + } + + if (sscanf(buf, "%s", esd_enable) == 1) { + AW_DEV_LOGD(aw87xxx->dev, "input esd-enable=[%s]", esd_enable); + if (!strcmp(esd_enable, "true")) + monitor->esd_enable = AW_ESD_ENABLE; + else + monitor->esd_enable = AW_ESD_DISABLE; + AW_DEV_LOGI(dev, "set esd-enable=[%s]", + monitor->esd_enable ? "true" : "false"); + } else { + AW_DEV_LOGE(aw87xxx->dev, "input esd-enable error"); + return -EINVAL; + } + + return len; +} + +static ssize_t aw_attr_get_vbat(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int ret = -1; + int vbat_capacity = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + if (monitor->custom_capacity == 0) { + ret = aw_monitor_get_battery_capacity(dev, monitor, + &vbat_capacity); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "get battery_capacity failed"); + return ret; + } + len += snprintf(buf + len, PAGE_SIZE - len, + "vbat capacity=%d\n", vbat_capacity); + } else { + len += snprintf(buf + len, PAGE_SIZE - len, + "vbat capacity=%d\n", + monitor->custom_capacity); + } + + return len; +} + +static ssize_t aw_attr_set_vbat(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + int ret = -1; + int capacity = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + ret = kstrtouint(buf, 0, &capacity); + if (ret < 0) + return ret; + AW_DEV_LOGI(aw87xxx->dev, "set capacity = %d", capacity); + if (capacity >= AW_VBAT_CAPACITY_MIN && + capacity <= AW_VBAT_CAPACITY_MAX){ + monitor->custom_capacity = capacity; + } else { + AW_DEV_LOGE(aw87xxx->dev, "vbat_set=invalid,please input value [%d-%d]", + AW_VBAT_CAPACITY_MIN, AW_VBAT_CAPACITY_MAX); + return -EINVAL; + } + + return len; +} + +static ssize_t aw_attr_get_vmax(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int ret = -1; + int vbat_capacity = 0; + int vmax_get = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + if (monitor->open_dsp_en) { + ret = aw87xxx_dsp_get_vmax(&vmax_get, aw87xxx->dev_index); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, + "get dsp vmax fail, ret=%d", ret); + return ret; + } + len += snprintf(buf + len, PAGE_SIZE - len, + "get_vmax=%d\n", vmax_get); + } else { + ret = aw_monitor_get_battery_capacity(dev, monitor, + &vbat_capacity); + if (ret < 0) + return ret; + AW_DEV_LOGI(aw87xxx->dev, "get_battery_capacity is [%d]", + vbat_capacity); + + if (monitor->custom_capacity) { + vbat_capacity = monitor->custom_capacity; + AW_DEV_LOGI(aw87xxx->dev, "get custom_capacity is [%d]", + vbat_capacity); + } + + ret = aw_search_vmax_from_table(aw87xxx->dev, monitor, + vbat_capacity, &vmax_get); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "not find vmax_vol"); + len += snprintf(buf + len, PAGE_SIZE - len, + "not_find_vmax_vol\n"); + return len; + } + len += snprintf(buf + len, PAGE_SIZE - len, + "0x%x\n", vmax_get); + AW_DEV_LOGI(aw87xxx->dev, "0x%x", vmax_get); + } + + return len; +} + +static ssize_t aw_attr_set_vmax(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + uint32_t vmax_set = 0; + int ret = -1; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + ret = kstrtouint(buf, 0, &vmax_set); + if (ret < 0) + return ret; + + AW_DEV_LOGI(aw87xxx->dev, "vmax_set=0x%x", vmax_set); + + if (monitor->open_dsp_en) { + ret = aw87xxx_dsp_set_vmax(vmax_set, aw87xxx->dev_index); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "send dsp_msg error, ret = %d", + ret); + return ret; + } + msleep(2); + } else { + AW_DEV_LOGE(aw87xxx->dev, "no_dsp system,vmax_set invalid"); + return -EINVAL; + } + + return count; +} + +static ssize_t aw_attr_get_monitor_switch(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + len += snprintf(buf + len, PAGE_SIZE - len, + "aw87xxx monitor switch: %u\n", + monitor_hdr->monitor_switch); + return len; +} + + +int aw87xxx_dev_monitor_switch_set(struct aw_monitor *monitor, uint32_t enable) +{ + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + AW_DEV_LOGI(aw87xxx->dev, "monitor switch set =%d", enable); + + if (!monitor->bin_status) { + AW_DEV_LOGE(aw87xxx->dev, "bin parse faile or not loaded,set invalid"); + return -EINVAL; + } + + if (monitor_hdr->monitor_switch == enable) + return 0; + + if (enable > 0) { + monitor_hdr->monitor_switch = 1; + if (monitor->open_dsp_en) { + monitor->pre_vmax = AW_VMAX_INIT_VAL; + monitor->first_entry = AW_FIRST_ENTRY; + monitor->timer_cnt = 0; + monitor->vbat_sum = 0; + } + } else { + monitor_hdr->monitor_switch = 0; + } + + return 0; +} + +static ssize_t aw_attr_set_monitor_switch(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + uint32_t enable = 0; + int ret = -1; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + + ret = kstrtouint(buf, 0, &enable); + if (ret < 0) + return ret; + + ret = aw87xxx_dev_monitor_switch_set(monitor, enable); + if (ret) + return ret; + + return count; +} + +static ssize_t aw_attr_get_monitor_time(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + len += snprintf(buf + len, PAGE_SIZE - len, + "aw_monitor_timer = %u(ms)\n", + monitor_hdr->monitor_time); + return len; +} + +static ssize_t aw_attr_set_monitor_time(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int timer_val = 0; + int ret = -1; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + ret = kstrtouint(buf, 0, &timer_val); + if (ret < 0) + return ret; + + AW_DEV_LOGI(aw87xxx->dev, "input monitor timer=%d(ms)", timer_val); + + if (!monitor->bin_status) { + AW_DEV_LOGE(aw87xxx->dev, "bin parse faile or not loaded,set invalid"); + return -EINVAL; + } + + if (timer_val != monitor_hdr->monitor_time) + monitor_hdr->monitor_time = timer_val; + else + AW_DEV_LOGI(aw87xxx->dev, "no_change monitor_time"); + + return count; +} + +static ssize_t aw_attr_get_monitor_count(struct device *dev, + struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + len += snprintf(buf + len, PAGE_SIZE - len, + "aw_monitor_count = %u\n", + monitor_hdr->monitor_count); + return len; +} + +static ssize_t aw_attr_set_monitor_count(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + unsigned int monitor_count = 0; + int ret = -1; + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + struct aw_monitor_header *monitor_hdr = &monitor->monitor_hdr; + + ret = kstrtouint(buf, 0, &monitor_count); + if (ret < 0) + return ret; + AW_DEV_LOGI(aw87xxx->dev, "input monitor count=%d", monitor_count); + + if (!monitor->bin_status) { + AW_DEV_LOGE(aw87xxx->dev, "bin parse faile or not loaded,set invalid"); + return -EINVAL; + } + + if (monitor_count != monitor_hdr->monitor_count) + monitor_hdr->monitor_count = monitor_count; + else + AW_DEV_LOGI(aw87xxx->dev, "no_change monitor_count"); + + return count; +} + + +static ssize_t aw_attr_get_rx(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + ssize_t len = 0; + int ret = -1; + uint32_t enable = 0; + + if (monitor->open_dsp_en) { + ret = aw87xxx_dsp_get_rx_module_enable(&enable); + if (ret) { + AW_DEV_LOGE(aw87xxx->dev, "dsp_msg error, ret=%d", ret); + return ret; + } + len += snprintf(buf + len, PAGE_SIZE - len, + "aw87xxx rx: %u\n", enable); + } else { + len += snprintf(buf + len, PAGE_SIZE - len, + "command is invalid\n"); + } + + return len; +} + +static ssize_t aw_attr_set_rx(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct aw87xxx *aw87xxx = dev_get_drvdata(dev); + struct aw_monitor *monitor = &aw87xxx->monitor; + int ret = -1; + uint32_t enable; + + ret = kstrtouint(buf, 0, &enable); + if (ret < 0) + return ret; + + if (monitor->open_dsp_en) { + AW_DEV_LOGI(aw87xxx->dev, "set rx enable=%d", enable); + + ret = aw87xxx_dsp_set_rx_module_enable(enable); + if (ret < 0) { + AW_DEV_LOGE(aw87xxx->dev, "dsp_msg error, ret=%d", + ret); + return ret; + } + } else { + AW_DEV_LOGE(aw87xxx->dev, "command is invalid"); + return -EINVAL; + } + + return count; +} + + +static DEVICE_ATTR(esd_enable, S_IWUSR | S_IRUGO, + aw_attr_get_esd_enable, aw_attr_set_esd_enable); +static DEVICE_ATTR(vbat, S_IWUSR | S_IRUGO, + aw_attr_get_vbat, aw_attr_set_vbat); +static DEVICE_ATTR(vmax, S_IWUSR | S_IRUGO, + aw_attr_get_vmax, aw_attr_set_vmax); + +static DEVICE_ATTR(monitor_switch, S_IWUSR | S_IRUGO, + aw_attr_get_monitor_switch, aw_attr_set_monitor_switch); +static DEVICE_ATTR(monitor_time, S_IWUSR | S_IRUGO, + aw_attr_get_monitor_time, aw_attr_set_monitor_time); +static DEVICE_ATTR(monitor_count, S_IWUSR | S_IRUGO, + aw_attr_get_monitor_count, aw_attr_set_monitor_count); +static DEVICE_ATTR(rx, S_IWUSR | S_IRUGO, + aw_attr_get_rx, aw_attr_set_rx); + +static struct attribute *aw_monitor_vol_adjust[] = { + &dev_attr_esd_enable.attr, + &dev_attr_vbat.attr, + &dev_attr_vmax.attr, + NULL +}; + +static struct attribute_group aw_monitor_vol_adjust_group = { + .attrs = aw_monitor_vol_adjust, +}; + +static struct attribute *aw_monitor_control[] = { + &dev_attr_monitor_switch.attr, + &dev_attr_monitor_time.attr, + &dev_attr_monitor_count.attr, + &dev_attr_rx.attr, + NULL +}; + +static struct attribute_group aw_monitor_control_group = { + .attrs = aw_monitor_control, +}; + +/*************************************************************************** + * + * aw87xxx monitor init + * + ***************************************************************************/ +static void aw_monitor_dtsi_parse(struct device *dev, + struct aw_monitor *monitor, + struct device_node *dev_node) +{ + int ret = -1; + const char *esd_enable; + + ret = of_property_read_string(dev_node, "esd-enable", &esd_enable); + if (ret < 0) { + AW_DEV_LOGI(dev, "esd_enable parse failed, user default[disable]"); + monitor->esd_enable = AW_ESD_DISABLE; + } else { + if (!strcmp(esd_enable, "true")) + monitor->esd_enable = AW_ESD_ENABLE; + else + monitor->esd_enable = AW_ESD_DISABLE; + + AW_DEV_LOGI(dev, "parse esd-enable=[%s]", + monitor->esd_enable ? "true" : "false"); + } +} + +void aw87xxx_monitor_init(struct device *dev, struct aw_monitor *monitor, + struct device_node *dev_node) +{ + int ret = -1; + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + + monitor->dev_index = aw87xxx->dev_index; + monitor->monitor_hdr.monitor_time = AW_DEFAULT_MONITOR_TIME; + + aw_monitor_dtsi_parse(dev, monitor, dev_node); + + /* get platform open dsp type */ + monitor->open_dsp_en = aw87xxx_dsp_isEnable(); + + ret = sysfs_create_group(&dev->kobj, &aw_monitor_vol_adjust_group); + if (ret < 0) + AW_DEV_LOGE(dev, "failed to create monitor vol_adjust sysfs nodes"); + + INIT_DELAYED_WORK(&monitor->with_dsp_work, aw_monitor_work_func); + + if (monitor->open_dsp_en) { + ret = sysfs_create_group(&dev->kobj, &aw_monitor_control_group); + if (ret < 0) + AW_DEV_LOGE(dev, "failed to create monitor dsp control sysfs nodes"); + } + + if (!ret) + AW_DEV_LOGI(dev, "monitor init succeed"); +} + +void aw87xxx_monitor_exit(struct aw_monitor *monitor) +{ + struct aw87xxx *aw87xxx = + container_of(monitor, struct aw87xxx, monitor); + /*rm attr node*/ + sysfs_remove_group(&aw87xxx->dev->kobj, + &aw_monitor_vol_adjust_group); + + aw87xxx_monitor_stop(monitor); + + if (monitor->open_dsp_en) { + sysfs_remove_group(&aw87xxx->dev->kobj, + &aw_monitor_control_group); + } +} + diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_monitor.h b/sound/soc/codecs/aw87xxx/aw87xxx_monitor.h new file mode 100644 index 000000000000..daf9f2bfa09f --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_monitor.h @@ -0,0 +1,96 @@ +#ifndef __AW87XXX_MONITOR_H__ +#define __AW87XXX_MONITOR_H__ + +#define AW_WAIT_DSP_OPEN_TIME (3000) +#define AW_VBAT_CAPACITY_MIN (0) +#define AW_VBAT_CAPACITY_MAX (100) +#define AW_VMAX_INIT_VAL (0xFFFFFFFF) +#define AW_VBAT_MAX (100) +#define AW_VMAX_MAX (0) +#define AW_DEFAULT_MONITOR_TIME (3000) +#define AW_WAIT_TIME (3000) +#define REG_STATUS_CHECK_MAX (10) +#define AW_ESD_CHECK_DELAY (1) + +#define AW_ESD_ENABLE (true) +#define AW_ESD_DISABLE (false) +#define AW_ESD_ENABLE_STRLEN (16) + +enum aw_monitor_init { + AW_MONITOR_CFG_WAIT = 0, + AW_MONITOR_CFG_OK = 1, +}; + +enum aw_monitor_hdr_info { + AW_MONITOR_HDR_DATA_SIZE = 0x00000004, + AW_MONITOR_HDR_DATA_BYTE_LEN = 0x00000004, +}; + +enum aw_monitor_data_ver { + AW_MONITOR_DATA_VER = 0x00000001, + AW_MONITOR_DATA_VER_MAX, +}; + +enum aw_monitor_first_enter { + AW_FIRST_ENTRY = 0, + AW_NOT_FIRST_ENTRY = 1, +}; + +struct aw_bin_header { + uint32_t check_sum; + uint32_t header_ver; + uint32_t bin_data_type; + uint32_t bin_data_ver; + uint32_t bin_data_size; + uint32_t ui_ver; + char product[8]; + uint32_t addr_byte_len; + uint32_t data_byte_len; + uint32_t device_addr; + uint32_t reserve[4]; +}; + +struct aw_monitor_header { + uint32_t monitor_switch; + uint32_t monitor_time; + uint32_t monitor_count; + uint32_t step_count; + uint32_t reserve[4]; +}; + +struct vmax_step_config { + uint32_t vbat_min; + uint32_t vbat_max; + int vmax_vol; +}; + +struct aw_monitor { + bool open_dsp_en; + bool esd_enable; + int32_t dev_index; + uint8_t first_entry; + uint8_t timer_cnt; + uint32_t vbat_sum; + int32_t custom_capacity; + uint32_t pre_vmax; + + int bin_status; + struct aw_monitor_header monitor_hdr; + struct vmax_step_config *vmax_cfg; + + struct delayed_work with_dsp_work; +}; + +void aw87xxx_monitor_cfg_free(struct aw_monitor *monitor); +int aw87xxx_monitor_bin_parse(struct device *dev, + char *monitor_data, uint32_t data_len); +void aw87xxx_monitor_stop(struct aw_monitor *monitor); +void aw87xxx_monitor_start(struct aw_monitor *monitor); +int aw87xxx_monitor_no_dsp_get_vmax(struct aw_monitor *monitor, + int32_t *vmax); +void aw87xxx_monitor_init(struct device *dev, struct aw_monitor *monitor, + struct device_node *dev_node); +void aw87xxx_monitor_exit(struct aw_monitor *monitor); +int aw87xxx_dev_monitor_switch_set(struct aw_monitor *monitor, uint32_t enable); + +#endif diff --git a/sound/soc/codecs/aw87xxx/aw87xxx_pid_18_reg.h b/sound/soc/codecs/aw87xxx/aw87xxx_pid_18_reg.h new file mode 100644 index 000000000000..74d6548db91e --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_pid_18_reg.h @@ -0,0 +1,2315 @@ +#ifndef __AW87XXX_PID_18_REG_H__ +#define __AW87XXX_PID_18_REG_H__ + +/* registers list */ +#define AW87XXX_PID_18_CHIPID_REG (0x00) +#define AW87XXX_PID_18_SYSST_REG (0x01) +#define AW87XXX_PID_18_SYSINT_REG (0x02) +#define AW87XXX_PID_18_SYSCTRL_REG (0x03) +#define AW87XXX_PID_18_CPOC_REG (0x04) +#define AW87XXX_PID_18_CLASSD_REG (0x05) +#define AW87XXX_PID_18_MADPVTH_REG (0x06) +#define AW87XXX_PID_18_A3PARAM_REG (0x07) +#define AW87XXX_PID_18_A3A2PO_REG (0x08) +#define AW87XXX_PID_18_A2PARAM_REG (0x09) +#define AW87XXX_PID_18_A1PARAM_REG (0x0A) +#define AW87XXX_PID_18_POPCLK_REG (0x0B) +#define AW87XXX_PID_18_GTDRCPSS_REG (0x0C) +#define AW87XXX_PID_18_MULTI_REG (0x0D) +#define AW87XXX_PID_18_DFT1_REG (0x61) +#define AW87XXX_PID_18_DFT2_REG (0x62) +#define AW87XXX_PID_18_DFT3_REG (0x63) +#define AW87XXX_PID_18_DFT4_REG (0x64) +#define AW87XXX_PID_18_DFT5_REG (0x65) +#define AW87XXX_PID_18_DFT6_REG (0x66) + +#define AW87XXX_PID_18_CLASSD_DEFAULT (0x10) + +/******************************************** + * soft control info + * If you need to update this file, add this information manually + *******************************************/ +unsigned char aw87xxx_pid_18_softrst_access[2] = {0x00, 0xaa}; + +/******************************************** + * Register Access + *******************************************/ +#define AW87XXX_PID_18_REG_MAX (0x67) + +#define REG_NONE_ACCESS (0) +#define REG_RD_ACCESS (1 << 0) +#define REG_WR_ACCESS (1 << 1) + +const unsigned char aw87xxx_pid_18_reg_access[AW87XXX_PID_18_REG_MAX] = { + [AW87XXX_PID_18_CHIPID_REG] = (REG_RD_ACCESS), + [AW87XXX_PID_18_SYSST_REG] = (REG_RD_ACCESS), + [AW87XXX_PID_18_SYSINT_REG] = (REG_RD_ACCESS), + [AW87XXX_PID_18_SYSCTRL_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_CPOC_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_CLASSD_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_MADPVTH_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_A3PARAM_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_A3A2PO_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_A2PARAM_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_A1PARAM_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_POPCLK_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_GTDRCPSS_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_MULTI_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT1_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT2_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT3_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT4_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT5_REG] = (REG_RD_ACCESS | REG_WR_ACCESS), + [AW87XXX_PID_18_DFT6_REG] = (REG_RD_ACCESS), +}; + +/* detail information of registers begin */ +/* CHIPID (0x00) detail */ +/* IDCODE bit 7:0 (CHIPID 0x00) */ +#define AW87XXX_PID_18_IDCODE_START_BIT (0) +#define AW87XXX_PID_18_IDCODE_BITS_LEN (8) +#define AW87XXX_PID_18_IDCODE_MASK \ + (~(((1<reset_gpio) { + /* satisfy minimum reset pulse width spec */ + gpiod_set_value_cansleep(cs35l41->reset_gpio, 0); + usleep_range(2000, 2100); + gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); + } + + usleep_range(2000, 2100); + + ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, + int_status, int_status & CS35L41_OTP_BOOT_DONE, + 1000, 100000); + if (ret) { + dev_err_probe(cs35l41->dev, ret, + "Failed waiting for OTP_BOOT_DONE\n"); + return ret; + } + + regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); + if (int_status & CS35L41_OTP_BOOT_ERR) { + dev_err(cs35l41->dev, "OTP Boot error\n"); + return -EINVAL; + } + + return 0; +} + int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg) { - u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match; + u32 regid, reg_revid, i, mtl_revid, chipid_match; int irq_pol = 0; int ret; @@ -1242,29 +1274,10 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * goto err; } } - if (cs35l41->reset_gpio) { - /* satisfy minimum reset pulse width spec */ - usleep_range(2000, 2100); - gpiod_set_value_cansleep(cs35l41->reset_gpio, 1); - } - - usleep_range(2000, 2100); - - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, - int_status, int_status & CS35L41_OTP_BOOT_DONE, - 1000, 100000); - if (ret) { - dev_err_probe(cs35l41->dev, ret, - "Failed waiting for OTP_BOOT_DONE\n"); - goto err; - } - regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status); - if (int_status & CS35L41_OTP_BOOT_ERR) { - dev_err(cs35l41->dev, "OTP Boot error\n"); - ret = -EINVAL; + ret = cs35l41_boot(cs35l41); + if (ret) goto err; - } ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); if (ret < 0) { @@ -1450,7 +1463,20 @@ static int cs35l41_sys_suspend(struct device *dev) { struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - dev_dbg(cs35l41->dev, "System suspend, disabling IRQ\n"); + dev_dbg(cs35l41->dev, "System suspend, saving state\n"); + disable_irq(cs35l41->irq); + + regcache_cache_only(cs35l41->regmap, true); + regcache_mark_dirty(cs35l41->regmap); + + return 0; +} + +static int cs35l41_sys_poweroff(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "System poweroff, disabling IRQ\n"); disable_irq(cs35l41->irq); return 0; @@ -1476,20 +1502,71 @@ static int cs35l41_sys_resume_noirq(struct device *dev) return 0; } +static int cs35l41_sys_thaw(struct device *dev) +{ + struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + + dev_dbg(cs35l41->dev, "System thaw, reenabling IRQ\n"); + enable_irq(cs35l41->irq); + + return 0; +} + static int cs35l41_sys_resume(struct device *dev) { struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); + int ret; + + dev_dbg(cs35l41->dev, "System resume, restoring state\n"); + + regcache_cache_only(cs35l41->regmap, false); + + ret = cs35l41_boot(cs35l41); + if (ret) + return ret; + + cs35l41_test_key_unlock(cs35l41->dev, cs35l41->regmap); + ret = regcache_sync(cs35l41->regmap); + cs35l41_test_key_lock(cs35l41->dev, cs35l41->regmap); + if (ret) { + dev_err(cs35l41->dev, "Failed to restore register cache: %d\n", ret); + return ret; + } + + ret = cs35l41_init_boost(cs35l41->dev, cs35l41->regmap, + &cs35l41->hw_cfg); + if (ret) + return ret; - dev_dbg(cs35l41->dev, "System resume, reenabling IRQ\n"); enable_irq(cs35l41->irq); + if (!cs35l41->dsp.cs_dsp.running) + return 0; + + /* + * DSP firmware is gone after a full power cycle. Reset the DSP + * software state so that firmware is reloaded on next DSP preload event + */ + wm_adsp_stop(&cs35l41->dsp); + + if (!cs35l41->dsp.cs_dsp.booted) + return 0; + + wm_adsp_power_down(&cs35l41->dsp); + return 0; } EXPORT_GPL_DEV_PM_OPS(cs35l41_pm_ops) = { RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) - SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume) + .suspend = pm_sleep_ptr(cs35l41_sys_suspend), + .resume = pm_sleep_ptr(cs35l41_sys_resume), + .freeze = pm_sleep_ptr(cs35l41_sys_suspend), + .thaw = pm_sleep_ptr(cs35l41_sys_thaw), + .poweroff = pm_sleep_ptr(cs35l41_sys_poweroff), + .restore = pm_sleep_ptr(cs35l41_sys_resume), + NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq) }; diff --git a/sound/soc/codecs/max98388.c b/sound/soc/codecs/max98388.c index a4c57152d25f..46cbee4caa96 100644 --- a/sound/soc/codecs/max98388.c +++ b/sound/soc/codecs/max98388.c @@ -297,6 +297,56 @@ static SOC_ENUM_SINGLE_DECL(max98388_ssm_mod_enum, MAX98388_SPK_AMP_SSM_MOD_SHIFT, max98388_ssm_mod_text); + +#define MAX98388_VOL_MUTE 0x7f + +static int max98388_resume_mute_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + int mute = ucontrol->value.integer.value[0]; + unsigned int vol, val; + int ret; + + ret = regmap_read(max98388->regmap, MAX98388_R2090_SPK_CH_VOL_CTRL, &vol); + if (ret) + return ret; + + if (vol == MAX98388_VOL_MUTE && mute) + return 0; + if (vol != MAX98388_VOL_MUTE && !mute) + return 0; + + if (mute) { + max98388->saved_vol = vol; + val = MAX98388_VOL_MUTE; + } else { + val = max98388->saved_vol; + } + + ret = regmap_write(max98388->regmap, MAX98388_R2090_SPK_CH_VOL_CTRL, val); + + return ret; +} + +static int max98388_resume_mute_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct max98388_priv *max98388 = snd_soc_component_get_drvdata(component); + unsigned int vol; + int ret; + + ret = regmap_read(max98388->regmap, MAX98388_R2090_SPK_CH_VOL_CTRL, &vol); + if (ret) + return ret; + + ucontrol->value.integer.value[0] = (vol == MAX98388_VOL_MUTE) ? 1 : 0; + + return 0; +} + static const struct snd_kcontrol_new max98388_snd_controls[] = { SOC_SINGLE("Ramp Up Switch", MAX98388_R2091_SPK_CH_CFG, MAX98388_SPK_CFG_VOL_RMPUP_SHIFT, 1, 0), @@ -337,6 +387,8 @@ static const struct snd_kcontrol_new max98388_snd_controls[] = { /* Digital Volume */ SOC_SINGLE_TLV("Digital Volume", MAX98388_R2090_SPK_CH_VOL_CTRL, 0, 0x7F, 1, max98388_digital_tlv), + SOC_SINGLE_EXT("Resume Mute Switch", 0, 0, 1, 0, + max98388_resume_mute_switch_get, max98388_resume_mute_switch_put), /* Speaker Volume */ SOC_SINGLE_TLV("Speaker Volume", MAX98388_R2092_SPK_AMP_OUT_CFG, 0, 5, 0, max98388_amp_gain_tlv), @@ -389,27 +441,43 @@ static void max98388_reset(struct max98388_priv *max98388, struct device *dev) { int ret, reg, count; + /* Software Reset */ ret = regmap_update_bits(max98388->regmap, MAX98388_R2000_SW_RESET, MAX98388_SOFT_RESET, MAX98388_SOFT_RESET); - if (ret) + + if (ret) { dev_err(dev, "Reset command failed. (ret:%d)\n", ret); + goto exit; + } + count = 0; while (count < 3) { usleep_range(10000, 11000); + /* Software Reset Verification */ ret = regmap_read(max98388->regmap, MAX98388_R22FF_REV_ID, ®); + if (!ret) { dev_info(dev, "Reset completed (retry:%d)\n", count); - return; + goto exit; } count++; } + dev_err(dev, "Reset failed. (ret:%d)\n", ret); + + +exit: + regcache_cache_only(max98388->regmap, true); + ret = regmap_update_bits(max98388->regmap, + MAX98388_R2000_SW_RESET, + MAX98388_SOFT_RESET, 0); + regcache_cache_only(max98388->regmap, false); } static int max98388_probe(struct snd_soc_component *component) @@ -418,6 +486,7 @@ static int max98388_probe(struct snd_soc_component *component) /* Software Reset */ max98388_reset(max98388, component->dev); + usleep_range(400, 1000); /* General channel source configuration */ regmap_write(max98388->regmap, @@ -811,6 +880,7 @@ static bool max98388_readable_register(struct device *dev, case MAX98388_R210E_AUTO_RESTART: case MAX98388_R210F_GLOBAL_EN: case MAX98388_R22FF_REV_ID: + case MAX98388_R2000_SW_RESET: return true; default: return false; @@ -823,6 +893,7 @@ static bool max98388_volatile_reg(struct device *dev, unsigned int reg) case MAX98388_R2001_INT_RAW1 ... MAX98388_R2005_INT_STATE2: case MAX98388_R210F_GLOBAL_EN: case MAX98388_R22FF_REV_ID: + case MAX98388_R2000_SW_RESET: return true; default: return false; @@ -866,6 +937,7 @@ static int max98388_resume(struct device *dev) regcache_cache_only(max98388->regmap, false); max98388_reset(max98388, dev); + usleep_range(400, 1000); regcache_sync(max98388->regmap); return 0; diff --git a/sound/soc/codecs/max98388.h b/sound/soc/codecs/max98388.h index 77833d181913..d038acbf24b0 100644 --- a/sound/soc/codecs/max98388.h +++ b/sound/soc/codecs/max98388.h @@ -229,6 +229,7 @@ struct max98388_priv { bool interleave_mode; unsigned int ch_size; bool tdm_mode; + unsigned int saved_vol; }; #endif diff --git a/sound/soc/codecs/nau8821.c b/sound/soc/codecs/nau8821.c index c45c5b864ea4..2a60b25aa627 100644 --- a/sound/soc/codecs/nau8821.c +++ b/sound/soc/codecs/nau8821.c @@ -1432,6 +1432,10 @@ static int nau8821_set_fll(struct snd_soc_component *component, mdelay(2); regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_VCO); + regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_MCLK); + regmap_update_bits(nau8821->regmap, NAU8821_R03_CLK_DIVIDER, + NAU8821_CLK_SRC_MASK, NAU8821_CLK_SRC_VCO); return 0; } diff --git a/sound/soc/sof/core.c b/sound/soc/sof/core.c index 2d394389c945..1bc288f4d784 100644 --- a/sound/soc/sof/core.c +++ b/sound/soc/sof/core.c @@ -61,7 +61,7 @@ MODULE_PARM_DESC(boot_timeout, #endif /* SOF defaults if not provided by the platform in ms */ -#define TIMEOUT_DEFAULT_IPC_MS 500 +#define TIMEOUT_DEFAULT_IPC_MS 2000 #define TIMEOUT_DEFAULT_BOOT_MS 2000 /** diff --git a/sound/soc/sof/pm.c b/sound/soc/sof/pm.c index dd7cd87f1fa5..2052b35e1b50 100644 --- a/sound/soc/sof/pm.c +++ b/sound/soc/sof/pm.c @@ -12,6 +12,8 @@ #include "ops.h" #include "sof-priv.h" #include "sof-audio.h" +#include "sound/soc-component.h" +#include "sound/soc.h" static int override_on_demand_boot = -1; module_param_named(on_demand_boot, override_on_demand_boot, int, 0444); @@ -75,6 +77,28 @@ static void sof_cache_debugfs(struct snd_sof_dev *sdev) } #endif +static void sof_mute_components(struct snd_sof_dev *sdev, bool mute) +{ + struct snd_soc_card *card = sdev->component->card; + struct snd_soc_component *component; + + if (!card) + return; + + for_each_card_components(card, component) { + struct snd_kcontrol *kctrl; + struct snd_ctl_elem_value val; + + kctrl = snd_soc_component_get_kcontrol(component, "Resume Mute Switch"); + if (!kctrl) + continue; + + val.value.integer.value[0] = mute; + + kctrl->put(kctrl, &val); + } +} + int snd_sof_boot_dsp_firmware(struct snd_sof_dev *sdev) { const struct sof_ipc_pm_ops *pm_ops = sof_ipc_get_ops(sdev, pm); @@ -144,6 +168,7 @@ int snd_sof_boot_dsp_firmware(struct snd_sof_dev *sdev) __func__, ret); } + sof_mute_components(sdev, false); setup_fail: #if IS_ENABLED(CONFIG_SND_SOC_SOF_DEBUG_ENABLE_DEBUGFS_CACHE) if (ret < 0) { @@ -263,6 +288,8 @@ static int sof_suspend(struct device *dev, bool runtime_suspend) ret); return ret; } + + sof_mute_components(sdev, true); } pm_state.event = target_state; -- 2.55.0