From dea4ccb29551191a17bd3e2dbe8e924d48a89830 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Sat, 20 Jun 2026 13:07:42 +0200 Subject: [PATCH] handheld Signed-off-by: Peter Jung --- .../wmi/devices/msi-wmi-platform.rst | 26 + MAINTAINERS | 6 + drivers/acpi/ec.c | 9 + drivers/bluetooth/btrtl.c | 71 + drivers/bluetooth/hci_qca.c | 1 + drivers/bus/mhi/host/boot.c | 15 +- drivers/bus/mhi/host/init.c | 18 +- drivers/bus/mhi/host/internal.h | 2 + drivers/bus/mhi/host/pm.c | 1 + drivers/extcon/Kconfig | 7 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-steamdeck.c | 180 + drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 2 - .../gpu/drm/amd/amdgpu/atombios_encoders.c | 10 +- .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 5 +- drivers/gpu/drm/amd/display/dc/dc.h | 2 +- .../amd/display/dc/hwss/dce110/dce110_hwseq.c | 33 +- .../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 +- drivers/gpu/drm/drm_panel_backlight_quirks.c | 15 + .../gpu/drm/drm_panel_orientation_quirks.c | 144 + drivers/hid/Kconfig | 24 + drivers/hid/Makefile | 4 + drivers/hid/hid-asus-ally.c | 2215 +++++++ drivers/hid/hid-asus-ally.h | 398 ++ drivers/hid/hid-asus.c | 25 +- drivers/hid/hid-asus.h | 13 + drivers/hid/hid-ids.h | 10 + drivers/hid/hid-msi.c | 1998 +++++++ 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 | 214 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpd-fan.c | 8 + drivers/hwmon/steamdeck-hwmon.c | 294 + drivers/input/joystick/xpad.c | 4 + drivers/input/misc/uinput.c | 48 +- 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 +++ drivers/mfd/Kconfig | 11 + drivers/mfd/Makefile | 2 + drivers/mfd/steamdeck.c | 147 + drivers/mmc/core/mmc.c | 2 + drivers/mmc/core/quirks.h | 2 + drivers/mmc/core/sd.c | 2 + drivers/net/wireless/ath/ath11k/mhi.c | 8 +- drivers/net/wireless/ath/ath12k/mhi.c | 14 +- drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 2 + drivers/platform/x86/asus-wmi.c | 6 +- drivers/platform/x86/msi-wmi-platform.c | 1176 +++- drivers/platform/x86/msi-wmi.c | 97 +- drivers/platform/x86/zotac-zone-platform.c | 1122 ++++ drivers/usb/dwc3/dwc3-pci.c | 3 +- drivers/usb/usbip/vhci_hcd.c | 33 +- drivers/video/backlight/backlight.c | 2 + include/linux/mhi.h | 2 + include/linux/mmc/card.h | 1 + include/net/bluetooth/hci.h | 1 + include/net/bluetooth/hci_core.h | 2 +- include/uapi/linux/uinput.h | 5 + net/bluetooth/hci_core.c | 10 +- net/bluetooth/hci_sync.c | 12 +- net/bluetooth/mgmt.c | 4 +- sound/hda/codecs/realtek/alc269.c | 59 + 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 | 975 +++ 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 | 1206 ++++ 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/max98388.c | 24 +- 101 files changed, 33587 insertions(+), 184 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/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/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 db0a2ca41c14..0de9489b77aa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18163,6 +18163,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/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/bluetooth/btrtl.c b/drivers/bluetooth/btrtl.c index 62f9d4df3a4f..5f5395e67bf6 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; @@ -1297,8 +1329,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. */ @@ -1511,6 +1581,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/hci_qca.c b/drivers/bluetooth/hci_qca.c index 34500137df2c..08af9a48b746 100644 --- a/drivers/bluetooth/hci_qca.c +++ b/drivers/bluetooth/hci_qca.c @@ -2791,6 +2791,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 0a728ca2c494..f6c89ef63440 100644 --- a/drivers/bus/mhi/host/init.c +++ b/drivers/bus/mhi/host/init.c @@ -1160,8 +1160,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); @@ -1187,6 +1188,14 @@ int mhi_prepare_for_power_up(struct mhi_controller *mhi_cntrl) } EXPORT_SYMBOL_GPL(mhi_prepare_for_power_up); +void __mhi_unprepare_keep_dev(struct mhi_controller *mhi_cntrl) +{ + mhi_cntrl->bhi = NULL; + mhi_cntrl->bhie = NULL; + + mhi_deinit_dev_ctxt(mhi_cntrl); +} + void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl) { if (mhi_cntrl->fbc_image) { @@ -1199,10 +1208,7 @@ void mhi_unprepare_after_power_down(struct mhi_controller *mhi_cntrl) mhi_cntrl->rddm_image = NULL; } - mhi_cntrl->bhi = NULL; - mhi_cntrl->bhie = NULL; - - mhi_deinit_dev_ctxt(mhi_cntrl); + __mhi_unprepare_keep_dev(mhi_cntrl); } EXPORT_SYMBOL_GPL(mhi_unprepare_after_power_down); diff --git a/drivers/bus/mhi/host/internal.h b/drivers/bus/mhi/host/internal.h index 7b0ee5e3a12d..80209b5cc10f 100644 --- a/drivers/bus/mhi/host/internal.h +++ b/drivers/bus/mhi/host/internal.h @@ -420,4 +420,6 @@ void mhi_unmap_single_no_bb(struct mhi_controller *mhi_cntrl, void mhi_unmap_single_use_bb(struct mhi_controller *mhi_cntrl, struct mhi_buf_info *buf_info); +void __mhi_unprepare_keep_dev(struct mhi_controller *mhi_cntrl); + #endif /* _MHI_INT_H */ diff --git a/drivers/bus/mhi/host/pm.c b/drivers/bus/mhi/host/pm.c index f799503c8f36..3a37bfa3e7a6 100644 --- a/drivers/bus/mhi/host/pm.c +++ b/drivers/bus/mhi/host/pm.c @@ -1271,6 +1271,7 @@ void mhi_power_down_keep_dev(struct mhi_controller *mhi_cntrl, bool graceful) { __mhi_power_down(mhi_cntrl, graceful, false); + __mhi_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..49cb5d30bf6b --- /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_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h index 51ab1a332615..08ac4a357ac4 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h @@ -438,8 +438,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/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/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c index f74a258f868a..ebbec303a640 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -157,6 +157,9 @@ MODULE_FIRMWARE(FIRMWARE_DCN_401_DMUB); #define FIRMWARE_DCN_42_DMUB "amdgpu/dcn_4_2_dmcub.bin" MODULE_FIRMWARE(FIRMWARE_DCN_42_DMUB); +/* Maximum backlight level. */ +#define AMDGPU_MAX_BL_LEVEL 0xFFFF + /** * DOC: overview * @@ -10036,7 +10039,7 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, int planes_count = 0, vpos, hpos; unsigned long flags; u32 target_vblank, last_flip_vblank; - bool vrr_active = amdgpu_dm_crtc_vrr_active(acrtc_state); + bool vrr_active = true;//amdgpu_dm_crtc_vrr_active(acrtc_state); bool cursor_update = false; bool pflip_present = false; bool dirty_rects_changed = false; diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 23f2fce2a6a7..27b93870053d 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -68,7 +68,7 @@ struct dcn_dccg_reg_state; /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC */ -#define MAX_SURFACES 4 +#define MAX_SURFACES 6 /** * MAX_PLANES - representative of the upper bound of planes that are supported by the HW */ 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 f0abbb7c2cb2..8f57fc58fea4 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; }; @@ -2063,9 +2065,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 @@ -3408,6 +3430,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/link/link_detection.c b/drivers/gpu/drm/amd/display/dc/link/link_detection.c index 794dd6a95918..fd54eefa7c01 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_detection.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_detection.c @@ -650,7 +650,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 eb791285ed06..cbd68aebbb88 100644 --- a/drivers/gpu/drm/amd/display/dc/link/link_validation.c +++ b/drivers/gpu/drm/amd/display/dc/link/link_validation.c @@ -35,6 +35,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) { @@ -292,6 +294,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 a74b150cbe9c..5b639f818695 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 @@ -1472,9 +1472,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/drm_panel_backlight_quirks.c b/drivers/gpu/drm/drm_panel_backlight_quirks.c index 537dc6dd0534..69c73f0f98fa 100644 --- a/drivers/gpu/drm/drm_panel_backlight_quirks.c +++ b/drivers/gpu/drm/drm_panel_backlight_quirks.c @@ -96,6 +96,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..17ded3fc1f58 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,84 @@ 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, }, { /* OrangePi Neo */ .matches = { DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OrangePi"), @@ -498,6 +630,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/hid/Kconfig b/drivers/hid/Kconfig index 4eed57ed23df..81dfc4f63b0f 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 @@ -1479,4 +1501,6 @@ source "drivers/hid/spi-hid/Kconfig" source "drivers/hid/dockchannel-hid/Kconfig" +source "drivers/hid/zotac-zone-hid/Kconfig" + endif # HID_SUPPORT diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index d2579e299b5e..6a2739c01f2b 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 @@ -183,6 +185,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..8b51560fdbcf --- /dev/null +++ b/drivers/hid/hid-asus-ally.c @@ -0,0 +1,2215 @@ +// 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 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) { + ff_report->ff.magnitude_left = ff_report->ff.magnitude_strong; + ff_report->ff.magnitude_right = ff_report->ff.magnitude_weak; + 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)); +} + +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); + + /* 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); + 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..c83817589082 --- /dev/null +++ b/drivers/hid/hid-asus-ally.h @@ -0,0 +1,398 @@ +/* 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_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_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 3f5e96900b67..23061a1339ec 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -23,6 +23,7 @@ #include #include #include +#include #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) { - 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 drvdata->kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(struct asus_kbd_leds), @@ -1209,6 +1212,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) { + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_host_endpoint *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 64e0e66a7641..a6664dbb5f1b 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -238,6 +238,7 @@ #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_FOLIO 0x1a30 #define USB_DEVICE_ID_ASUSTEK_ROG_Z13_LIGHTBAR 0x18c6 +#define USB_DEVICE_ID_ASUSTEK_ROG_RAIKIRI_PAD 0x1abb #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY_X 0x1b4c #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b @@ -1076,7 +1077,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 @@ -1593,6 +1599,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 diff --git a/drivers/hid/hid-msi.c b/drivers/hid/hid-msi.c new file mode 100644 index 000000000000..f5c54cd62f3b --- /dev/null +++ b/drivers/hid/hid-msi.c @@ -0,0 +1,1998 @@ +// 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 "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, +}; + +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 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; + struct mutex cfg_mutex; /* mutex for synchronous data */ + struct mutex rom_mutex; /* mutex for SYNC_TO_ROM calls */ + bool waiting_for_ack; + 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, &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, ret = 0; + + scoped_guard(spinlock, &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 -EINVAL; + 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); + 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 -EINVAL; + } + f_idx = frame_calc; + + scoped_guard(spinlock, &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 -EINVAL; + scoped_guard(spinlock, &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 -EINVAL; + scoped_guard(spinlock, &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, &drvdata->profile_lock) + drvdata->profile_pending = CLAW_NO_PENDING; + + return ret; +} + +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)(&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_for_ack && + drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) { + drvdata->cmd_status = ret; + drvdata->waiting_for_ack = false; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_READ_PROFILE_ACK: + ret = claw_profile_event(drvdata, cmd_rep); + if (drvdata->waiting_for_ack && + drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE) { + drvdata->cmd_status = ret; + drvdata->waiting_for_ack = false; + complete(&drvdata->send_cmd_complete); + } + + break; + case CLAW_COMMAND_TYPE_ACK: + if (drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_PROFILE || + drvdata->waiting_cmd == CLAW_COMMAND_TYPE_READ_GAMEPAD_MODE) + break; + if (drvdata->waiting_for_ack) { + drvdata->cmd_status = 0; + drvdata->waiting_for_ack = false; + 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); +} + +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); + int ret; + + 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); + + guard(mutex)(&drvdata->cfg_mutex); + if (timeout) { + reinit_completion(&drvdata->send_cmd_complete); + scoped_guard(spinlock_irqsave, &drvdata->cmd_lock) { + drvdata->waiting_cmd = index; + drvdata->waiting_for_ack = true; + drvdata->cmd_status = -ETIMEDOUT; + } + } + + 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_clear_state; + + ret = ret == CLAW_PACKET_SIZE ? 0 : -EIO; + if (ret) + goto err_clear_state; + + 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; + } + +err_clear_state: + if (timeout) { + guard(spinlock_irqsave)(&drvdata->cmd_lock); + drvdata->waiting_cmd = CLAW_COMMAND_TYPE_NONE; + drvdata->waiting_for_ack = false; + } + + return ret; +} + +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; + u8 data[2]; + + 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; + + data[0] = ret; + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + data[1] = drvdata->mkeys_function; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0); + 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; + u8 data[2]; + + 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; + + scoped_guard(spinlock_irqsave, &drvdata->mode_lock) + data[0] = drvdata->gamepad_mode; + data[1] = ret; + + ret = claw_hw_output_report(hdev, CLAW_COMMAND_TYPE_SWITCH_MODE, data, ARRAY_SIZE(data), 0); + 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->led_mc.led_cdev.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->led_mc.led_cdev.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; + + 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; + + 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; + + 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"); + + drvdata->led_mc.led_cdev.name = "msi_claw: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->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); + + cancel_delayed_work_sync(&drvdata->rgb_queue); + if (rgb_registered) { + device_remove_group(drvdata->led_mc.led_cdev.dev, &claw_rgb_attr_group); + led_classdev_multicolor_unregister(&drvdata->led_mc); + } + + 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 = 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/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..31188cee80f8 --- /dev/null +++ b/drivers/hid/zotac-zone-hid/zotac-zone.h @@ -0,0 +1,214 @@ +// 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 + +#define DEVICE_ATTR_RO_NAMED(_name, _attr_name) \ + struct device_attribute dev_attr_##_name = { \ + .attr = { .name = _attr_name, .mode = 0444 }, \ + .show = _name##_show, \ + } + +#define DEVICE_ATTR_WO_NAMED(_name, _attr_name) \ + struct device_attribute dev_attr_##_name = { \ + .attr = { .name = _attr_name, .mode = 0200 }, \ + .store = _name##_store, \ + } + +#define DEVICE_ATTR_RW_NAMED(_name, _attr_name) \ + struct device_attribute dev_attr_##_name = { \ + .attr = { .name = _attr_name, .mode = 0644 }, \ + .show = _name##_show, \ + .store = _name##_store, \ + } + +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 cf86e6f0ecec..b4f8023cedbb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2159,6 +2159,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 982ee2c6f9de..b6c991ea30e6 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -218,6 +218,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o +obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o obj-$(CONFIG_SENSORS_SPD5118) += spd5118.o obj-$(CONFIG_SENSORS_STTS751) += stts751.o obj-$(CONFIG_SENSORS_SURFACE_FAN)+= surface_fan.o diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index 80de5f20781e..98260e471fff 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -156,6 +156,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/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index f541a4e51687..0a5dba8004f0 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..7a982c0053bc 100644 --- a/drivers/input/misc/uinput.c +++ b/drivers/input/misc/uinput.c @@ -20,6 +20,7 @@ */ #include #include +#include #include #include #include @@ -296,7 +297,7 @@ static int uinput_dev_flush(struct input_dev *dev, struct file *file) static void uinput_destroy_device(struct uinput_device *udev) { - const char *name, *phys; + const char *name, *phys, *uniq; struct input_dev *dev = udev->dev; enum uinput_state old_state = udev->state; @@ -312,6 +313,7 @@ static void uinput_destroy_device(struct uinput_device *udev) if (dev) { name = dev->name; phys = dev->phys; + uniq = dev->uniq; if (old_state == UIST_CREATED) { uinput_flush_requests(udev); input_unregister_device(dev); @@ -320,6 +322,7 @@ static void uinput_destroy_device(struct uinput_device *udev) } kfree(name); kfree(phys); + kfree(uniq); udev->dev = NULL; } } @@ -906,6 +909,24 @@ static int uinput_str_to_user(void __user *dest, const char *str, return ret ? -EFAULT : len; } +static int uinput_get_user_str(struct uinput_device *udev, const char **kptr, + const char *uptr, unsigned int size) +{ + char *tmp; + + if (udev->state == UIST_CREATED) + return -EINVAL; + + tmp = strndup_user(uptr, size); + if (IS_ERR(tmp)) + return PTR_ERR(tmp); + + kfree(*kptr); + *kptr = tmp; + + return 0; +} + static long uinput_ioctl_handler(struct file *file, unsigned int cmd, unsigned long arg, void __user *p) { @@ -914,7 +935,6 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, struct uinput_ff_upload ff_up; struct uinput_ff_erase ff_erase; struct uinput_request *req; - char *phys; const char *name; unsigned int size; @@ -991,19 +1011,8 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, goto out; case UI_SET_PHYS: - if (udev->state == UIST_CREATED) { - retval = -EINVAL; - goto out; - } - - phys = strndup_user(p, 1024); - if (IS_ERR(phys)) { - retval = PTR_ERR(phys); - goto out; - } - - kfree(udev->dev->phys); - udev->dev->phys = phys; + pr_warn_once("uinput: UI_SET_PHYS is deprecated. Use UI_SET_PHYS_STR"); + retval = uinput_get_user_str(udev, &udev->dev->phys, p, 1024); goto out; case UI_BEGIN_FF_UPLOAD: @@ -1098,6 +1107,15 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, case UI_ABS_SETUP & ~IOCSIZE_MASK: retval = uinput_abs_setup(udev, p, size); goto out; + + case UI_SET_PHYS_STR(0): + retval = uinput_get_user_str(udev, &udev->dev->phys, p, size); + goto out; + + case UI_SET_UNIQ_STR(0): + retval = uinput_get_user_str(udev, &udev->dev->uniq, p, size); + goto out; + } retval = -EINVAL; diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index bc38e4e7ccc2..23d58ba97768 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1038,6 +1038,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 81d0705e10de..ccaf6124cf72 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -88,6 +88,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..3d56ec4403cd --- /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 28ef4c487367..a764918356c1 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -88,4 +88,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 be45991f63f5..945c82acb029 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.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/mfd/Kconfig b/drivers/mfd/Kconfig index a54fe5bfd227..91956299438b 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -2571,5 +2571,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 7ca766796c96..db83739da24a 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -301,6 +301,8 @@ obj-$(CONFIG_MFD_QNAP_MCU) += qnap-mcu.o obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o +obj-$(CONFIG_MFD_STEAMDECK) += steamdeck.o + obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.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/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/net/wireless/ath/ath11k/mhi.c b/drivers/net/wireless/ath/ath11k/mhi.c index f994233df2bb..823b20d16977 100644 --- a/drivers/net/wireless/ath/ath11k/mhi.c +++ b/drivers/net/wireless/ath/ath11k/mhi.c @@ -456,12 +456,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) 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/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index fe3ce93843af..193dd06f33ca 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -581,9 +581,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. @@ -1072,6 +1075,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 5124dad7bd7a..dc52a6095175 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -169,5 +169,7 @@ obj-$(CONFIG_WINMATE_FM07_KEYS) += winmate-fm07-keys.o # SEL obj-$(CONFIG_SEL3350_PLATFORM) += sel3350-platform.o +obj-$(CONFIG_ZOTAC_ZONE_PLATFORM) += zotac-zone-platform.o + # OneXPlayer obj-$(CONFIG_OXP_EC) += oxpec.o diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 80144c412b90..a71f019fe658 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -4120,7 +4120,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; @@ -4144,7 +4144,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: @@ -4157,7 +4157,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..b40e15ac71cf 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,33 @@ #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) + +/* Get_Data() and Set_Data() Params */ +#define MSI_PLATFORM_PL1_ADDR 0x50 +#define MSI_PLATFORM_PL2_ADDR 0x51 +#define MSI_PLATFORM_BAT_ADDR 0xd7 + static bool force; module_param_unsafe(force, bool, 0); MODULE_PARM_DESC(force, "Force loading without checking for supported WMI interface versions"); @@ -79,9 +119,68 @@ 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 */ + int pl_min; /* Minimum PLx value */ + int pl1_max; /* Maximum PL1 value */ + int pl2_max; /* Maximum PL2 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, +}; + +static const char *const msi_fw_attr_name[] = { + [MSI_ATTR_PPT_PL1_SPL] = "ppt_pl1_spl", + [MSI_ATTR_PPT_PL2_SPPT] = "ppt_pl2_sppt", +}; + +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)", +}; + +#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 +223,53 @@ 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, + .pl_min = 8, + .pl1_max = 43, + .pl2_max = 45 +}; +static struct msi_wmi_platform_quirk quirk_gen2 = { + .shift_mode = true, + .charge_threshold = true, + .dual_fans = true, + .restore_curves = true, + .pl_min = 8, + .pl1_max = 30, + .pl2_max = 37 +}; + +static const struct dmi_system_id msi_quirks[] = { + { + .ident = "MSI Claw (gen 1)", + .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 AI+ 7", + .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 AI+ 8", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International Co., Ltd."), + DMI_MATCH(DMI_BOARD_NAME, "MS-1T52"), + }, + .driver_data = &quirk_gen2, + }, +}; + static int msi_wmi_platform_parse_buffer(union acpi_object *obj, u8 *output, size_t length) { if (obj->type != ACPI_TYPE_BUFFER) @@ -140,45 +286,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 +619,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 +736,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 +748,497 @@ 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) +{ + 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); + 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); + 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; + + 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; + case MSI_PLATFORM_SHIFT_USER: + *profile = PLATFORM_PROFILE_CUSTOM; + 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: + buffer[1] = MSI_PLATFORM_SHIFT_USER; + 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; + 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->pl_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->pl_min, + data->quirks->pl2_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,17 +1258,21 @@ 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, + ret = msi_wmi_platform_query(data->data, data->method, data->buffer, data->length); up_write(&data->buffer_lock); if (ret < 0) return ret; + down_write(&data->buffer_lock); + memcpy(data->buffer, payload, data->length); + up_write(&data->buffer_lock); + 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 +1284,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 +1357,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 +1396,46 @@ 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_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 +1445,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; @@ -429,11 +1463,44 @@ static int msi_wmi_platform_probe(struct wmi_device *wdev, const void *context) 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 +1514,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/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/linux/mhi.h b/include/linux/mhi.h index fb3ba639f4f8..d4e687c14137 100644 --- a/include/linux/mhi.h +++ b/include/linux/mhi.h @@ -320,6 +320,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) @@ -396,6 +397,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/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 572b1c620c5d..0e4939a20bd9 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -486,6 +486,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 aa600fbf9a53..adc0823a0bad 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -497,7 +497,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/uapi/linux/uinput.h b/include/uapi/linux/uinput.h index c9e677e3af1d..84d4fa142830 100644 --- a/include/uapi/linux/uinput.h +++ b/include/uapi/linux/uinput.h @@ -142,9 +142,14 @@ struct uinput_abs_setup { #define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) #define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) #define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) + +/* DEPRECATED: Data size is ambiguous. Use UI_SET_PHYS_STR instead. */ #define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) + #define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) #define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int) +#define UI_SET_PHYS_STR(len) _IOC(_IOC_WRITE, UINPUT_IOCTL_BASE, 111, len) +#define UI_SET_UNIQ_STR(len) _IOC(_IOC_WRITE, UINPUT_IOCTL_BASE, 112, len) #define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) #define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 28d7929dc593..30b82120e2d5 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -520,7 +520,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); @@ -906,7 +906,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); @@ -2511,7 +2511,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); @@ -2632,7 +2632,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); @@ -2669,7 +2669,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); hci_cmd_sync_clear(hdev); diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index df23245d6ccd..9975ec746bc6 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -2761,6 +2761,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; @@ -2838,6 +2839,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++; } @@ -6279,7 +6289,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 f4aa814a0397..90dd007dd59c 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -8108,7 +8108,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); @@ -8164,7 +8164,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/sound/hda/codecs/realtek/alc269.c b/sound/hda/codecs/realtek/alc269.c index 943394cbec25..b5c351ce6065 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) { @@ -3894,6 +3908,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, @@ -3907,6 +3923,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, @@ -4194,6 +4211,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 @@ -4678,6 +4719,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, @@ -5492,6 +5540,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[]) { @@ -7927,6 +7983,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), @@ -8068,6 +8126,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/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 269c31ce0814..ce752b68a664 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2881,4 +2881,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 172861d17cfd..7bcf72b4c616 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -871,6 +871,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..00c7aedb7c11 --- /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..ebe0c77f5674 --- /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..967662628399 --- /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..35a0f9114718 --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_device.c @@ -0,0 +1,975 @@ +/* + * 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..7d8dadd008dc --- /dev/null +++ b/sound/soc/codecs/aw87xxx/aw87xxx_monitor.c @@ -0,0 +1,1206 @@ +/* + * 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<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) @@ -419,6 +435,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, @@ -812,6 +829,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; @@ -824,6 +842,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; @@ -867,6 +886,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; -- 2.54.0 From de82d818a91b93b29e7e1846bfba64ffbe1b4277 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Tue, 23 Jun 2026 21:14:29 +0200 Subject: [PATCH] Revert "drm/amd/display: Enable 3 overlay planes for Steam Deck" --- drivers/gpu/drm/amd/display/dc/dc.h | 2 +- .../drm/amd/display/dc/resource/dcn301/dcn301_resource.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/amd/display/dc/dc.h b/drivers/gpu/drm/amd/display/dc/dc.h index 3d0731418423..37714d4371fb 100644 --- a/drivers/gpu/drm/amd/display/dc/dc.h +++ b/drivers/gpu/drm/amd/display/dc/dc.h @@ -68,7 +68,7 @@ struct dcn_dccg_reg_state; /** * MAX_SURFACES - representative of the upper bound of surfaces that can be piped to a single CRTC */ -#define MAX_SURFACES 6 +#define MAX_SURFACES 4 /** * MAX_PLANES - representative of the upper bound of planes that are supported by the HW */ 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 5b639f818695..a74b150cbe9c 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 @@ -1472,9 +1472,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 = 3; - dc->caps.max_slave_yuv_planes = 3; - dc->caps.max_slave_rgb_planes = 3; + dc->caps.max_slave_planes = 2; + dc->caps.max_slave_yuv_planes = 2; + dc->caps.max_slave_rgb_planes = 2; dc->caps.is_apu = true; dc->caps.post_blend_color_processing = true; dc->caps.force_dp_tps4_for_cp2520 = true; -- 2.54.0