From ae046407ab3d4d4c0ed079a9d476d73fc34e572a Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Wed, 21 Jul 2021 01:13:45 +0200 Subject: [PATCH] Add patches for v5.12 Notes: - Added support for OV8865 camera sensor, used as world-facing cameras on many Surface devices. The Surface Go line is currently not supported. Links: - kernel: https://github.com/linux-surface/kernel/tree/v5.13-surface-devel - SAM: https://github.com/linux-surface/surface-aggregator-module/commit/0686c05d4b65602d149e7c6ed255b529d50bafd0 - SAM-gen4: https://github.com/linux-surface/surface-aggregator-module-gen4/commit/e321205faaf5f39675ccb8c2314b171c2319b9bc - IPTS: https://github.com/linux-surface/intel-precise-touch/commit/e66f2fe9fe79732c8083104413300cfa36f5b579 --- configs/surface-5.13.config | 60 + patches/5.13/0001-surface3-oemb.patch | 101 + patches/5.13/0002-mwifiex.patch | 2520 ++++++++ patches/5.13/0003-ath10k.patch | 121 + patches/5.13/0004-ipts.patch | 1503 +++++ patches/5.13/0005-surface-sam-over-hid.patch | 335 + patches/5.13/0006-surface-sam.patch | 2324 +++++++ patches/5.13/0007-surface-hotplug.patch | 44 + patches/5.13/0008-surface-typecover.patch | 233 + patches/5.13/0009-cameras.patch | 5876 ++++++++++++++++++ patches/5.13/0010-amd-gpio.patch | 109 + patches/5.13/0011-amd-s0ix.patch | 1025 +++ 12 files changed, 14251 insertions(+) create mode 100644 configs/surface-5.13.config create mode 100644 patches/5.13/0001-surface3-oemb.patch create mode 100644 patches/5.13/0002-mwifiex.patch create mode 100644 patches/5.13/0003-ath10k.patch create mode 100644 patches/5.13/0004-ipts.patch create mode 100644 patches/5.13/0005-surface-sam-over-hid.patch create mode 100644 patches/5.13/0006-surface-sam.patch create mode 100644 patches/5.13/0007-surface-hotplug.patch create mode 100644 patches/5.13/0008-surface-typecover.patch create mode 100644 patches/5.13/0009-cameras.patch create mode 100644 patches/5.13/0010-amd-gpio.patch create mode 100644 patches/5.13/0011-amd-s0ix.patch diff --git a/configs/surface-5.13.config b/configs/surface-5.13.config new file mode 100644 index 000000000..cb2b3c9c4 --- /dev/null +++ b/configs/surface-5.13.config @@ -0,0 +1,60 @@ +# +# Surface Aggregator Module +# +CONFIG_SURFACE_AGGREGATOR=m +CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION=n +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_CDEV=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m + +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_PLATFORM_PROFILE=m + +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_KBD=m + +CONFIG_BATTERY_SURFACE=m +CONFIG_CHARGER_SURFACE=m + +# +# Surface Hotplug +# +CONFIG_SURFACE_HOTPLUG=m + +# +# IPTS touchscreen +# +# This only enables the user interface for IPTS data. +# For the touchscreen to work, you need to install iptsd. +# +CONFIG_MISC_IPTS=m + +# +# Cameras: IPU3 +# +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_CIO2_BRIDGE=y +CONFIG_INTEL_SKL_INT3472=m + +# +# Cameras: Sensor drivers +# +CONFIG_VIDEO_OV5693=m +CONFIG_VIDEO_OV8865=m + +# +# ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7 +# +CONFIG_APDS9960=m + +# +# Other Drivers +# +CONFIG_INPUT_SOC_BUTTON_ARRAY=m +CONFIG_SURFACE_3_BUTTON=m +CONFIG_SURFACE_3_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/patches/5.13/0001-surface3-oemb.patch b/patches/5.13/0001-surface3-oemb.patch new file mode 100644 index 000000000..50c705daa --- /dev/null +++ b/patches/5.13/0001-surface3-oemb.patch @@ -0,0 +1,101 @@ +From c3f46a736f4db084b44642c46f3112011cbea18e Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3-oemb +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index fcd1d4fb94d5..ee26a5998b07 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 9408ee63cb26..5cac83953901 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3718,6 +3718,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index 227424236fd5..1013a57be89a 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; + +-- +2.32.0 + diff --git a/patches/5.13/0002-mwifiex.patch b/patches/5.13/0002-mwifiex.patch new file mode 100644 index 000000000..7de03dddb --- /dev/null +++ b/patches/5.13/0002-mwifiex.patch @@ -0,0 +1,2520 @@ +From 07bbae758c4f5e2fd5f90d3d2326614cddaee14a Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Mon, 28 Sep 2020 17:46:49 +0900 +Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices + +This commit adds quirk implementation based on DMI matching with DMI +table for Surface devices. + +This implementation can be used for quirks later. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/Makefile | 1 + + drivers/net/wireless/marvell/mwifiex/pcie.c | 4 + + drivers/net/wireless/marvell/mwifiex/pcie.h | 1 + + .../wireless/marvell/mwifiex/pcie_quirks.c | 114 ++++++++++++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.h | 11 ++ + 5 files changed, 131 insertions(+) + create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c + create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h + +diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile +index 162d557b78af..2bd00f40958e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/Makefile ++++ b/drivers/net/wireless/marvell/mwifiex/Makefile +@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o + obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o + + mwifiex_pcie-y += pcie.o ++mwifiex_pcie-y += pcie_quirks.o + obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o + + mwifiex_usb-y += usb.o +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 46517515ba72..a530832c9421 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -27,6 +27,7 @@ + #include "wmm.h" + #include "11n.h" + #include "pcie.h" ++#include "pcie_quirks.h" + + #define PCIE_VERSION "1.0" + #define DRV_NAME "Marvell mwifiex PCIe" +@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return ret; + } + ++ /* check quirks */ ++ mwifiex_initialize_quirks(card); ++ + if (mwifiex_add_card(card, &card->fw_done, &pcie_ops, + MWIFIEX_PCIE, &pdev->dev)) { + pr_err("%s failed\n", __func__); +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h +index 5ed613d65709..981e330c77d7 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.h +@@ -244,6 +244,7 @@ struct pcie_service_card { + unsigned long work_flags; + + bool pci_reset_ongoing; ++ unsigned long quirks; + }; + + static inline int +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +new file mode 100644 +index 000000000000..929aee2b0a60 +--- /dev/null ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -0,0 +1,114 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * File for PCIe quirks. ++ */ ++ ++/* The low-level PCI operations will be performed in this file. Therefore, ++ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g. ++ * to avoid using mwifiex_adapter struct before init or wifi is powered ++ * down, or causes NULL ptr deref). ++ */ ++ ++#include ++ ++#include "pcie_quirks.h" ++ ++/* quirk table based on DMI matching */ ++static const struct dmi_system_id mwifiex_quirk_table[] = { ++ { ++ .ident = "Surface Pro 4", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 5", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 5 (LTE)", ++ .matches = { ++ /* match for SKU here due to generic product name "Surface Pro" */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 6", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Book 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Book 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Laptop 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Laptop 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), ++ }, ++ .driver_data = 0, ++ }, ++ { ++ .ident = "Surface Pro 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"), ++ }, ++ .driver_data = 0, ++ }, ++ {} ++}; ++ ++void mwifiex_initialize_quirks(struct pcie_service_card *card) ++{ ++ struct pci_dev *pdev = card->dev; ++ const struct dmi_system_id *dmi_id; ++ ++ dmi_id = dmi_first_match(mwifiex_quirk_table); ++ if (dmi_id) ++ card->quirks = (uintptr_t)dmi_id->driver_data; ++ ++ if (!card->quirks) ++ dev_info(&pdev->dev, "no quirks enabled\n"); ++} +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +new file mode 100644 +index 000000000000..5326ae7e5671 +--- /dev/null ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -0,0 +1,11 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * Header file for PCIe quirks. ++ */ ++ ++#include "pcie.h" ++ ++/* quirks */ ++// quirk flags can be added here ++ ++void mwifiex_initialize_quirks(struct pcie_service_card *card); +-- +2.32.0 + +From 53a5ae3a76c86d1124f8436f9ce734cf64d7b695 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Tue, 29 Sep 2020 17:25:22 +0900 +Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+ + devices + +To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it +seems that putting the wifi device into D3cold is required according +to errata.inf file on Windows installation (Windows/INF/errata.inf). + +This patch adds a function that performs power-cycle (put into D3cold +then D0) and call the function at the end of reset_prepare(). + +Note: Need to also reset the parent device (bridge) of wifi on SB1; +it might be because the bridge of wifi always reports it's in D3hot. +When I tried to reset only the wifi device (not touching parent), it gave +the following error and the reset failed: + + acpi device:4b: Cannot transition to power state D0 for parent in D3hot + mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible) + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 ++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 73 +++++++++++++++++-- + .../wireless/marvell/mwifiex/pcie_quirks.h | 3 +- + 3 files changed, 74 insertions(+), 9 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index a530832c9421..fbf2b9d30656 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev) + mwifiex_shutdown_sw(adapter); + clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags); + clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags); ++ ++ /* For Surface gen4+ devices, we need to put wifi into D3cold right ++ * before performing FLR ++ */ ++ if (card->quirks & QUIRK_FW_RST_D3COLD) ++ mwifiex_pcie_reset_d3cold_quirk(pdev); ++ + mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__); + + card->pci_reset_ongoing = true; +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index 929aee2b0a60..edc739c542fe 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 5", +@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Pro 6", +@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Book 1", +@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Book 2", +@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Laptop 1", +@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, + { + .ident = "Surface 3", +@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + + if (!card->quirks) + dev_info(&pdev->dev, "no quirks enabled\n"); ++ if (card->quirks & QUIRK_FW_RST_D3COLD) ++ dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++} ++ ++static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) ++{ ++ dev_info(&pdev->dev, "putting into D3cold...\n"); ++ ++ pci_save_state(pdev); ++ if (pci_is_enabled(pdev)) ++ pci_disable_device(pdev); ++ pci_set_power_state(pdev, PCI_D3cold); ++} ++ ++static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev) ++{ ++ int ret; ++ ++ dev_info(&pdev->dev, "putting into D0...\n"); ++ ++ pci_set_power_state(pdev, PCI_D0); ++ ret = pci_enable_device(pdev); ++ if (ret) { ++ dev_err(&pdev->dev, "pci_enable_device failed\n"); ++ return ret; ++ } ++ pci_restore_state(pdev); ++ ++ return 0; ++} ++ ++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) ++{ ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); ++ int ret; ++ ++ /* Power-cycle (put into D3cold then D0) */ ++ dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n"); ++ ++ /* We need to perform power-cycle also for bridge of wifi because ++ * on some devices (e.g. Surface Book 1), the OS for some reasons ++ * can't know the real power state of the bridge. ++ * When tried to power-cycle only wifi, the reset failed with the ++ * following dmesg log: ++ * "Cannot transition to power state D0 for parent in D3hot". ++ */ ++ mwifiex_pcie_set_power_d3cold(pdev); ++ mwifiex_pcie_set_power_d3cold(parent_pdev); ++ ++ ret = mwifiex_pcie_set_power_d0(parent_pdev); ++ if (ret) ++ return ret; ++ ret = mwifiex_pcie_set_power_d0(pdev); ++ if (ret) ++ return ret; ++ ++ return 0; + } +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5326ae7e5671..8b9dcb5070d8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -6,6 +6,7 @@ + #include "pcie.h" + + /* quirks */ +-// quirk flags can be added here ++#define QUIRK_FW_RST_D3COLD BIT(0) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); ++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.32.0 + +From 837a70e7baef080564b1efb2899675a587ee74ab Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Tue, 29 Sep 2020 17:32:22 +0900 +Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3 + +This commit adds reset_wsid quirk and uses this quirk for Surface 3 on +card reset. + +To reset mwifiex on Surface 3, it seems that calling the _DSM method +exists in \_SB.WSID [1] device is required. + +On Surface 3, calling the _DSM method removes/re-probes the card by +itself. So, need to place the reset function before performing FLR and +skip performing any other reset-related works. + +Note that Surface Pro 3 also has the WSID device [2], but it seems to need +more work. This commit only supports Surface 3 yet. + +[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011 +[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216 + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 77 ++++++++++++++++++- + .../wireless/marvell/mwifiex/pcie_quirks.h | 5 ++ + 3 files changed, 91 insertions(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index fbf2b9d30656..10e66d866a0f 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -2967,6 +2967,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; + ++ /* On Surface 3, reset_wsid method removes then re-probes card by ++ * itself. So, need to place it here and skip performing any other ++ * reset-related works. ++ */ ++ if (card->quirks & QUIRK_FW_RST_WSID_S3) { ++ mwifiex_pcie_reset_wsid_quirk(card->dev); ++ /* skip performing any other reset-related works */ ++ return; ++ } ++ + /* We can't afford to wait here; remove() might be waiting on us. If we + * can't grab the device lock, maybe we'll get another chance later. + */ +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index edc739c542fe..f0a6fa0a7ae5 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -9,10 +9,21 @@ + * down, or causes NULL ptr deref). + */ + ++#include + #include + + #include "pcie_quirks.h" + ++/* For reset_wsid quirk */ ++#define ACPI_WSID_PATH "\\_SB.WSID" ++#define WSID_REV 0x0 ++#define WSID_FUNC_WIFI_PWR_OFF 0x1 ++#define WSID_FUNC_WIFI_PWR_ON 0x2 ++/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */ ++static const guid_t wsid_dsm_guid = ++ GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a, ++ 0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef); ++ + /* quirk table based on DMI matching */ + static const struct dmi_system_id mwifiex_quirk_table[] = { + { +@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, +- .driver_data = 0, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, + }, + { + .ident = "Surface Pro 3", +@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_FW_RST_WSID_S3) ++ dev_info(&pdev->dev, ++ "quirk reset_wsid for Surface 3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev) + + return 0; + } ++ ++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev) ++{ ++ acpi_handle handle; ++ union acpi_object *obj; ++ acpi_status status; ++ ++ dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n"); ++ ++ status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "No ACPI handle for path %s\n", ++ ACPI_WSID_PATH); ++ return -ENODEV; ++ } ++ ++ if (!acpi_has_method(handle, "_DSM")) { ++ dev_err(&pdev->dev, "_DSM method not found\n"); ++ return -ENODEV; ++ } ++ ++ if (!acpi_check_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) { ++ dev_err(&pdev->dev, ++ "_DSM method doesn't support wifi power off func\n"); ++ return -ENODEV; ++ } ++ ++ if (!acpi_check_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_ON)) { ++ dev_err(&pdev->dev, ++ "_DSM method doesn't support wifi power on func\n"); ++ return -ENODEV; ++ } ++ ++ /* card will be removed immediately after this call on Surface 3 */ ++ dev_info(&pdev->dev, "turning wifi off...\n"); ++ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_OFF, ++ NULL); ++ if (!obj) { ++ dev_err(&pdev->dev, ++ "device _DSM execution failed for turning wifi off\n"); ++ return -EIO; ++ } ++ ACPI_FREE(obj); ++ ++ /* card will be re-probed immediately after this call on Surface 3 */ ++ dev_info(&pdev->dev, "turning wifi on...\n"); ++ obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid, ++ WSID_REV, WSID_FUNC_WIFI_PWR_ON, ++ NULL); ++ if (!obj) { ++ dev_err(&pdev->dev, ++ "device _DSM execution failed for turning wifi on\n"); ++ return -EIO; ++ } ++ ACPI_FREE(obj); ++ ++ return 0; ++} +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 8b9dcb5070d8..3ef7440418e3 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -7,6 +7,11 @@ + + /* quirks */ + #define QUIRK_FW_RST_D3COLD BIT(0) ++/* Surface 3 and Surface Pro 3 have the same _DSM method but need to ++ * be handled differently. Currently, only S3 is supported. ++ */ ++#define QUIRK_FW_RST_WSID_S3 BIT(1) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); ++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev); +-- +2.32.0 + +From 967fd2b0d7e768d2e60714f785935fdf3acf83a1 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Wed, 30 Sep 2020 18:08:24 +0900 +Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI + table + +(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883) + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI info for the affected Surface 3. + +On affected systems, DMI info will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f0a6fa0a7ae5..34dcd84f02a6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + }, + .driver_data = (void *)QUIRK_FW_RST_WSID_S3, + }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, ++ }, + { + .ident = "Surface Pro 3", + .matches = { +-- +2.32.0 + +From d8702aea0384f3ae1cb2977e6c43cc37a53df31d Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 10e66d866a0f..767032cd696b 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) + { + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; + + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; + } + ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; ++ + return 0; + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index 34dcd84f02a6..a2aeb2af907e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface 3", +@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + if (card->quirks & QUIRK_FW_RST_WSID_S3) + dev_info(&pdev->dev, + "quirk reset_wsid for Surface 3 enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 3ef7440418e3..a95ebac06e13 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -11,6 +11,7 @@ + * be handled differently. Currently, only S3 is supported. + */ + #define QUIRK_FW_RST_WSID_S3 BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.32.0 + +From c2a096f4f724274094e81482152e9361fe91e95b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 767032cd696b..745946f5a519 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1755,9 +1755,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); ++ + /* Write the RX ring read pointer in to reg->rx_rdptr */ + if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | + tx_wrap)) { +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index a2aeb2af907e..6885575826a6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_NO_BRIDGE_D3), ++ QUIRK_NO_BRIDGE_D3 | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface 3", +@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + if (card->quirks & QUIRK_NO_BRIDGE_D3) + dev_info(&pdev->dev, + "quirk no_brigde_d3 enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index a95ebac06e13..4ec2ae72f632 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -12,6 +12,7 @@ + */ + #define QUIRK_FW_RST_WSID_S3 BIT(1) + #define QUIRK_NO_BRIDGE_D3 BIT(2) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(3) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.32.0 + +From c60dac64dda6ac3a344b72e8bca66f25f4043969 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 6d23308119d1..31f255428b4b 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_VALID_LE_STATES 0x800000 + #define BTUSB_QCA_WCN6855 0x1000000 + #define BTUSB_INTEL_NEWGEN 0x2000000 ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -359,6 +360,7 @@ static const struct usb_device_id blacklist_table[] = { + { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, + { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, ++ { USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL }, + + /* Intel Bluetooth devices */ + { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW | +@@ -4672,6 +4674,19 @@ static int btusb_probe(struct usb_interface *intf, + if (id->driver_info & BTUSB_MARVELL) + hdev->set_bdaddr = btusb_set_bdaddr_marvell; + ++ /* The Marvell 88W8897 combined wifi and bluetooth card is known for ++ * very bad bt+wifi coexisting performance. ++ * ++ * Decrease the passive BT Low Energy scan interval a bit ++ * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter ++ * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly ++ * higher wifi throughput while passively scanning for BT LE devices. ++ */ ++ if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) { ++ hdev->le_scan_interval = 0x0190; ++ hdev->le_scan_window = 0x000a; ++ } ++ + if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && + (id->driver_info & BTUSB_MEDIATEK)) { + hdev->setup = btusb_mtk_setup; +-- +2.32.0 + +From 5781bf8b78152c46ef294120deb1952684af4dc1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 12:31:26 +0100 +Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type + changes + +Handle the obvious invalid virtual interface type changes with a general +check instead of looking at the individual change. + +For type changes from P2P_CLIENT to P2P_GO and the other way round, this +changes the behavior slightly: We now still do nothing, but return +-EOPNOTSUPP instead of 0. Now that behavior was incorrect before and +still is, because type changes between these two types are actually +possible and supported, which we'll fix in a following commit. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 39 +++++++------------ + 1 file changed, 14 insertions(+), 25 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 0961f4a5e415..e8deba119ff1 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return -EBUSY; + } + ++ if (type == NL80211_IFTYPE_UNSPECIFIED) { ++ mwifiex_dbg(priv->adapter, INFO, ++ "%s: no new type specified, keeping old type %d\n", ++ dev->name, curr_iftype); ++ return 0; ++ } ++ ++ if (curr_iftype == type) { ++ mwifiex_dbg(priv->adapter, INFO, ++ "%s: interface already is of type %d\n", ++ dev->name, curr_iftype); ++ return 0; ++ } ++ + switch (curr_iftype) { + case NL80211_IFTYPE_ADHOC: + switch (type) { +@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as IBSS\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_ADHOC: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as STA\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_STATION: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_P2P_GO: + return mwifiex_change_vif_to_p2p(dev, curr_iftype, + type, params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as AP\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_AP: /* This shouldn't happen */ +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return -EFAULT; + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +- case NL80211_IFTYPE_UNSPECIFIED: +- mwifiex_dbg(priv->adapter, INFO, +- "%s: kept type as P2P\n", dev->name); +- fallthrough; +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- return 0; + default: + mwifiex_dbg(priv->adapter, ERROR, + "%s: changing to %d not supported\n", +-- +2.32.0 + +From bb0ff1ab207055fa4610ad850f5bbc1dea7c51b1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 10 Nov 2020 12:49:56 +0100 +Subject: [PATCH] mwifiex: Use non-posted PCI register writes + +On the 88W8897 card it's very important the TX ring write pointer is +updated correctly to its new value before setting the TX ready +interrupt, otherwise the firmware appears to crash (probably because +it's trying to DMA-read from the wrong place). + +Since PCI uses "posted writes" when writing to a register, it's not +guaranteed that a write will happen immediately. That means the pointer +might be outdated when setting the TX ready interrupt, leading to +firmware crashes especially when ASPM L1 and L1 substates are enabled +(because of the higher link latency, the write will probably take +longer). + +So fix those firmware crashes by always forcing non-posted writes. We do +that by simply reading back the register after writing it, just as a lot +of other drivers do. + +There are two reproducers that are fixed with this patch: + +1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled +substates are platform dependent), the firmware crashes and eventually a +command timeout appears in the logs. That crash is fixed by using a +non-posted write in mwifiex_pcie_send_data(). + +2) When sending lots of commands to the card, waking it up from sleep in +very quick intervals, the firmware eventually crashes. That crash +appears to be fixed by some other non-posted write included here. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 745946f5a519..9b62937111a1 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data) + + iowrite32(data, card->pci_mmap1 + reg); + ++ /* Do a read-back, which makes the write non-posted, ensuring the ++ * completion before returning. ++ * The firmware of the 88W8897 card is buggy and this avoids crashes. ++ */ ++ ioread32(card->pci_mmap1 + reg); ++ + return 0; + } + +-- +2.32.0 + +From acd52193c0969b3a8a2902ccf118b8e754e9eac4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 12:44:39 +0100 +Subject: [PATCH] mwifiex: Use function to check whether interface type change + is allowed + +Instead of bailing out in the function which is supposed to do the type +change, detect invalid changes beforehand using a generic function and +return an error if the change is not allowed. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 139 ++++++++++++------ + 1 file changed, 92 insertions(+), 47 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index e8deba119ff1..dabc59c47de3 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, + return 0; + } + ++static bool ++is_vif_type_change_allowed(struct mwifiex_adapter *adapter, ++ enum nl80211_iftype old_iftype, ++ enum nl80211_iftype new_iftype) ++{ ++ switch (old_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_STATION: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_AP: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return adapter->curr_iface_comb.sta_intf != ++ adapter->iface_limit.sta_intf; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ return adapter->curr_iface_comb.p2p_intf != ++ adapter->iface_limit.p2p_intf; ++ default: ++ return false; ++ } ++ ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ ++ default: ++ break; ++ } ++ ++ return false; ++} ++ + static int + mwifiex_change_vif_to_p2p(struct net_device *dev, + enum nl80211_iftype curr_iftype, +@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + + adapter = priv->adapter; + +- if (adapter->curr_iface_comb.p2p_intf == +- adapter->iface_limit.p2p_intf) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple P2P ifaces\n"); +- return -1; +- } +- + mwifiex_dbg(adapter, INFO, + "%s: changing role to p2p\n", dev->name); + +@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + + adapter = priv->adapter; + +- if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT && +- curr_iftype != NL80211_IFTYPE_P2P_GO) && +- (adapter->curr_iface_comb.sta_intf == +- adapter->iface_limit.sta_intf)) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple station/adhoc ifaces\n"); +- return -1; +- } +- + if (type == NL80211_IFTYPE_STATION) + mwifiex_dbg(adapter, INFO, + "%s: changing role to station\n", dev->name); +@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + + adapter = priv->adapter; + +- if (adapter->curr_iface_comb.uap_intf == +- adapter->iface_limit.uap_intf) { +- mwifiex_dbg(adapter, ERROR, +- "cannot create multiple AP ifaces\n"); +- return -1; +- } +- + mwifiex_dbg(adapter, INFO, + "%s: changing role to AP\n", dev->name); + +@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return 0; + } + ++ if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) { ++ mwifiex_dbg(priv->adapter, ERROR, ++ "%s: change from type %d to %d is not allowed\n", ++ dev->name, curr_iftype, type); ++ return -EOPNOTSUPP; ++ } ++ + switch (curr_iftype) { + case NL80211_IFTYPE_ADHOC: + switch (type) { +@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_STATION: + switch (type) { + case NL80211_IFTYPE_ADHOC: +@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_ADHOC: +@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_p2p(dev, curr_iftype, + type, params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: + switch (type) { +@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: changing to %d not supported\n", +- dev->name, type); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } +- break; ++ + default: +- mwifiex_dbg(priv->adapter, ERROR, +- "%s: unknown iftype: %d\n", +- dev->name, dev->ieee80211_ptr->iftype); +- return -EOPNOTSUPP; ++ goto errnotsupp; + } + + + return 0; ++ ++errnotsupp: ++ mwifiex_dbg(priv->adapter, ERROR, ++ "unsupported interface type transition: %d to %d\n", ++ curr_iftype, type); ++ return -EOPNOTSUPP; + } + + static void +-- +2.32.0 + +From 0e942160d4624518dac1d10e0516bc4da2216dbd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 13:33:04 +0100 +Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION + vif-type + +We currently handle changing from the P2P to the STATION virtual +interface type slightly different than changing from P2P to ADHOC: When +changing to STATION, we don't send the SET_BSS_MODE command. We do send +that command on all other type-changes though, and it probably makes +sense to send the command since after all we just changed our BSS_MODE. +Looking at prior changes to this part of the code, it seems that this is +simply a leftover from old refactorings. + +Since sending the SET_BSS_MODE command is the only difference between +mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use +mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and +STATION interface type. + +This does not fix any particular bug and just "looked right", so there's +a small chance it might be a regression. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 22 ++++--------------- + 1 file changed, 4 insertions(+), 18 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index dabc59c47de3..146aabe14753 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + + case NL80211_IFTYPE_P2P_CLIENT: + case NL80211_IFTYPE_P2P_GO: ++ if (mwifiex_cfg80211_deinit_p2p(priv)) ++ return -EFAULT; ++ + switch (type) { +- case NL80211_IFTYPE_STATION: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; +- priv->adapter->curr_iface_comb.p2p_intf--; +- priv->adapter->curr_iface_comb.sta_intf++; +- dev->ieee80211_ptr->iftype = type; +- if (mwifiex_deinit_priv_params(priv)) +- return -1; +- if (mwifiex_init_new_priv_params(priv, dev, type)) +- return -1; +- if (mwifiex_sta_init_cmd(priv, false, false)) +- return -1; +- break; + case NL80211_IFTYPE_ADHOC: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; ++ case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); +- break; + case NL80211_IFTYPE_AP: +- if (mwifiex_cfg80211_deinit_p2p(priv)) +- return -EFAULT; + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); + default: +-- +2.32.0 + +From a61208b3e78fc92f005c8656113d7d9c4e4d7bd9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 14:42:54 +0100 +Subject: [PATCH] mwifiex: Use helper function for counting interface types + +Use a small helper function to increment and decrement the counter of +the interface types we currently manage. This makes the code that +actually changes and sets up the interface type a bit less messy and +also helps avoiding mistakes in case someone increments/decrements a +counter wrongly. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 110 ++++++------------ + 1 file changed, 35 insertions(+), 75 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 146aabe14753..8b9517c243c8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, + return false; + } + ++static void ++update_vif_type_counter(struct mwifiex_adapter *adapter, ++ enum nl80211_iftype iftype, ++ int change) ++{ ++ switch (iftype) { ++ case NL80211_IFTYPE_UNSPECIFIED: ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ adapter->curr_iface_comb.sta_intf += change; ++ break; ++ case NL80211_IFTYPE_AP: ++ adapter->curr_iface_comb.uap_intf += change; ++ break; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ case NL80211_IFTYPE_P2P_GO: ++ adapter->curr_iface_comb.p2p_intf += change; ++ break; ++ default: ++ mwifiex_dbg(adapter, ERROR, ++ "%s: Unsupported iftype passed: %d\n", ++ __func__, iftype); ++ break; ++ } ++} ++ + static int + mwifiex_change_vif_to_p2p(struct net_device *dev, + enum nl80211_iftype curr_iftype, +@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.p2p_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; + + return 0; +@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.sta_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; ++ + return 0; + } + +@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- switch (curr_iftype) { +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- default: +- break; +- } +- +- adapter->curr_iface_comb.uap_intf++; ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); + dev->ieee80211_ptr->iftype = type; + return 0; + } +@@ -3128,23 +3121,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + mwifiex_dev_debugfs_init(priv); + #endif + +- switch (type) { +- case NL80211_IFTYPE_UNSPECIFIED: +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf++; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf++; +- break; +- case NL80211_IFTYPE_P2P_CLIENT: +- adapter->curr_iface_comb.p2p_intf++; +- break; +- default: +- /* This should be dead code; checked above */ +- mwifiex_dbg(adapter, ERROR, "type not supported\n"); +- return ERR_PTR(-EINVAL); +- } ++ update_vif_type_counter(adapter, type, +1); + + return &priv->wdev; + +@@ -3210,24 +3187,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev) + /* Clear the priv in adapter */ + priv->netdev = NULL; + +- switch (priv->bss_mode) { +- case NL80211_IFTYPE_UNSPECIFIED: +- case NL80211_IFTYPE_STATION: +- case NL80211_IFTYPE_ADHOC: +- adapter->curr_iface_comb.sta_intf--; +- break; +- case NL80211_IFTYPE_AP: +- adapter->curr_iface_comb.uap_intf--; +- break; +- case NL80211_IFTYPE_P2P_CLIENT: +- case NL80211_IFTYPE_P2P_GO: +- adapter->curr_iface_comb.p2p_intf--; +- break; +- default: +- mwifiex_dbg(adapter, ERROR, +- "del_virtual_intf: type not supported\n"); +- break; +- } ++ update_vif_type_counter(adapter, priv->bss_mode, -1); + + priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED; + +-- +2.32.0 + +From 35a9a12d5369aaaff70b45dc989e45f8054ce02e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:56:58 +0100 +Subject: [PATCH] mwifiex: Update virtual interface counters right after + setting bss_type + +In mwifiex_init_new_priv_params() we update our private driver state to +reflect the currently selected virtual interface type. Most notably we +set the bss_mode to the mode we're going to put the firmware in. + +Now after we updated the driver state we actually start talking to the +firmware and instruct it to set up the new mode. Those commands can and +will sometimes fail, in which case we return with an error from +mwifiex_change_vif_to_*. We currently update our virtual interface type +counters after this return, which means the code is never reached when a +firmware error happens and we never update the counters. Since we have +updated our bss_mode earlier though, the counters now no longer reflect +the actual state of the driver. + +This will break things on the next virtual interface change, because the +virtual interface type we're switching away from didn't get its counter +incremented, and we end up decrementing a 0-counter. + +To fix this, simply update the virtual interface type counters right +after updating our driver structures, so that they are always in sync. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 25 +++++++++++-------- + 1 file changed, 14 insertions(+), 11 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 8b9517c243c8..f2797102c5a2 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; + ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + switch (type) { + case NL80211_IFTYPE_P2P_CLIENT: + if (mwifiex_cfg80211_init_p2p_client(priv)) +@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev, + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; +- + return 0; + } + +@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev, + return -1; + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; ++ ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL, true)) + return -1; + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; +- + return 0; + } + +@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev, + return -1; + if (mwifiex_init_new_priv_params(priv, dev, type)) + return -1; ++ ++ update_vif_type_counter(adapter, curr_iftype, -1); ++ update_vif_type_counter(adapter, type, +1); ++ dev->ieee80211_ptr->iftype = type; ++ + if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE, + HostCmd_ACT_GEN_SET, 0, NULL, true)) + return -1; + if (mwifiex_sta_init_cmd(priv, false, false)) + return -1; + +- update_vif_type_counter(adapter, curr_iftype, -1); +- update_vif_type_counter(adapter, type, +1); +- dev->ieee80211_ptr->iftype = type; + return 0; + } + /* +-- +2.32.0 + +From f5d894195bf684cc47a280d1a19ee7be05b0a57d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 13:42:40 +0100 +Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to + P2P_GO + +It's possible to change virtual interface type between P2P_CLIENT and +P2P_GO, the card supports that just fine, and it happens for example +when using miracast with the miraclecast software. + +So allow type changes between P2P_CLIENT and P2P_GO and simply call into +mwifiex_change_vif_to_p2p(), which handles this just fine. We have to +call mwifiex_cfg80211_deinit_p2p() before though to make sure the old +p2p mode is properly uninitialized. + +Patchset: mwifiex +--- + .../net/wireless/marvell/mwifiex/cfg80211.c | 36 +++++++++++++++++++ + 1 file changed, 36 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index f2797102c5a2..ed4041ff9c89 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter, + } + + case NL80211_IFTYPE_P2P_CLIENT: ++ switch (new_iftype) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return true; ++ case NL80211_IFTYPE_P2P_GO: ++ return true; ++ case NL80211_IFTYPE_AP: ++ return adapter->curr_iface_comb.uap_intf != ++ adapter->iface_limit.uap_intf; ++ default: ++ return false; ++ } ++ + case NL80211_IFTYPE_P2P_GO: + switch (new_iftype) { + case NL80211_IFTYPE_ADHOC: + case NL80211_IFTYPE_STATION: + return true; ++ case NL80211_IFTYPE_P2P_CLIENT: ++ return true; + case NL80211_IFTYPE_AP: + return adapter->curr_iface_comb.uap_intf != + adapter->iface_limit.uap_intf; +@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + } + + case NL80211_IFTYPE_P2P_CLIENT: ++ if (mwifiex_cfg80211_deinit_p2p(priv)) ++ return -EFAULT; ++ ++ switch (type) { ++ case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: ++ return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, ++ type, params); ++ case NL80211_IFTYPE_P2P_GO: ++ return mwifiex_change_vif_to_p2p(dev, curr_iftype, ++ type, params); ++ case NL80211_IFTYPE_AP: ++ return mwifiex_change_vif_to_ap(dev, curr_iftype, type, ++ params); ++ default: ++ goto errnotsupp; ++ } ++ + case NL80211_IFTYPE_P2P_GO: + if (mwifiex_cfg80211_deinit_p2p(priv)) + return -EFAULT; +@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); ++ case NL80211_IFTYPE_P2P_CLIENT: ++ return mwifiex_change_vif_to_p2p(dev, curr_iftype, ++ type, params); + case NL80211_IFTYPE_AP: + return mwifiex_change_vif_to_ap(dev, curr_iftype, type, + params); +-- +2.32.0 + +From a1ddb6ad261b994c998e7c2c027918288d87bc1b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:31:08 +0100 +Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION + +Looks like this case was simply overseen, so handle it, too. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index ed4041ff9c89..64caa5c4350d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy, + case NL80211_IFTYPE_AP: + switch (type) { + case NL80211_IFTYPE_ADHOC: ++ case NL80211_IFTYPE_STATION: + return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype, + type, params); + break; +-- +2.32.0 + +From 4e6b62df178470cd6b742aed5398f24739c8c795 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Fri, 26 Mar 2021 15:32:16 +0100 +Subject: [PATCH] mwifiex: Properly initialize private structure on interface + type changes + +When creating a new virtual interface in mwifiex_add_virtual_intf(), we +update our internal driver states like bss_type, bss_priority, bss_role +and bss_mode to reflect the mode the firmware will be set to. + +When switching virtual interface mode using +mwifiex_init_new_priv_params() though, we currently only update bss_mode +and bss_role. In order for the interface mode switch to actually work, +we also need to update bss_type to its proper value, so do that. + +This fixes a crash of the firmware (because the driver tries to execute +commands that are invalid in AP mode) when switching from station mode +to AP mode. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 64caa5c4350d..0eb31201a82b 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv, + switch (type) { + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_ADHOC: +- priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_type = MWIFIEX_BSS_TYPE_STA; + break; + case NL80211_IFTYPE_P2P_CLIENT: +- priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_role = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + break; + case NL80211_IFTYPE_P2P_GO: +- priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + break; + case NL80211_IFTYPE_AP: + priv->bss_role = MWIFIEX_BSS_ROLE_UAP; ++ priv->bss_type = MWIFIEX_BSS_TYPE_UAP; + break; + default: + mwifiex_dbg(adapter, ERROR, +-- +2.32.0 + +From 65a77c93cdb3b647e8724f8ca8e9bb41d530f454 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sat, 27 Mar 2021 12:19:14 +0100 +Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual + interface + +The BSS priority here for a new P2P_CLIENT device was accidentally set +to an enum that's certainly not meant for this. Since +MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0 +instead here. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 0eb31201a82b..d62a20de3ada 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -3054,7 +3054,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy, + priv->bss_type = MWIFIEX_BSS_TYPE_P2P; + + priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II; +- priv->bss_priority = MWIFIEX_BSS_ROLE_STA; ++ priv->bss_priority = 0; + priv->bss_role = MWIFIEX_BSS_ROLE_STA; + priv->bss_started = 0; + +-- +2.32.0 + +From b8641d4348ed2775f24a0fd88b74da6ada8f4a11 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sun, 28 Mar 2021 21:10:06 +0200 +Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt + +It seems that the firmware of the 88W8897 card sometimes ignores or +misses when we try to wake it up by reading the firmware status +register. This leads to the firmware wakeup timeout expiring and the +driver resetting the card because we assume the firmware has hung up or +crashed (unfortunately that's not unlikely with this card). + +Turns out that most of the time the firmware actually didn't hang up, +but simply "missed" our wakeup request and doesn't send us an AWAKE +event. + +Trying again to read the firmware status register after a short timeout +usually makes the firmware wake we up as expected, so add a small retry +loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to +check whether the card woke up. + +The number of tries and timeout lengths for this were determined +experimentally: The firmware usually takes about 500 us to wake up +after we attempt to read the status register. In some cases where the +firmware is very busy (for example while doing a bluetooth scan) it +might even miss our requests for multiple milliseconds, which is why +after 15 tries the waiting time gets increased to 10 ms. The maximum +number of tries it took to wake the firmware when testing this was +around 20, so a maximum number of 50 tries should give us plenty of +safety margin. + +A good reproducer for this issue is letting the firmware sleep and wake +up in very short intervals, for example by pinging an device on the +network every 0.1 seconds. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++----- + 1 file changed, 23 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 9b62937111a1..0a52549a1703 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; ++ int n_tries = 0; + + mwifiex_dbg(adapter, EVENT, + "event: Wakeup device...\n"); +@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter) + if (reg->sleep_cookie) + mwifiex_pcie_dev_wakeup_delay(adapter); + +- /* Accessing fw_status register will wakeup device */ +- if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { +- mwifiex_dbg(adapter, ERROR, +- "Writing fw_status register failed\n"); +- return -1; +- } ++ /* Access the fw_status register to wake up the device. ++ * Since the 88W8897 firmware sometimes appears to ignore or miss ++ * that wakeup request, we continue trying until we receive an ++ * interrupt from the card. ++ */ ++ do { ++ if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) { ++ mwifiex_dbg(adapter, ERROR, ++ "Writing fw_status register failed\n"); ++ return -1; ++ } ++ ++ n_tries++; ++ ++ if (n_tries <= 15) ++ usleep_range(400, 700); ++ else ++ msleep(10); ++ } while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0); ++ ++ mwifiex_dbg(adapter, EVENT, ++ "event: Tried %d times until firmware woke up\n", n_tries); + + if (reg->sleep_cookie) { + mwifiex_pcie_dev_wakeup_delay(adapter); +-- +2.32.0 + +From 3e7c4015f1501d6866b9327c59f07c22f59178b1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 14:30:28 +0200 +Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was + activated manually + +When powersaving (so either wifi powersaving or deep sleep, depending on +which state the firmware is in) is disabled, the way the firmware goes +into host sleep is different: Usually the firmware implicitely enters +host sleep on the next SLEEP event we get when we configured host sleep +via HSCFG before. When powersaving is disabled though, there are no +SLEEP events, the way we enter host sleep in that case is different: The +firmware will send us a HS_ACT_REQ event and after that we "manually" +make the firmware enter host sleep by sending it another HSCFG command +with the action HS_ACTIVATE. + +Now waking up from host sleep appears to be different depending on +whether powersaving is enabled again: When powersaving is enabled, the +firmware implicitely leaves host sleep as soon as it wakes up and sends +us an AWAKE event. When powersaving is disabled though, it apparently +doesn't implicitely leave host sleep, but instead we need to send it a +HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL +condition. We didn't do that so far, which is why waking up from host +sleep was broken when powersaving is disabled. + +So add some additional state to mwifiex_adapter where we keep track of +whether host sleep was activated manually via HS_ACTIVATE, and if that +was the case, deactivate it manually again via HS_CFG_CANCEL. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.c | 18 ++++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.h | 1 + + .../net/wireless/marvell/mwifiex/sta_cmd.c | 4 ++++ + 4 files changed, 44 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c +index 3a11342a6bde..5487df8f994d 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c ++++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c +@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no, + return -1; + } + ++ if (priv->adapter->hs_activated_manually && ++ cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) { ++ mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD); ++ priv->adapter->hs_activated_manually = false; ++ } + + /* Get a new command node */ + cmd_node = mwifiex_get_cmd_node(adapter); +@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter, + } + } + ++ /* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */ ++ if (command == HostCmd_CMD_802_11_HS_CFG_ENH) { ++ struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg = ++ &host_cmd->params.opt_hs_cfg; ++ ++ if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE) ++ add_tail = false; ++ } ++ + spin_lock_bh(&adapter->cmd_pending_q_lock); + if (add_tail) + list_add_tail(&cmd_node->list, &adapter->cmd_pending_q); +@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter) + __func__); + + adapter->if_ops.wakeup(adapter); ++ ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + adapter->hs_activated = false; + clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags); + clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags); +diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c +index 17399d4aa129..1fbf5ba1042b 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.c ++++ b/drivers/net/wireless/marvell/mwifiex/main.c +@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !adapter->scan_processing) && + !adapter->data_sent && + !skb_queue_empty(&adapter->tx_data_q)) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_process_tx_queue(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !mwifiex_bypass_txlist_empty(adapter) && + !mwifiex_is_tdls_chan_switching + (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_process_bypass_tx(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) && + !mwifiex_is_tdls_chan_switching + (mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) { ++ if (adapter->hs_activated_manually) { ++ mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY), ++ MWIFIEX_ASYNC_CMD); ++ adapter->hs_activated_manually = false; ++ } ++ + mwifiex_wmm_process_tx(adapter); + if (adapter->hs_activated) { + clear_bit(MWIFIEX_IS_HS_CONFIGURED, +diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h +index 5923c5c14c8d..90012cbcfd15 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.h ++++ b/drivers/net/wireless/marvell/mwifiex/main.h +@@ -986,6 +986,7 @@ struct mwifiex_adapter { + struct timer_list wakeup_timer; + struct mwifiex_hs_config_param hs_cfg; + u8 hs_activated; ++ u8 hs_activated_manually; + u16 hs_activate_wait_q_woken; + wait_queue_head_t hs_activate_wait_q; + u8 event_body[MAX_EVENT_SIZE]; +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +index d3a968ef21ef..76db9a7b8199 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c +@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv, + if (hs_activate) { + hs_cfg->action = cpu_to_le16(HS_ACTIVATE); + hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED); ++ ++ adapter->hs_activated_manually = true; ++ mwifiex_dbg(priv->adapter, CMD, ++ "cmd: Activating host sleep manually\n"); + } else { + hs_cfg->action = cpu_to_le16(HS_CONFIGURE); + hs_cfg->params.hs_config.conditions = hscfg_param->conditions; +-- +2.32.0 + +From 14c11ae85ab085ca03492f51118907e94f08695c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 14:23:05 +0200 +Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain + hardware revision + +The 88W8897 pcie card with the hardware revision 20 apparently has a +hardware issue where the card wakes up from deep sleep randomly and very +often, somewhat depending on the card activity, maybe the hardware has a +floating wakeup pin or something. + +Those continuous wakeups prevent the card from entering host sleep when +the computer suspends. And because the host won't answer to events from +the card anymore while it's suspended, the firmwares internal +powersaving state machine seems to get confused and the card can't sleep +anymore at all after that. + +Since we can't work around that hardware bug in the firmware, let's +get the hardware revision string from the firmware and match it with +known bad revisions. Then disable auto deep sleep for those revisions, +which makes sure we no longer get those spurious wakeups. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/main.c | 14 ++++++++++++++ + drivers/net/wireless/marvell/mwifiex/main.h | 1 + + .../net/wireless/marvell/mwifiex/sta_cmdresp.c | 16 ++++++++++++++++ + 3 files changed, 31 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c +index 1fbf5ba1042b..be40813ffa5c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.c ++++ b/drivers/net/wireless/marvell/mwifiex/main.c +@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter) + return 0; + } + ++static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter) ++{ ++ struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA); ++ struct mwifiex_ver_ext ver_ext; ++ ++ set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags); ++ ++ memset(&ver_ext, 0, sizeof(ver_ext)); ++ ver_ext.version_str_sel = 1; ++ mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT, ++ HostCmd_ACT_GEN_GET, 0, &ver_ext, false); ++} ++ + /* + * The main process. + * +@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter) + if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) { + adapter->hw_status = MWIFIEX_HW_STATUS_READY; + mwifiex_init_fw_complete(adapter); ++ maybe_quirk_fw_disable_ds(adapter); + } + } + +diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h +index 90012cbcfd15..1e829d84b1f6 100644 +--- a/drivers/net/wireless/marvell/mwifiex/main.h ++++ b/drivers/net/wireless/marvell/mwifiex/main.h +@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags { + MWIFIEX_IS_SUSPENDED, + MWIFIEX_IS_HS_CONFIGURED, + MWIFIEX_IS_HS_ENABLING, ++ MWIFIEX_IS_REQUESTING_FW_VEREXT, + }; + + struct mwifiex_band_config { +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c +index 6b5d35d9e69f..8e49ebca1847 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c +@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv, + { + struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext; + ++ if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) { ++ if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) { ++ struct mwifiex_ds_auto_ds auto_ds = { ++ .auto_ds = DEEP_SLEEP_OFF, ++ }; ++ ++ mwifiex_dbg(priv->adapter, MSG, ++ "Bad HW revision detected, disabling deep sleep\n"); ++ ++ mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH, ++ DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false); ++ } ++ ++ return 0; ++ } ++ + if (version_ext) { + version_ext->version_str_sel = ver_ext->version_str_sel; + memcpy(version_ext->version_str, ver_ext->version_str, +-- +2.32.0 + +From f4927099d23ae17141d6a2ebebf72c914bb0da0c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Wed, 11 Nov 2020 15:17:07 +0100 +Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is + disabled + +It's not an error if someone chooses to put their computer to sleep, not +wanting it to wake up because the person next door has just discovered +what a magic packet is. So change the loglevel of this annoying message +from ERROR to INFO. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index d62a20de3ada..18b1a6d54bc8 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -3494,7 +3494,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy, + } + + if (!wowlan) { +- mwifiex_dbg(adapter, ERROR, ++ mwifiex_dbg(adapter, INFO, + "None of the WOWLAN triggers enabled\n"); + ret = 0; + goto done; +-- +2.32.0 + +From 1c5fdbc1a62c0a87385fc8c7d47deba0ea47dce1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Sun, 28 Mar 2021 21:42:54 +0200 +Subject: [PATCH] mwifiex: Log an error on command failure during key-material + upload + +Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware +(when this happens exactly seems pretty random). This appears to prevent +the access point from starting, so it seems like a good idea to log an +error in that case. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +index 18b1a6d54bc8..c00791701d78 100644 +--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c ++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c +@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy, + encrypt_key.is_igtk_def_key = true; + eth_broadcast_addr(encrypt_key.mac_addr); + +- return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, +- HostCmd_ACT_GEN_SET, true, &encrypt_key, true); ++ if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL, ++ HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) { ++ mwifiex_dbg(priv->adapter, ERROR, ++ "Sending KEY_MATERIAL command failed\n"); ++ return -1; ++ } ++ ++ return 0; + } + + /* +-- +2.32.0 + +From 367630ee1d6ff10d8fc4eaf74088e4178cc3017e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:44:03 +0200 +Subject: [PATCH] mwifiex: Fix an incorrect comment + +We're sending DELBA requests here, not ADDBA requests. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/11n.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c +index 6696bce56178..b0695432b26a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/11n.c ++++ b/drivers/net/wireless/marvell/mwifiex/11n.c +@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv, + tx_ba_tbl->ra); + } else { /* + * In case of failure, recreate the deleted stream in case +- * we initiated the ADDBA ++ * we initiated the DELBA + */ + if (!INITIATOR_BIT(del_ba_param_set)) + return 0; +-- +2.32.0 + +From 43daa26dd03044a5a9584920ad08d6413b3930c7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:45:59 +0200 +Subject: [PATCH] mwifiex: Send DELBA requests according to spec + +We're currently failing to set the initiator bit for DELBA requests: +While we set the bit on our del_ba_param_set bitmask, we forget to +actually copy that bitmask over to the command struct, which means we +never actually set the initiator bit. + +Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command +struct. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c +index b0695432b26a..9ff2058bcd7e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/11n.c ++++ b/drivers/net/wireless/marvell/mwifiex/11n.c +@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac, + uint16_t del_ba_param_set; + + memset(&delba, 0, sizeof(delba)); +- delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS); + +- del_ba_param_set = le16_to_cpu(delba.del_ba_param_set); ++ del_ba_param_set = tid << DELBA_TID_POS; ++ + if (initiator) + del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK; + else + del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK; + ++ delba.del_ba_param_set = cpu_to_le16(del_ba_param_set); + memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN); + + /* We don't wait for the response of this command */ +-- +2.32.0 + +From 2ccda81141448162d8f2e88d101c6f384fb208b1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 13 Apr 2021 12:57:41 +0200 +Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware + +The firmware of the pcie 88W8897 chip sends those events very +unreliably, which means we sometimes end up actually capping the window +size while bluetooth is disabled, artifically limiting wifi speeds even +though it's not needed. + +Since we can't fix the firmware, let's just ignore those events, it +seems that the Windows driver also doesn't change the rx/tx block ack +buffer sizes when bluetooth gets enabled or disabled, so this is +consistent with the Windows driver. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c +index 68c63268e2e6..933111a3511c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/sta_event.c ++++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c +@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv) + adapter->event_skb); + break; + case EVENT_BT_COEX_WLAN_PARA_CHANGE: +- dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n"); +- mwifiex_bt_coex_wlan_param_update_event(priv, +- adapter->event_skb); ++ dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n"); + break; + case EVENT_RXBA_SYNC: + dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n"); +-- +2.32.0 + diff --git a/patches/5.13/0003-ath10k.patch b/patches/5.13/0003-ath10k.patch new file mode 100644 index 000000000..aaf788f7b --- /dev/null +++ b/patches/5.13/0003-ath10k.patch @@ -0,0 +1,121 @@ +From 8b750d4b78c5c6db76fb445fe47149a679383acf Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 2f9be182fbfb..84ae17af3f98 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -35,6 +35,9 @@ static bool skip_otp; + static bool rawmode; + static bool fw_diag_log; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644); + module_param(fw_diag_log, bool, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath"); + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + snprintf(filename, sizeof(filename), "%s/%s", dir, file); + ret = firmware_request_nowarn(&fw, filename, ar->dev); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", +-- +2.32.0 + diff --git a/patches/5.13/0004-ipts.patch b/patches/5.13/0004-ipts.patch new file mode 100644 index 000000000..696a2f63b --- /dev/null +++ b/patches/5.13/0004-ipts.patch @@ -0,0 +1,1503 @@ +From a0ee4c030c8ffcc704f1523e44eff9e4c8d29d9a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] misc: mei: Add missing IPTS device IDs + +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index cb34925e10f1..2b3f8073a3ec 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ + +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index c3393b383e59..0098f98426c1 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)}, +-- +2.32.0 + +From b7af6adf0faba83088a76a6e96601047e32de6d7 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 6 Aug 2020 11:20:41 +0200 +Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus + +Based on linux-surface/intel-precise-touch@3f362c + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/misc/Kconfig | 1 + + drivers/misc/Makefile | 1 + + drivers/misc/ipts/Kconfig | 17 ++ + drivers/misc/ipts/Makefile | 12 ++ + drivers/misc/ipts/context.h | 47 +++++ + drivers/misc/ipts/control.c | 113 +++++++++++ + drivers/misc/ipts/control.h | 24 +++ + drivers/misc/ipts/mei.c | 125 ++++++++++++ + drivers/misc/ipts/protocol.h | 347 ++++++++++++++++++++++++++++++++++ + drivers/misc/ipts/receiver.c | 224 ++++++++++++++++++++++ + drivers/misc/ipts/receiver.h | 16 ++ + drivers/misc/ipts/resources.c | 128 +++++++++++++ + drivers/misc/ipts/resources.h | 17 ++ + drivers/misc/ipts/uapi.c | 208 ++++++++++++++++++++ + drivers/misc/ipts/uapi.h | 47 +++++ + 15 files changed, 1327 insertions(+) + create mode 100644 drivers/misc/ipts/Kconfig + create mode 100644 drivers/misc/ipts/Makefile + create mode 100644 drivers/misc/ipts/context.h + create mode 100644 drivers/misc/ipts/control.c + create mode 100644 drivers/misc/ipts/control.h + create mode 100644 drivers/misc/ipts/mei.c + create mode 100644 drivers/misc/ipts/protocol.h + create mode 100644 drivers/misc/ipts/receiver.c + create mode 100644 drivers/misc/ipts/receiver.h + create mode 100644 drivers/misc/ipts/resources.c + create mode 100644 drivers/misc/ipts/resources.h + create mode 100644 drivers/misc/ipts/uapi.c + create mode 100644 drivers/misc/ipts/uapi.h + +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index f4fb5c52b863..0e5a2fe6bffe 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -464,4 +464,5 @@ source "drivers/misc/cardreader/Kconfig" + source "drivers/misc/habanalabs/Kconfig" + source "drivers/misc/uacce/Kconfig" + source "drivers/misc/pvpanic/Kconfig" ++source "drivers/misc/ipts/Kconfig" + endmenu +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index e92a56d4442f..7adce5540183 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -57,3 +57,4 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ + obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o ++obj-$(CONFIG_MISC_IPTS) += ipts/ +diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig +new file mode 100644 +index 000000000000..83e2a930c396 +--- /dev/null ++++ b/drivers/misc/ipts/Kconfig +@@ -0,0 +1,17 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++config MISC_IPTS ++ tristate "Intel Precise Touch & Stylus" ++ depends on INTEL_MEI ++ help ++ Say Y here if your system has a touchscreen using Intels ++ Precise Touch & Stylus (IPTS) technology. ++ ++ If unsure say N. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ipts. ++ ++ Building this driver alone will not give you a working touchscreen. ++ It only exposed a userspace API that can be used by a daemon to ++ receive and process data from the touchscreen hardware. +diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile +new file mode 100644 +index 000000000000..8f58b9adbc94 +--- /dev/null ++++ b/drivers/misc/ipts/Makefile +@@ -0,0 +1,12 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++# ++# Makefile for the IPTS touchscreen driver ++# ++ ++obj-$(CONFIG_MISC_IPTS) += ipts.o ++ipts-objs := control.o ++ipts-objs += mei.o ++ipts-objs += receiver.o ++ipts-objs += resources.o ++ipts-objs += uapi.o ++ +diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h +new file mode 100644 +index 000000000000..f4b06a2d3f72 +--- /dev/null ++++ b/drivers/misc/ipts/context.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_CONTEXT_H_ ++#define _IPTS_CONTEXT_H_ ++ ++#include ++#include ++#include ++#include ++ ++#include "protocol.h" ++ ++enum ipts_host_status { ++ IPTS_HOST_STATUS_STARTING, ++ IPTS_HOST_STATUS_STARTED, ++ IPTS_HOST_STATUS_STOPPING, ++ IPTS_HOST_STATUS_STOPPED, ++}; ++ ++struct ipts_buffer_info { ++ u8 *address; ++ dma_addr_t dma_address; ++}; ++ ++struct ipts_context { ++ struct mei_cl_device *cldev; ++ struct device *dev; ++ ++ bool restart; ++ enum ipts_host_status status; ++ struct ipts_get_device_info_rsp device_info; ++ ++ struct ipts_buffer_info data[IPTS_BUFFERS]; ++ struct ipts_buffer_info doorbell; ++ ++ struct ipts_buffer_info feedback[IPTS_BUFFERS]; ++ struct ipts_buffer_info workqueue; ++ struct ipts_buffer_info host2me; ++}; ++ ++#endif /* _IPTS_CONTEXT_H_ */ +diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c +new file mode 100644 +index 000000000000..a1d1f97a13d7 +--- /dev/null ++++ b/drivers/misc/ipts/control.c +@@ -0,0 +1,113 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++ ++#include "context.h" ++#include "protocol.h" ++#include "resources.h" ++#include "uapi.h" ++ ++int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload, ++ size_t size) ++{ ++ int ret; ++ struct ipts_command cmd; ++ ++ memset(&cmd, 0, sizeof(struct ipts_command)); ++ cmd.code = code; ++ ++ if (payload && size > 0) ++ memcpy(&cmd.payload, payload, size); ++ ++ ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size); ++ if (ret >= 0) ++ return 0; ++ ++ /* ++ * During shutdown the device might get pulled away from below our feet. ++ * Dont log an error in this case, because it will confuse people. ++ */ ++ if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING) ++ dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret); ++ ++ return ret; ++} ++ ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer) ++{ ++ struct ipts_feedback_cmd cmd; ++ ++ memset(&cmd, 0, sizeof(struct ipts_feedback_cmd)); ++ cmd.buffer = buffer; ++ ++ return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd, ++ sizeof(struct ipts_feedback_cmd)); ++} ++ ++int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value) ++{ ++ struct ipts_feedback_buffer *feedback; ++ ++ memset(ipts->host2me.address, 0, ipts->device_info.feedback_size); ++ feedback = (struct ipts_feedback_buffer *)ipts->host2me.address; ++ ++ feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE; ++ feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES; ++ feedback->buffer = IPTS_HOST2ME_BUFFER; ++ feedback->size = 2; ++ feedback->payload[0] = report; ++ feedback->payload[1] = value; ++ ++ return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER); ++} ++ ++int ipts_control_start(struct ipts_context *ipts) ++{ ++ if (ipts->status != IPTS_HOST_STATUS_STOPPED) ++ return -EBUSY; ++ ++ dev_info(ipts->dev, "Starting IPTS\n"); ++ ipts->status = IPTS_HOST_STATUS_STARTING; ++ ipts->restart = false; ++ ++ ipts_uapi_link(ipts); ++ return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0); ++} ++ ++int ipts_control_stop(struct ipts_context *ipts) ++{ ++ int ret; ++ ++ if (ipts->status == IPTS_HOST_STATUS_STOPPING) ++ return -EBUSY; ++ ++ if (ipts->status == IPTS_HOST_STATUS_STOPPED) ++ return -EBUSY; ++ ++ dev_info(ipts->dev, "Stopping IPTS\n"); ++ ipts->status = IPTS_HOST_STATUS_STOPPING; ++ ++ ipts_uapi_unlink(); ++ ipts_resources_free(ipts); ++ ++ ret = ipts_control_send_feedback(ipts, 0); ++ if (ret == -ENODEV) ++ ipts->status = IPTS_HOST_STATUS_STOPPED; ++ ++ return ret; ++} ++ ++int ipts_control_restart(struct ipts_context *ipts) ++{ ++ if (ipts->restart) ++ return -EBUSY; ++ ++ ipts->restart = true; ++ return ipts_control_stop(ipts); ++} +diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h +new file mode 100644 +index 000000000000..2c44e9e0e99f +--- /dev/null ++++ b/drivers/misc/ipts/control.h +@@ -0,0 +1,24 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_CONTROL_H_ ++#define _IPTS_CONTROL_H_ ++ ++#include ++ ++#include "context.h" ++ ++int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload, ++ size_t size); ++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer); ++int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value); ++int ipts_control_start(struct ipts_context *ipts); ++int ipts_control_restart(struct ipts_context *ipts); ++int ipts_control_stop(struct ipts_context *ipts); ++ ++#endif /* _IPTS_CONTROL_H_ */ +diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c +new file mode 100644 +index 000000000000..59ecf13e00d2 +--- /dev/null ++++ b/drivers/misc/ipts/mei.c +@@ -0,0 +1,125 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "protocol.h" ++#include "receiver.h" ++#include "uapi.h" ++ ++static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev) ++{ ++ int ret; ++ ++ ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64)); ++ if (!ret) ++ return 0; ++ ++ return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32)); ++} ++ ++static int ipts_mei_probe(struct mei_cl_device *cldev, ++ const struct mei_cl_device_id *id) ++{ ++ int ret; ++ struct ipts_context *ipts; ++ ++ if (ipts_mei_set_dma_mask(cldev)) { ++ dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n"); ++ return -EFAULT; ++ } ++ ++ ret = mei_cldev_enable(cldev); ++ if (ret) { ++ dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret); ++ return ret; ++ } ++ ++ ipts = kzalloc(sizeof(*ipts), GFP_KERNEL); ++ if (!ipts) { ++ mei_cldev_disable(cldev); ++ return -ENOMEM; ++ } ++ ++ ipts->cldev = cldev; ++ ipts->dev = &cldev->dev; ++ ipts->status = IPTS_HOST_STATUS_STOPPED; ++ ++ mei_cldev_set_drvdata(cldev, ipts); ++ mei_cldev_register_rx_cb(cldev, ipts_receiver_callback); ++ ++ return ipts_control_start(ipts); ++} ++ ++static void ipts_mei_remove(struct mei_cl_device *cldev) ++{ ++ int i; ++ struct ipts_context *ipts = mei_cldev_get_drvdata(cldev); ++ ++ ipts_control_stop(ipts); ++ ++ for (i = 0; i < 20; i++) { ++ if (ipts->status == IPTS_HOST_STATUS_STOPPED) ++ break; ++ ++ msleep(25); ++ } ++ ++ mei_cldev_disable(cldev); ++ kfree(ipts); ++} ++ ++static struct mei_cl_device_id ipts_mei_device_id_table[] = { ++ { "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table); ++ ++static struct mei_cl_driver ipts_mei_driver = { ++ .id_table = ipts_mei_device_id_table, ++ .name = "ipts", ++ .probe = ipts_mei_probe, ++ .remove = ipts_mei_remove, ++}; ++ ++static int __init ipts_mei_init(void) ++{ ++ int ret; ++ ++ ret = ipts_uapi_init(); ++ if (ret) ++ return ret; ++ ++ ret = mei_cldev_driver_register(&ipts_mei_driver); ++ if (ret) { ++ ipts_uapi_free(); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void __exit ipts_mei_exit(void) ++{ ++ mei_cldev_driver_unregister(&ipts_mei_driver); ++ ipts_uapi_free(); ++} ++ ++MODULE_DESCRIPTION("IPTS touchscreen driver"); ++MODULE_AUTHOR("Dorian Stoll "); ++MODULE_LICENSE("GPL"); ++ ++module_init(ipts_mei_init); ++module_exit(ipts_mei_exit); +diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h +new file mode 100644 +index 000000000000..c3458904a94d +--- /dev/null ++++ b/drivers/misc/ipts/protocol.h +@@ -0,0 +1,347 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_PROTOCOL_H_ ++#define _IPTS_PROTOCOL_H_ ++ ++#include ++ ++/* ++ * The MEI client ID for IPTS functionality. ++ */ ++#define IPTS_MEI_UUID \ ++ UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94, \ ++ 0x02, 0xae, 0x04) ++ ++/* ++ * Queries the device for vendor specific information. ++ * ++ * The command must not contain any payload. ++ * The response will contain struct ipts_get_device_info_rsp as payload. ++ */ ++#define IPTS_CMD_GET_DEVICE_INFO 0x00000001 ++#define IPTS_RSP_GET_DEVICE_INFO 0x80000001 ++ ++/* ++ * Sets the mode that IPTS will operate in. ++ * ++ * The command must contain struct ipts_set_mode_cmd as payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_SET_MODE 0x00000002 ++#define IPTS_RSP_SET_MODE 0x80000002 ++ ++/* ++ * Configures the memory buffers that the ME will use ++ * for passing data to the host. ++ * ++ * The command must contain struct ipts_set_mem_window_cmd as payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_SET_MEM_WINDOW 0x00000003 ++#define IPTS_RSP_SET_MEM_WINDOW 0x80000003 ++ ++/* ++ * Signals that the host is ready to receive data to the ME. ++ * ++ * The command must not contain any payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_READY_FOR_DATA 0x00000005 ++#define IPTS_RSP_READY_FOR_DATA 0x80000005 ++ ++/* ++ * Signals that a buffer can be refilled to the ME. ++ * ++ * The command must contain struct ipts_feedback_cmd as payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_FEEDBACK 0x00000006 ++#define IPTS_RSP_FEEDBACK 0x80000006 ++ ++/* ++ * Resets the data flow from the ME to the hosts and ++ * clears the buffers that were set with SET_MEM_WINDOW. ++ * ++ * The command must not contain any payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007 ++#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007 ++ ++/* ++ * Instructs the ME to reset the touch sensor. ++ * ++ * The command must contain struct ipts_reset_sensor_cmd as payload. ++ * The response will not contain any payload. ++ */ ++#define IPTS_CMD_RESET_SENSOR 0x0000000B ++#define IPTS_RSP_RESET_SENSOR 0x8000000B ++ ++/** ++ * enum ipts_status - Possible status codes returned by IPTS commands. ++ * @IPTS_STATUS_SUCCESS: Operation completed successfully. ++ * @IPTS_STATUS_INVALID_PARAMS: Command contained a payload with invalid parameters. ++ * @IPTS_STATUS_ACCESS_DENIED: ME could not validate buffer addresses supplied by host. ++ * @IPTS_STATUS_CMD_SIZE_ERROR: Command contains an invalid payload. ++ * @IPTS_STATUS_NOT_READY: Buffer addresses have not been set. ++ * @IPTS_STATUS_REQUEST_OUTSTANDING: There is an outstanding command of the same type. ++ * The host must wait for a response before sending another ++ * command of the same type. ++ * @IPTS_STATUS_NO_SENSOR_FOUND: No sensor could be found. Either no sensor is connected, it ++ * has not been initialized yet, or the system is improperly ++ * configured. ++ * @IPTS_STATUS_OUT_OF_MEMORY: Not enough free memory for requested operation. ++ * @IPTS_STATUS_INTERNAL_ERROR: An unexpected error occurred. ++ * @IPTS_STATUS_SENSOR_DISABLED: The sensor has been disabled and must be reinitialized. ++ * @IPTS_STATUS_COMPAT_CHECK_FAIL: Compatibility revision check between sensor and ME failed. ++ * The host can ignore this error and attempt to continue. ++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET: The sensor went through a reset initiated by ME or host. ++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset. ++ * @IPTS_STATUS_RESET_FAILED: Requested sensor reset failed to complete. ++ * @IPTS_STATUS_TIMEOUT: The operation timed out. ++ * @IPTS_STATUS_TEST_MODE_FAIL: Test mode pattern did not match expected values. ++ * @IPTS_STATUS_SENSOR_FAIL_FATAL: The sensor reported a fatal error during reset sequence. ++ * Further progress is not possible. ++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL: The sensor reported a fatal error during reset sequence. ++ * The host can attempt to continue. ++ * @IPTS_STATUS_INVALID_DEVICE_CAPS: The device reported invalid capabilities. ++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS: Command cannot be completed until Quiesce IO is done. ++ */ ++enum ipts_status { ++ IPTS_STATUS_SUCCESS = 0, ++ IPTS_STATUS_INVALID_PARAMS = 1, ++ IPTS_STATUS_ACCESS_DENIED = 2, ++ IPTS_STATUS_CMD_SIZE_ERROR = 3, ++ IPTS_STATUS_NOT_READY = 4, ++ IPTS_STATUS_REQUEST_OUTSTANDING = 5, ++ IPTS_STATUS_NO_SENSOR_FOUND = 6, ++ IPTS_STATUS_OUT_OF_MEMORY = 7, ++ IPTS_STATUS_INTERNAL_ERROR = 8, ++ IPTS_STATUS_SENSOR_DISABLED = 9, ++ IPTS_STATUS_COMPAT_CHECK_FAIL = 10, ++ IPTS_STATUS_SENSOR_EXPECTED_RESET = 11, ++ IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12, ++ IPTS_STATUS_RESET_FAILED = 13, ++ IPTS_STATUS_TIMEOUT = 14, ++ IPTS_STATUS_TEST_MODE_FAIL = 15, ++ IPTS_STATUS_SENSOR_FAIL_FATAL = 16, ++ IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17, ++ IPTS_STATUS_INVALID_DEVICE_CAPS = 18, ++ IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19, ++}; ++ ++/* ++ * The amount of buffers that is used for IPTS ++ */ ++#define IPTS_BUFFERS 16 ++ ++/* ++ * The special buffer ID that is used for direct host2me feedback. ++ */ ++#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS ++ ++/** ++ * enum ipts_mode - Operation mode for IPTS hardware ++ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus. ++ * The data is received as a HID report with ID 64. ++ * @IPTS_MODE_MULTITOUCH: The "proper" operation mode for IPTS. It will return ++ * stylus data as well as capacitive heatmap touch data. ++ * This data needs to be processed in userspace. ++ */ ++enum ipts_mode { ++ IPTS_MODE_SINGLETOUCH = 0, ++ IPTS_MODE_MULTITOUCH = 1, ++}; ++ ++/** ++ * struct ipts_set_mode_cmd - Payload for the SET_MODE command. ++ * @mode: The mode that IPTS should operate in. ++ */ ++struct ipts_set_mode_cmd { ++ enum ipts_mode mode; ++ u8 reserved[12]; ++} __packed; ++ ++#define IPTS_WORKQUEUE_SIZE 8192 ++#define IPTS_WORKQUEUE_ITEM_SIZE 16 ++ ++/** ++ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command. ++ * @data_buffer_addr_lower: Lower 32 bits of the data buffer addresses. ++ * @data_buffer_addr_upper: Upper 32 bits of the data buffer addresses. ++ * @workqueue_addr_lower: Lower 32 bits of the workqueue buffer address. ++ * @workqueue_addr_upper: Upper 32 bits of the workqueue buffer address. ++ * @doorbell_addr_lower: Lower 32 bits of the doorbell buffer address. ++ * @doorbell_addr_upper: Upper 32 bits of the doorbell buffer address. ++ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses. ++ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses. ++ * @host2me_addr_lower: Lower 32 bits of the host2me buffer address. ++ * @host2me_addr_upper: Upper 32 bits of the host2me buffer address. ++ * @workqueue_item_size: Magic value. (IPTS_WORKQUEUE_ITEM_SIZE) ++ * @workqueue_size: Magic value. (IPTS_WORKQUEUE_SIZE) ++ * ++ * The data buffers are buffers that get filled with touch data by the ME. ++ * The doorbell buffer is a u32 that gets incremented by the ME once a data ++ * buffer has been filled with new data. ++ * ++ * The other buffers are required for using GuC submission with binary ++ * firmware. Since support for GuC submission has been dropped from i915, ++ * they are not used anymore, but they need to be allocated and passed, ++ * otherwise the hardware will refuse to start. ++ */ ++struct ipts_set_mem_window_cmd { ++ u32 data_buffer_addr_lower[IPTS_BUFFERS]; ++ u32 data_buffer_addr_upper[IPTS_BUFFERS]; ++ u32 workqueue_addr_lower; ++ u32 workqueue_addr_upper; ++ u32 doorbell_addr_lower; ++ u32 doorbell_addr_upper; ++ u32 feedback_buffer_addr_lower[IPTS_BUFFERS]; ++ u32 feedback_buffer_addr_upper[IPTS_BUFFERS]; ++ u32 host2me_addr_lower; ++ u32 host2me_addr_upper; ++ u32 host2me_size; ++ u8 reserved1; ++ u8 workqueue_item_size; ++ u16 workqueue_size; ++ u8 reserved[32]; ++} __packed; ++ ++/** ++ * struct ipts_feedback_cmd - Payload for the FEEDBACK command. ++ * @buffer: The buffer that the ME should refill. ++ */ ++struct ipts_feedback_cmd { ++ u32 buffer; ++ u8 reserved[12]; ++} __packed; ++ ++/** ++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback. ++ */ ++enum ipts_feedback_cmd_type { ++ IPTS_FEEDBACK_CMD_TYPE_NONE = 0, ++ IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4, ++ IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5, ++ IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6, ++}; ++ ++/** ++ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains. ++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR: The buffer contains vendor specific feedback. ++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES: The buffer contains a HID set features command. ++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES: The buffer contains a HID get features command. ++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report. ++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA: The buffer contains calibration data for the sensor. ++ */ ++enum ipts_feedback_data_type { ++ IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0, ++ IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1, ++ IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2, ++ IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3, ++ IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4, ++}; ++ ++/** ++ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer. ++ * @cmd_type: A command that should be executed on the sensor. ++ * @size: The size of the payload to be written. ++ * @buffer: The ID of the buffer that contains this feedback data. ++ * @protocol: The protocol version of the EDS. ++ * @data_type: The type of payload that the buffer contains. ++ * @spi_offset: The offset at which to write the payload data. ++ * @payload: Payload for the feedback command, or 0 if no payload is sent. ++ */ ++struct ipts_feedback_buffer { ++ enum ipts_feedback_cmd_type cmd_type; ++ u32 size; ++ u32 buffer; ++ u32 protocol; ++ enum ipts_feedback_data_type data_type; ++ u32 spi_offset; ++ u8 reserved[40]; ++ u8 payload[]; ++} __packed; ++ ++/** ++ * enum ipts_reset_type - Possible ways of resetting the touch sensor ++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin. ++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface. ++ */ ++enum ipts_reset_type { ++ IPTS_RESET_TYPE_HARD = 0, ++ IPTS_RESET_TYPE_SOFT = 1, ++}; ++ ++/** ++ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command. ++ * @type: What type of reset should be performed. ++ */ ++struct ipts_reset_sensor_cmd { ++ enum ipts_reset_type type; ++ u8 reserved[4]; ++} __packed; ++ ++/** ++ * struct ipts_command - A message sent from the host to the ME. ++ * @code: The message code describing the command. (see IPTS_CMD_*) ++ * @payload: Payload for the command, or 0 if no payload is required. ++ */ ++struct ipts_command { ++ u32 code; ++ u8 payload[320]; ++} __packed; ++ ++/** ++ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response. ++ * @vendor_id: Vendor ID of the touch sensor. ++ * @device_id: Device ID of the touch sensor. ++ * @hw_rev: Hardware revision of the touch sensor. ++ * @fw_rev: Firmware revision of the touch sensor. ++ * @data_size: Required size of one data buffer. ++ * @feedback_size: Required size of one feedback buffer. ++ * @mode: Current operation mode of IPTS. ++ * @max_contacts: The amount of concurrent touches supported by the sensor. ++ */ ++struct ipts_get_device_info_rsp { ++ u16 vendor_id; ++ u16 device_id; ++ u32 hw_rev; ++ u32 fw_rev; ++ u32 data_size; ++ u32 feedback_size; ++ enum ipts_mode mode; ++ u8 max_contacts; ++ u8 reserved[19]; ++} __packed; ++ ++/** ++ * struct ipts_feedback_rsp - Payload for the FEEDBACK response. ++ * @buffer: The buffer that has received feedback. ++ */ ++struct ipts_feedback_rsp { ++ u32 buffer; ++} __packed; ++ ++/** ++ * struct ipts_response - A message sent from the ME to the host. ++ * @code: The message code describing the response. (see IPTS_RSP_*) ++ * @status: The status code returned by the command. ++ * @payload: Payload returned by the command. ++ */ ++struct ipts_response { ++ u32 code; ++ enum ipts_status status; ++ u8 payload[80]; ++} __packed; ++ ++#endif /* _IPTS_PROTOCOL_H_ */ +diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c +new file mode 100644 +index 000000000000..23dca13c2139 +--- /dev/null ++++ b/drivers/misc/ipts/receiver.c +@@ -0,0 +1,224 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "protocol.h" ++#include "resources.h" ++ ++/* ++ * Temporary parameter to guard gen7 multitouch mode. ++ * Remove once gen7 has stable iptsd support. ++ */ ++static bool gen7mt; ++module_param(gen7mt, bool, 0644); ++ ++static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts, ++ struct ipts_response *rsp) ++{ ++ struct ipts_set_mode_cmd cmd; ++ ++ memcpy(&ipts->device_info, rsp->payload, ++ sizeof(struct ipts_get_device_info_rsp)); ++ ++ memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd)); ++ cmd.mode = IPTS_MODE_MULTITOUCH; ++ ++ return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd, ++ sizeof(struct ipts_set_mode_cmd)); ++} ++ ++static int ipts_receiver_handle_set_mode(struct ipts_context *ipts) ++{ ++ int i, ret; ++ struct ipts_set_mem_window_cmd cmd; ++ ++ ret = ipts_resources_alloc(ipts); ++ if (ret) { ++ dev_err(ipts->dev, "Failed to allocate resources\n"); ++ return ret; ++ } ++ ++ memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd)); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ cmd.data_buffer_addr_lower[i] = ++ lower_32_bits(ipts->data[i].dma_address); ++ ++ cmd.data_buffer_addr_upper[i] = ++ upper_32_bits(ipts->data[i].dma_address); ++ ++ cmd.feedback_buffer_addr_lower[i] = ++ lower_32_bits(ipts->feedback[i].dma_address); ++ ++ cmd.feedback_buffer_addr_upper[i] = ++ upper_32_bits(ipts->feedback[i].dma_address); ++ } ++ ++ cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address); ++ cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address); ++ ++ cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address); ++ cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address); ++ ++ cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address); ++ cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address); ++ ++ cmd.workqueue_size = IPTS_WORKQUEUE_SIZE; ++ cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE; ++ ++ return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd, ++ sizeof(struct ipts_set_mem_window_cmd)); ++} ++ ++static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts) ++{ ++ int ret; ++ ++ dev_info(ipts->dev, "Device %04hX:%04hX ready\n", ++ ipts->device_info.vendor_id, ipts->device_info.device_id); ++ ipts->status = IPTS_HOST_STATUS_STARTED; ++ ++ ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0); ++ if (ret) ++ return ret; ++ ++ if (!gen7mt) ++ return 0; ++ ++ return ipts_control_set_feature(ipts, 0x5, 0x1); ++} ++ ++static int ipts_receiver_handle_feedback(struct ipts_context *ipts, ++ struct ipts_response *rsp) ++{ ++ struct ipts_feedback_rsp feedback; ++ ++ if (ipts->status != IPTS_HOST_STATUS_STOPPING) ++ return 0; ++ ++ memcpy(&feedback, rsp->payload, sizeof(feedback)); ++ ++ if (feedback.buffer < IPTS_BUFFERS - 1) ++ return ipts_control_send_feedback(ipts, feedback.buffer + 1); ++ ++ return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0); ++} ++ ++static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts) ++{ ++ ipts->status = IPTS_HOST_STATUS_STOPPED; ++ ++ if (ipts->restart) ++ return ipts_control_start(ipts); ++ ++ return 0; ++} ++ ++static bool ipts_receiver_sensor_was_reset(u32 status) ++{ ++ return status == IPTS_STATUS_SENSOR_EXPECTED_RESET || ++ status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET; ++} ++ ++static bool ipts_receiver_handle_error(struct ipts_context *ipts, ++ struct ipts_response *rsp) ++{ ++ bool error; ++ ++ switch (rsp->status) { ++ case IPTS_STATUS_SUCCESS: ++ case IPTS_STATUS_COMPAT_CHECK_FAIL: ++ error = false; ++ break; ++ case IPTS_STATUS_INVALID_PARAMS: ++ error = rsp->code != IPTS_RSP_FEEDBACK; ++ break; ++ case IPTS_STATUS_SENSOR_DISABLED: ++ error = ipts->status != IPTS_HOST_STATUS_STOPPING; ++ break; ++ default: ++ error = true; ++ break; ++ } ++ ++ if (!error) ++ return false; ++ ++ dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code, ++ rsp->status); ++ ++ if (ipts_receiver_sensor_was_reset(rsp->status)) { ++ dev_err(ipts->dev, "Sensor was reset\n"); ++ ++ if (ipts_control_restart(ipts)) ++ dev_err(ipts->dev, "Failed to restart IPTS\n"); ++ } ++ ++ return true; ++} ++ ++static void ipts_receiver_handle_response(struct ipts_context *ipts, ++ struct ipts_response *rsp) ++{ ++ int ret; ++ ++ if (ipts_receiver_handle_error(ipts, rsp)) ++ return; ++ ++ switch (rsp->code) { ++ case IPTS_RSP_GET_DEVICE_INFO: ++ ret = ipts_receiver_handle_get_device_info(ipts, rsp); ++ break; ++ case IPTS_RSP_SET_MODE: ++ ret = ipts_receiver_handle_set_mode(ipts); ++ break; ++ case IPTS_RSP_SET_MEM_WINDOW: ++ ret = ipts_receiver_handle_set_mem_window(ipts); ++ break; ++ case IPTS_RSP_FEEDBACK: ++ ret = ipts_receiver_handle_feedback(ipts, rsp); ++ break; ++ case IPTS_RSP_CLEAR_MEM_WINDOW: ++ ret = ipts_receiver_handle_clear_mem_window(ipts); ++ break; ++ default: ++ ret = 0; ++ break; ++ } ++ ++ if (!ret) ++ return; ++ ++ dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n", ++ rsp->code, ret); ++ ++ if (ipts_control_stop(ipts)) ++ dev_err(ipts->dev, "Failed to stop IPTS\n"); ++} ++ ++void ipts_receiver_callback(struct mei_cl_device *cldev) ++{ ++ int ret; ++ struct ipts_response rsp; ++ struct ipts_context *ipts; ++ ++ ipts = mei_cldev_get_drvdata(cldev); ++ ++ ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response)); ++ if (ret <= 0) { ++ dev_err(ipts->dev, "Error while reading response: %d\n", ret); ++ return; ++ } ++ ++ ipts_receiver_handle_response(ipts, &rsp); ++} +diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h +new file mode 100644 +index 000000000000..7f075afa7ef8 +--- /dev/null ++++ b/drivers/misc/ipts/receiver.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_RECEIVER_H_ ++#define _IPTS_RECEIVER_H_ ++ ++#include ++ ++void ipts_receiver_callback(struct mei_cl_device *cldev); ++ ++#endif /* _IPTS_RECEIVER_H_ */ +diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c +new file mode 100644 +index 000000000000..8e3a2409e438 +--- /dev/null ++++ b/drivers/misc/ipts/resources.c +@@ -0,0 +1,128 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++ ++#include "context.h" ++ ++void ipts_resources_free(struct ipts_context *ipts) ++{ ++ int i; ++ struct ipts_buffer_info *buffers; ++ ++ u32 data_buffer_size = ipts->device_info.data_size; ++ u32 feedback_buffer_size = ipts->device_info.feedback_size; ++ ++ buffers = ipts->data; ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ if (!buffers[i].address) ++ continue; ++ ++ dma_free_coherent(ipts->dev, data_buffer_size, ++ buffers[i].address, buffers[i].dma_address); ++ ++ buffers[i].address = NULL; ++ buffers[i].dma_address = 0; ++ } ++ ++ buffers = ipts->feedback; ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ if (!buffers[i].address) ++ continue; ++ ++ dma_free_coherent(ipts->dev, feedback_buffer_size, ++ buffers[i].address, buffers[i].dma_address); ++ ++ buffers[i].address = NULL; ++ buffers[i].dma_address = 0; ++ } ++ ++ if (ipts->doorbell.address) { ++ dma_free_coherent(ipts->dev, sizeof(u32), ++ ipts->doorbell.address, ++ ipts->doorbell.dma_address); ++ ++ ipts->doorbell.address = NULL; ++ ipts->doorbell.dma_address = 0; ++ } ++ ++ if (ipts->workqueue.address) { ++ dma_free_coherent(ipts->dev, sizeof(u32), ++ ipts->workqueue.address, ++ ipts->workqueue.dma_address); ++ ++ ipts->workqueue.address = NULL; ++ ipts->workqueue.dma_address = 0; ++ } ++ ++ if (ipts->host2me.address) { ++ dma_free_coherent(ipts->dev, feedback_buffer_size, ++ ipts->host2me.address, ++ ipts->host2me.dma_address); ++ ++ ipts->host2me.address = NULL; ++ ipts->host2me.dma_address = 0; ++ } ++} ++ ++int ipts_resources_alloc(struct ipts_context *ipts) ++{ ++ int i; ++ struct ipts_buffer_info *buffers; ++ ++ u32 data_buffer_size = ipts->device_info.data_size; ++ u32 feedback_buffer_size = ipts->device_info.feedback_size; ++ ++ buffers = ipts->data; ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ buffers[i].address = ++ dma_alloc_coherent(ipts->dev, data_buffer_size, ++ &buffers[i].dma_address, GFP_KERNEL); ++ ++ if (!buffers[i].address) ++ goto release_resources; ++ } ++ ++ buffers = ipts->feedback; ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ buffers[i].address = ++ dma_alloc_coherent(ipts->dev, feedback_buffer_size, ++ &buffers[i].dma_address, GFP_KERNEL); ++ ++ if (!buffers[i].address) ++ goto release_resources; ++ } ++ ++ ipts->doorbell.address = ++ dma_alloc_coherent(ipts->dev, sizeof(u32), ++ &ipts->doorbell.dma_address, GFP_KERNEL); ++ ++ if (!ipts->doorbell.address) ++ goto release_resources; ++ ++ ipts->workqueue.address = ++ dma_alloc_coherent(ipts->dev, sizeof(u32), ++ &ipts->workqueue.dma_address, GFP_KERNEL); ++ ++ if (!ipts->workqueue.address) ++ goto release_resources; ++ ++ ipts->host2me.address = ++ dma_alloc_coherent(ipts->dev, feedback_buffer_size, ++ &ipts->host2me.dma_address, GFP_KERNEL); ++ ++ if (!ipts->workqueue.address) ++ goto release_resources; ++ ++ return 0; ++ ++release_resources: ++ ++ ipts_resources_free(ipts); ++ return -ENOMEM; ++} +diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h +new file mode 100644 +index 000000000000..fdac0eee9156 +--- /dev/null ++++ b/drivers/misc/ipts/resources.h +@@ -0,0 +1,17 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_RESOURCES_H_ ++#define _IPTS_RESOURCES_H_ ++ ++#include "context.h" ++ ++int ipts_resources_alloc(struct ipts_context *ipts); ++void ipts_resources_free(struct ipts_context *ipts); ++ ++#endif /* _IPTS_RESOURCES_H_ */ +diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c +new file mode 100644 +index 000000000000..598f0710ad64 +--- /dev/null ++++ b/drivers/misc/ipts/uapi.c +@@ -0,0 +1,208 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "context.h" ++#include "control.h" ++#include "protocol.h" ++#include "uapi.h" ++ ++struct ipts_uapi uapi; ++ ++static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count, ++ loff_t *offset) ++{ ++ int buffer; ++ int maxbytes; ++ struct ipts_context *ipts = uapi.ipts; ++ ++ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ maxbytes = ipts->device_info.data_size - *offset; ++ if (maxbytes <= 0 || count > maxbytes) ++ return -EINVAL; ++ ++ if (copy_to_user(buf, ipts->data[buffer].address + *offset, count)) ++ return -EFAULT; ++ ++ return count; ++} ++ ++static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts, ++ unsigned long arg) ++{ ++ void __user *buffer = (void __user *)arg; ++ u8 ready = 0; ++ ++ if (ipts) ++ ready = ipts->status == IPTS_HOST_STATUS_STARTED; ++ ++ if (copy_to_user(buffer, &ready, sizeof(u8))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts, ++ unsigned long arg) ++{ ++ struct ipts_device_info info; ++ void __user *buffer = (void __user *)arg; ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ info.vendor = ipts->device_info.vendor_id; ++ info.product = ipts->device_info.device_id; ++ info.version = ipts->device_info.fw_rev; ++ info.buffer_size = ipts->device_info.data_size; ++ info.max_contacts = ipts->device_info.max_contacts; ++ ++ if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts, ++ unsigned long arg) ++{ ++ void __user *buffer = (void __user *)arg; ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32))) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts, ++ struct file *file) ++{ ++ int ret; ++ u32 buffer; ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ buffer = MINOR(file->f_path.dentry->d_inode->i_rdev); ++ ++ ret = ipts_control_send_feedback(ipts, buffer); ++ if (ret) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts) ++{ ++ int ret; ++ struct ipts_reset_sensor_cmd cmd; ++ ++ if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED) ++ return -ENODEV; ++ ++ memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd)); ++ cmd.type = IPTS_RESET_TYPE_SOFT; ++ ++ ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd, ++ sizeof(struct ipts_reset_sensor_cmd)); ++ ++ if (ret) ++ return -EFAULT; ++ ++ return 0; ++} ++ ++static long ipts_uapi_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct ipts_context *ipts = uapi.ipts; ++ ++ switch (cmd) { ++ case IPTS_IOCTL_GET_DEVICE_READY: ++ return ipts_uapi_ioctl_get_device_ready(ipts, arg); ++ case IPTS_IOCTL_GET_DEVICE_INFO: ++ return ipts_uapi_ioctl_get_device_info(ipts, arg); ++ case IPTS_IOCTL_GET_DOORBELL: ++ return ipts_uapi_ioctl_get_doorbell(ipts, arg); ++ case IPTS_IOCTL_SEND_FEEDBACK: ++ return ipts_uapi_ioctl_send_feedback(ipts, file); ++ case IPTS_IOCTL_SEND_RESET: ++ return ipts_uapi_ioctl_send_reset(ipts); ++ default: ++ return -ENOTTY; ++ } ++} ++ ++static const struct file_operations ipts_uapi_fops = { ++ .owner = THIS_MODULE, ++ .read = ipts_uapi_read, ++ .unlocked_ioctl = ipts_uapi_ioctl, ++#ifdef CONFIG_COMPAT ++ .compat_ioctl = ipts_uapi_ioctl, ++#endif ++}; ++ ++void ipts_uapi_link(struct ipts_context *ipts) ++{ ++ uapi.ipts = ipts; ++} ++ ++void ipts_uapi_unlink(void) ++{ ++ uapi.ipts = NULL; ++} ++ ++int ipts_uapi_init(void) ++{ ++ int i, major; ++ ++ alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts"); ++ uapi.class = class_create(THIS_MODULE, "ipts"); ++ ++ major = MAJOR(uapi.dev); ++ ++ cdev_init(&uapi.cdev, &ipts_uapi_fops); ++ uapi.cdev.owner = THIS_MODULE; ++ cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) { ++ device_create(uapi.class, NULL, MKDEV(major, i), NULL, ++ "ipts/%d", i); ++ } ++ ++ return 0; ++} ++ ++void ipts_uapi_free(void) ++{ ++ int i; ++ int major; ++ ++ major = MAJOR(uapi.dev); ++ ++ for (i = 0; i < IPTS_BUFFERS; i++) ++ device_destroy(uapi.class, MKDEV(major, i)); ++ ++ cdev_del(&uapi.cdev); ++ ++ unregister_chrdev_region(MKDEV(major, 0), MINORMASK); ++ class_destroy(uapi.class); ++} +diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h +new file mode 100644 +index 000000000000..53fb86a88f97 +--- /dev/null ++++ b/drivers/misc/ipts/uapi.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (c) 2016 Intel Corporation ++ * Copyright (c) 2020 Dorian Stoll ++ * ++ * Linux driver for Intel Precise Touch & Stylus ++ */ ++ ++#ifndef _IPTS_UAPI_H_ ++#define _IPTS_UAPI_H_ ++ ++#include ++ ++#include "context.h" ++ ++struct ipts_uapi { ++ dev_t dev; ++ struct class *class; ++ struct cdev cdev; ++ ++ struct ipts_context *ipts; ++}; ++ ++struct ipts_device_info { ++ __u16 vendor; ++ __u16 product; ++ __u32 version; ++ __u32 buffer_size; ++ __u8 max_contacts; ++ ++ /* For future expansion */ ++ __u8 reserved[19]; ++}; ++ ++#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8) ++#define IPTS_IOCTL_GET_DEVICE_INFO _IOR(0x86, 0x02, struct ipts_device_info) ++#define IPTS_IOCTL_GET_DOORBELL _IOR(0x86, 0x03, __u32) ++#define IPTS_IOCTL_SEND_FEEDBACK _IO(0x86, 0x04) ++#define IPTS_IOCTL_SEND_RESET _IO(0x86, 0x05) ++ ++void ipts_uapi_link(struct ipts_context *ipts); ++void ipts_uapi_unlink(void); ++ ++int ipts_uapi_init(void); ++void ipts_uapi_free(void); ++ ++#endif /* _IPTS_UAPI_H_ */ +-- +2.32.0 + diff --git a/patches/5.13/0005-surface-sam-over-hid.patch b/patches/5.13/0005-surface-sam-over-hid.patch new file mode 100644 index 000000000..0fd3b4c16 --- /dev/null +++ b/patches/5.13/0005-surface-sam-over-hid.patch @@ -0,0 +1,335 @@ +From 27f215439b8fed6978c7b8aa4ab5c06ffe767d8a Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index 8ceaa88dd78f..deceed0d76c6 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -570,6 +570,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, + return (ret == 1) ? 0 : -EIO; + } + ++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client, ++ u8 *data, u8 data_len) ++{ ++ struct i2c_msg msgs[1]; ++ int ret = AE_OK; ++ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = client->flags; ++ msgs[0].len = data_len + 1; ++ msgs[0].buf = data; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ ++ if (ret < 0) { ++ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret); ++ return ret; ++ } ++ ++ /* 1 transfer must have completed successfully */ ++ return (ret == 1) ? 0 : -EIO; ++} ++ + static acpi_status + i2c_acpi_space_handler(u32 function, acpi_physical_address command, + u32 bits, u64 *value64, +@@ -671,6 +693,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, + } + break; + ++ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: ++ if (action == ACPI_READ) { ++ dev_warn(&adapter->dev, ++ "protocol 0x%02x not supported for client 0x%02x\n", ++ accessor_type, client->addr); ++ ret = AE_BAD_PARAMETER; ++ goto err; ++ } else { ++ status = acpi_gsb_i2c_write_raw_bytes(client, ++ gsb->data, info->access_length); ++ } ++ break; ++ + default: + dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", + accessor_type, client->addr); +-- +2.32.0 + +From 2daa0bd68fa4ce027977b160d8301cd69fc68cde Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ + 3 files changed, 170 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index 3105f651614f..53beaedefdd1 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -104,6 +104,13 @@ config SURFACE_AGGREGATOR_REGISTRY + the respective client devices. Drivers for these devices still need to + be selected via the other options. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 32889482de55..0cc63440328d 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -11,6 +11,7 @@ obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o + obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ + obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 000000000000..8b816ed8f35c +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,162 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++ ++ ++#ifdef pr_fmt ++#undef pr_fmt ++#endif ++#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ ++ ++ ++static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *ret; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!ret) ++ return -EINVAL; ++ ++ ACPI_FREE(ret); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(void) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ pr_err("failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ if (buf.pointer) ++ ACPI_FREE(buf.pointer); ++ ++ pr_info("turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(void) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ pr_err("failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ if (buf.pointer) ++ ACPI_FREE(buf.pointer); ++ ++ pr_info("turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ int status, value; ++ ++ status = kstrtoint(buf, 0, &value); ++ if (status < 0) ++ return status; ++ ++ if (value != 1) ++ return -EINVAL; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(); ++ else ++ status = sb1_dgpu_sw_hgof(); ++ ++ return status < 0 ? status : len; ++} ++ ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL, ++}; ++ ++static const struct attribute_group sb1_dgpu_sw_attr_group = { ++ .attrs = sb1_dgpu_sw_attrs, ++}; ++ ++ ++static int sb1_dgpu_sw_probe(struct platform_device *pdev) ++{ ++ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); ++} ++ ++static int sb1_dgpu_sw_remove(struct platform_device *pdev) ++{ ++ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); ++ return 0; ++} ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .probe = sb1_dgpu_sw_probe, ++ .remove = sb1_dgpu_sw_remove, ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.32.0 + diff --git a/patches/5.13/0006-surface-sam.patch b/patches/5.13/0006-surface-sam.patch new file mode 100644 index 000000000..b840360e8 --- /dev/null +++ b/patches/5.13/0006-surface-sam.patch @@ -0,0 +1,2324 @@ +From 529419e047ed78101c90bfe75b9aef900c71a47e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 23 May 2021 14:09:42 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node + groups for 5th- and 6th-gen devices + +5th- and 6th-generation Surface devices have all SAM clients defined in +ACPI, except for the platform profile/performance mode which his handled +via the WSID (Windows Surface Integration Device). Thus, the node groups +for those devices are the same and we can just use a single one instead +of re-defining the same one over and over again. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 47 +++++-------------- + 1 file changed, 12 insertions(+), 35 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index ef83461fa536..4428c4330229 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { + .parent = &ssam_node_hub_base, + }; + +-/* Devices for Surface Book 2. */ +-static const struct software_node *ssam_node_group_sb2[] = { ++/* ++ * Devices for 5th- and 6th-generations models: ++ * - Surface Book 2, ++ * - Surface Laptop 1 and 2, ++ * - Surface Pro 5 and 6. ++ */ ++static const struct software_node *ssam_node_group_gen5[] = { + &ssam_node_root, + &ssam_node_tmp_pprof, + NULL, +@@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { + NULL, + }; + +-/* Devices for Surface Laptop 1. */ +-static const struct software_node *ssam_node_group_sl1[] = { +- &ssam_node_root, +- &ssam_node_tmp_pprof, +- NULL, +-}; +- +-/* Devices for Surface Laptop 2. */ +-static const struct software_node *ssam_node_group_sl2[] = { +- &ssam_node_root, +- &ssam_node_tmp_pprof, +- NULL, +-}; +- + /* Devices for Surface Laptop 3 and 4. */ + static const struct software_node *ssam_node_group_sl3[] = { + &ssam_node_root, +@@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { + NULL, + }; + +-/* Devices for Surface Pro 5. */ +-static const struct software_node *ssam_node_group_sp5[] = { +- &ssam_node_root, +- &ssam_node_tmp_pprof, +- NULL, +-}; +- +-/* Devices for Surface Pro 6. */ +-static const struct software_node *ssam_node_group_sp6[] = { +- &ssam_node_root, +- &ssam_node_tmp_pprof, +- NULL, +-}; +- + /* Devices for Surface Pro 7 and Surface Pro 7+. */ + static const struct software_node *ssam_node_group_sp7[] = { + &ssam_node_root, +@@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { + + static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ +- { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, ++ { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, + + /* Surface Pro 6 (OMBR >= 0x10) */ +- { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, ++ { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, + + /* Surface Pro 7 */ + { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, +@@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, + + /* Surface Book 2 */ +- { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, ++ { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, + + /* Surface Book 3 */ + { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, + + /* Surface Laptop 1 */ +- { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, ++ { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, + + /* Surface Laptop 2 */ +- { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, ++ { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, + + /* Surface Laptop 3 (13", Intel) */ + { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, +-- +2.32.0 + +From 7cfa1fee5d33055aac80df5c5fd98d923cf48bb6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:49 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow registering notifiers + without enabling events + +Currently, each SSAM event notifier is directly tied to one group of +events. This makes sense as registering a notifier will automatically +take care of enabling the corresponding event group and normally drivers +only need notifications for a very limited number of events, associated +with different callbacks for each group. + +However, there are rare cases, especially for debugging, when we want to +get notifications for a whole event target category instead of just a +single group of events in that category. Registering multiple notifiers, +i.e. one per group, may be infeasible due to two issues: a) we might not +know every event enable/disable specification as some events are +auto-enabled by the EC and b) forwarding this to the same callback will +lead to duplicate events as we might not know the full event +specification to perform the appropriate filtering. + +This commit introduces observer-notifiers, which are notifiers that are +not tied to a specific event group and do not attempt to manage any +events. In other words, they can be registered without enabling any +event group or incrementing the corresponding reference count and just +act as silent observers, listening to all currently/previously enabled +events based on their match-specification. + +Essentially, this allows us to register one single notifier for a full +event target category, meaning that we can process all events of that +target category in a single callback without duplication. Specifically, +this will be used in the cdev debug interface to forward events to +user-space via a device file from which the events can be read. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210604134755.535590-2-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../platform/surface/aggregator/controller.c | 69 +++++++++++-------- + include/linux/surface_aggregator/controller.h | 17 +++++ + 2 files changed, 58 insertions(+), 28 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index a06964aa96e7..cd3a6b77f48d 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) + * @ctrl: The controller to register the notifier on. + * @n: The event notifier to register. + * +- * Register an event notifier and increment the usage counter of the +- * associated SAM event. If the event was previously not enabled, it will be +- * enabled during this call. ++ * Register an event notifier. Increment the usage counter of the associated ++ * SAM event if the notifier is not marked as an observer. If the event is not ++ * marked as an observer and is currently not enabled, it will be enabled ++ * during this call. If the notifier is marked as an observer, no attempt will ++ * be made at enabling any event and no reference count will be modified. ++ * ++ * Notifiers marked as observers do not need to be associated with one specific ++ * event, i.e. as long as no event matching is performed, only the event target ++ * category needs to be set. + * + * Return: Returns zero on success, %-ENOSPC if there have already been + * %INT_MAX notifiers for the event ID/type associated with the notifier block +@@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) + * for the specific associated event, returns the status of the event-enable + * EC-command. + */ +-int ssam_notifier_register(struct ssam_controller *ctrl, +- struct ssam_event_notifier *n) ++int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) + { + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); +- struct ssam_nf_refcount_entry *entry; ++ struct ssam_nf_refcount_entry *entry = NULL; + struct ssam_nf_head *nf_head; + struct ssam_nf *nf; + int status; +@@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, + + mutex_lock(&nf->lock); + +- entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); +- if (IS_ERR(entry)) { +- mutex_unlock(&nf->lock); +- return PTR_ERR(entry); +- } ++ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { ++ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); ++ if (IS_ERR(entry)) { ++ mutex_unlock(&nf->lock); ++ return PTR_ERR(entry); ++ } + +- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", +- n->event.reg.target_category, n->event.id.target_category, +- n->event.id.instance, entry->refcount); ++ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", ++ n->event.reg.target_category, n->event.id.target_category, ++ n->event.id.instance, entry->refcount); ++ } + + status = ssam_nfblk_insert(nf_head, &n->base); + if (status) { +- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); +- if (entry->refcount == 0) +- kfree(entry); ++ if (entry) { ++ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); ++ if (entry->refcount == 0) ++ kfree(entry); ++ } + + mutex_unlock(&nf->lock); + return status; + } + +- if (entry->refcount == 1) { +- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, +- n->event.flags); ++ if (entry && entry->refcount == 1) { ++ status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); + if (status) { + ssam_nfblk_remove(&n->base); + kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); +@@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl, + + entry->flags = n->event.flags; + +- } else if (entry->flags != n->event.flags) { ++ } else if (entry && entry->flags != n->event.flags) { + ssam_warn(ctrl, + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + n->event.flags, entry->flags, n->event.reg.target_category, +@@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); + * @ctrl: The controller the notifier has been registered on. + * @n: The event notifier to unregister. + * +- * Unregister an event notifier and decrement the usage counter of the +- * associated SAM event. If the usage counter reaches zero, the event will be +- * disabled. ++ * Unregister an event notifier. Decrement the usage counter of the associated ++ * SAM event if the notifier is not marked as an observer. If the usage counter ++ * reaches zero, the event will be disabled. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +-int ssam_notifier_unregister(struct ssam_controller *ctrl, +- struct ssam_event_notifier *n) ++int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) + { + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; +@@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, + return -ENOENT; + } + ++ /* ++ * If this is an observer notifier, do not attempt to disable the ++ * event, just remove it. ++ */ ++ if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) ++ goto remove; ++ + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (WARN_ON(!entry)) { + /* +@@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, + } + + if (entry->refcount == 0) { +- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, +- n->event.flags); ++ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); + kfree(entry); + } + +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index 0806796eabcb..cf4bb48a850e 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -795,6 +795,20 @@ enum ssam_event_mask { + #define SSAM_EVENT_REGISTRY_REG \ + SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) + ++/** ++ * enum ssam_event_notifier_flags - Flags for event notifiers. ++ * @SSAM_EVENT_NOTIFIER_OBSERVER: ++ * The corresponding notifier acts as observer. Registering a notifier ++ * with this flag set will not attempt to enable any event. Equally, ++ * unregistering will not attempt to disable any event. Note that a ++ * notifier with this flag may not even correspond to a certain event at ++ * all, only to a specific event target category. Event matching will not ++ * be influenced by this flag. ++ */ ++enum ssam_event_notifier_flags { ++ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), ++}; ++ + /** + * struct ssam_event_notifier - Notifier block for SSAM events. + * @base: The base notifier block with callback function and priority. +@@ -803,6 +817,7 @@ enum ssam_event_mask { + * @event.id: ID specifying the event. + * @event.mask: Flags determining how events are matched to the notifier. + * @event.flags: Flags used for enabling the event. ++ * @flags: Notifier flags (see &enum ssam_event_notifier_flags). + */ + struct ssam_event_notifier { + struct ssam_notifier_block base; +@@ -813,6 +828,8 @@ struct ssam_event_notifier { + enum ssam_event_mask mask; + u8 flags; + } event; ++ ++ unsigned long flags; + }; + + int ssam_notifier_register(struct ssam_controller *ctrl, +-- +2.32.0 + +From ae51e1e0f612e8ce069a583e189dacdfbe7616a1 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:50 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow enabling of events + without notifiers + +We can already enable and disable SAM events via one of two ways: either +via a (non-observer) notifier tied to a specific event group, or a +generic event enable/disable request. In some instances, however, +neither method may be desirable. + +The first method will tie the event enable request to a specific +notifier, however, when we want to receive notifications for multiple +event groups of the same target category and forward this to the same +notifier callback, we may receive duplicate events, i.e. one event per +registered notifier. The second method will bypass the internal +reference counting mechanism, meaning that a disable request will +disable the event regardless of any other client driver using it, which +may break the functionality of that driver. + +To address this problem, add new functions that allow enabling and +disabling of events via the event reference counting mechanism built +into the controller, without needing to register a notifier. + +This can then be used in combination with observer notifiers to process +multiple events of the same target category without duplication in the +same callback function. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210604134755.535590-3-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../platform/surface/aggregator/controller.c | 293 +++++++++++++++--- + include/linux/surface_aggregator/controller.h | 8 + + 2 files changed, 253 insertions(+), 48 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index cd3a6b77f48d..cedd0f779f7a 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, + return NULL; + } + ++/** ++ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the ++ * given event and free its entry if the reference count reaches zero. ++ * @nf: The notifier system reference. ++ * @reg: The registry used to enable/disable the event. ++ * @id: The event ID. ++ * ++ * Decrements the reference-/activation-count of the specified event, freeing ++ * its entry if it reaches zero. ++ * ++ * Note: ``nf->lock`` must be held when calling this function. ++ */ ++static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, ++ struct ssam_event_registry reg, ++ struct ssam_event_id id) ++{ ++ struct ssam_nf_refcount_entry *entry; ++ ++ lockdep_assert_held(&nf->lock); ++ ++ entry = ssam_nf_refcount_dec(nf, reg, id); ++ if (entry && entry->refcount == 0) ++ kfree(entry); ++} ++ + /** + * ssam_nf_refcount_empty() - Test if the notification system has any + * enabled/active events. +@@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) + + /* -- Top-level event registry interface. ----------------------------------- */ + ++/** ++ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has ++ * not already been enabled. ++ * @ctrl: The controller to enable the event on. ++ * @entry: The reference count entry for the event to be enabled. ++ * @flags: The flags used for enabling the event on the EC. ++ * ++ * Enable the event associated with the given reference count entry if the ++ * reference count equals one, i.e. the event has not previously been enabled. ++ * If the event has already been enabled (i.e. reference count not equal to ++ * one), check that the flags used for enabling match and warn about this if ++ * they do not. ++ * ++ * This does not modify the reference count itself, which is done with ++ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). ++ * ++ * Note: ``nf->lock`` must be held when calling this function. ++ * ++ * Return: Returns zero on success. If the event is enabled by this call, ++ * returns the status of the event-enable EC command. ++ */ ++static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, ++ struct ssam_nf_refcount_entry *entry, u8 flags) ++{ ++ const struct ssam_event_registry reg = entry->key.reg; ++ const struct ssam_event_id id = entry->key.id; ++ struct ssam_nf *nf = &ctrl->cplt.event.notif; ++ int status; ++ ++ lockdep_assert_held(&nf->lock); ++ ++ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", ++ reg.target_category, id.target_category, id.instance, entry->refcount); ++ ++ if (entry->refcount == 1) { ++ status = ssam_ssh_event_enable(ctrl, reg, id, flags); ++ if (status) ++ return status; ++ ++ entry->flags = flags; ++ ++ } else if (entry->flags != flags) { ++ ssam_warn(ctrl, ++ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", ++ flags, entry->flags, reg.target_category, id.target_category, ++ id.instance); ++ } ++ ++ return 0; ++} ++ ++/** ++ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is ++ * no longer in use and free the corresponding entry. ++ * @ctrl: The controller to disable the event on. ++ * @entry: The reference count entry for the event to be disabled. ++ * @flags: The flags used for enabling the event on the EC. ++ * ++ * If the reference count equals zero, i.e. the event is no longer requested by ++ * any client, the event will be disabled and the corresponding reference count ++ * entry freed. The reference count entry must not be used any more after a ++ * call to this function. ++ * ++ * Also checks if the flags used for disabling the event match the flags used ++ * for enabling the event and warns if they do not (regardless of reference ++ * count). ++ * ++ * This does not modify the reference count itself, which is done with ++ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). ++ * ++ * Note: ``nf->lock`` must be held when calling this function. ++ * ++ * Return: Returns zero on success. If the event is disabled by this call, ++ * returns the status of the event-enable EC command. ++ */ ++static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, ++ struct ssam_nf_refcount_entry *entry, u8 flags) ++{ ++ const struct ssam_event_registry reg = entry->key.reg; ++ const struct ssam_event_id id = entry->key.id; ++ struct ssam_nf *nf = &ctrl->cplt.event.notif; ++ int status; ++ ++ lockdep_assert_held(&nf->lock); ++ ++ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", ++ reg.target_category, id.target_category, id.instance, entry->refcount); ++ ++ if (entry->flags != flags) { ++ ssam_warn(ctrl, ++ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", ++ flags, entry->flags, reg.target_category, id.target_category, ++ id.instance); ++ } ++ ++ if (entry->refcount == 0) { ++ status = ssam_ssh_event_disable(ctrl, reg, id, flags); ++ kfree(entry); ++ } ++ ++ return status; ++} ++ + /** + * ssam_notifier_register() - Register an event notifier. + * @ctrl: The controller to register the notifier on. +@@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif + mutex_unlock(&nf->lock); + return PTR_ERR(entry); + } +- +- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", +- n->event.reg.target_category, n->event.id.target_category, +- n->event.id.instance, entry->refcount); + } + + status = ssam_nfblk_insert(nf_head, &n->base); + if (status) { +- if (entry) { +- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); +- if (entry->refcount == 0) +- kfree(entry); +- } ++ if (entry) ++ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); + + mutex_unlock(&nf->lock); + return status; + } + +- if (entry && entry->refcount == 1) { +- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); ++ if (entry) { ++ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); + if (status) { + ssam_nfblk_remove(&n->base); +- kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); ++ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); + mutex_unlock(&nf->lock); + synchronize_srcu(&nf_head->srcu); + return status; + } +- +- entry->flags = n->event.flags; +- +- } else if (entry && entry->flags != n->event.flags) { +- ssam_warn(ctrl, +- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", +- n->event.flags, entry->flags, n->event.reg.target_category, +- n->event.id.target_category, n->event.id.instance); + } + + mutex_unlock(&nf->lock); +@@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + * If this is an observer notifier, do not attempt to disable the + * event, just remove it. + */ +- if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) +- goto remove; +- +- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); +- if (WARN_ON(!entry)) { +- /* +- * If this does not return an entry, there's a logic error +- * somewhere: The notifier block is registered, but the event +- * refcount entry is not there. Remove the notifier block +- * anyways. +- */ +- status = -ENOENT; +- goto remove; +- } +- +- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", +- n->event.reg.target_category, n->event.id.target_category, +- n->event.id.instance, entry->refcount); +- +- if (entry->flags != n->event.flags) { +- ssam_warn(ctrl, +- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", +- n->event.flags, entry->flags, n->event.reg.target_category, +- n->event.id.target_category, n->event.id.instance); +- } ++ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { ++ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); ++ if (WARN_ON(!entry)) { ++ /* ++ * If this does not return an entry, there's a logic ++ * error somewhere: The notifier block is registered, ++ * but the event refcount entry is not there. Remove ++ * the notifier block anyways. ++ */ ++ status = -ENOENT; ++ goto remove; ++ } + +- if (entry->refcount == 0) { +- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); +- kfree(entry); ++ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); + } + + remove: +@@ -2287,6 +2385,105 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + } + EXPORT_SYMBOL_GPL(ssam_notifier_unregister); + ++/** ++ * ssam_controller_event_enable() - Enable the specified event. ++ * @ctrl: The controller to enable the event for. ++ * @reg: The event registry to use for enabling the event. ++ * @id: The event ID specifying the event to be enabled. ++ * @flags: The SAM event flags used for enabling the event. ++ * ++ * Increment the event reference count of the specified event. If the event has ++ * not been enabled previously, it will be enabled by this call. ++ * ++ * Note: In general, ssam_notifier_register() with a non-observer notifier ++ * should be preferred for enabling/disabling events, as this will guarantee ++ * proper ordering and event forwarding in case of errors during event ++ * enabling/disabling. ++ * ++ * Return: Returns zero on success, %-ENOSPC if the reference count for the ++ * specified event has reached its maximum, %-ENOMEM if the corresponding event ++ * entry could not be allocated. If this is the first time that this event has ++ * been enabled (i.e. the reference count was incremented from zero to one by ++ * this call), returns the status of the event-enable EC-command. ++ */ ++int ssam_controller_event_enable(struct ssam_controller *ctrl, ++ struct ssam_event_registry reg, ++ struct ssam_event_id id, u8 flags) ++{ ++ u16 rqid = ssh_tc_to_rqid(id.target_category); ++ struct ssam_nf *nf = &ctrl->cplt.event.notif; ++ struct ssam_nf_refcount_entry *entry; ++ int status; ++ ++ if (!ssh_rqid_is_event(rqid)) ++ return -EINVAL; ++ ++ mutex_lock(&nf->lock); ++ ++ entry = ssam_nf_refcount_inc(nf, reg, id); ++ if (IS_ERR(entry)) { ++ mutex_unlock(&nf->lock); ++ return PTR_ERR(entry); ++ } ++ ++ status = ssam_nf_refcount_enable(ctrl, entry, flags); ++ if (status) { ++ ssam_nf_refcount_dec_free(nf, reg, id); ++ mutex_unlock(&nf->lock); ++ return status; ++ } ++ ++ mutex_unlock(&nf->lock); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(ssam_controller_event_enable); ++ ++/** ++ * ssam_controller_event_disable() - Disable the specified event. ++ * @ctrl: The controller to disable the event for. ++ * @reg: The event registry to use for disabling the event. ++ * @id: The event ID specifying the event to be disabled. ++ * @flags: The flags used when enabling the event. ++ * ++ * Decrement the reference count of the specified event. If the reference count ++ * reaches zero, the event will be disabled. ++ * ++ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a ++ * non-observer notifier should be preferred for enabling/disabling events, as ++ * this will guarantee proper ordering and event forwarding in case of errors ++ * during event enabling/disabling. ++ * ++ * Return: Returns zero on success, %-ENOENT if the given event has not been ++ * enabled on the controller. If the reference count of the event reaches zero ++ * during this call, returns the status of the event-disable EC-command. ++ */ ++int ssam_controller_event_disable(struct ssam_controller *ctrl, ++ struct ssam_event_registry reg, ++ struct ssam_event_id id, u8 flags) ++{ ++ u16 rqid = ssh_tc_to_rqid(id.target_category); ++ struct ssam_nf *nf = &ctrl->cplt.event.notif; ++ struct ssam_nf_refcount_entry *entry; ++ int status = 0; ++ ++ if (!ssh_rqid_is_event(rqid)) ++ return -EINVAL; ++ ++ mutex_lock(&nf->lock); ++ ++ entry = ssam_nf_refcount_dec(nf, reg, id); ++ if (!entry) { ++ mutex_unlock(&nf->lock); ++ return -ENOENT; ++ } ++ ++ status = ssam_nf_refcount_disable_free(ctrl, entry, flags); ++ ++ mutex_unlock(&nf->lock); ++ return status; ++} ++EXPORT_SYMBOL_GPL(ssam_controller_event_disable); ++ + /** + * ssam_notifier_disable_registered() - Disable events for all registered + * notifiers. +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index cf4bb48a850e..7965bdc669c5 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl, + int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + ++int ssam_controller_event_enable(struct ssam_controller *ctrl, ++ struct ssam_event_registry reg, ++ struct ssam_event_id id, u8 flags); ++ ++int ssam_controller_event_disable(struct ssam_controller *ctrl, ++ struct ssam_event_registry reg, ++ struct ssam_event_id id, u8 flags); ++ + #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ +-- +2.32.0 + +From aee14cd170550e9c16fc106846056eb4d006b044 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:51 +0200 +Subject: [PATCH] platform/surface: aggregator: Update copyright + +It's 2021, update the copyright accordingly. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210604134755.535590-4-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/Kconfig | 2 +- + drivers/platform/surface/aggregator/Makefile | 2 +- + drivers/platform/surface/aggregator/bus.c | 2 +- + drivers/platform/surface/aggregator/bus.h | 2 +- + drivers/platform/surface/aggregator/controller.c | 2 +- + drivers/platform/surface/aggregator/controller.h | 2 +- + drivers/platform/surface/aggregator/core.c | 2 +- + drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- + drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- + drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- + drivers/platform/surface/aggregator/ssh_parser.c | 2 +- + drivers/platform/surface/aggregator/ssh_parser.h | 2 +- + drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- + drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- + drivers/platform/surface/aggregator/trace.h | 2 +- + include/linux/surface_aggregator/controller.h | 2 +- + include/linux/surface_aggregator/device.h | 2 +- + include/linux/surface_aggregator/serial_hub.h | 2 +- + 18 files changed, 18 insertions(+), 18 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig +index 3aaeea9f0433..fd6dc452f3e8 100644 +--- a/drivers/platform/surface/aggregator/Kconfig ++++ b/drivers/platform/surface/aggregator/Kconfig +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0+ +-# Copyright (C) 2019-2020 Maximilian Luz ++# Copyright (C) 2019-2021 Maximilian Luz + + menuconfig SURFACE_AGGREGATOR + tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" +diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile +index c112e2c7112b..c8498c41e758 100644 +--- a/drivers/platform/surface/aggregator/Makefile ++++ b/drivers/platform/surface/aggregator/Makefile +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0+ +-# Copyright (C) 2019-2020 Maximilian Luz ++# Copyright (C) 2019-2021 Maximilian Luz + + # For include/trace/define_trace.h to include trace.h + CFLAGS_core.o = -I$(src) +diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c +index a9b660af0917..0169677c243e 100644 +--- a/drivers/platform/surface/aggregator/bus.c ++++ b/drivers/platform/surface/aggregator/bus.c +@@ -2,7 +2,7 @@ + /* + * Surface System Aggregator Module bus and device integration. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h +index 7712baaed6a5..ed032c2cbdb2 100644 +--- a/drivers/platform/surface/aggregator/bus.h ++++ b/drivers/platform/surface/aggregator/bus.h +@@ -2,7 +2,7 @@ + /* + * Surface System Aggregator Module bus and device integration. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_BUS_H +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index cedd0f779f7a..6646f4d6e10d 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2,7 +2,7 @@ + /* + * Main SSAM/SSH controller structure and functionality. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h +index 8297d34e7489..a0963c3562ff 100644 +--- a/drivers/platform/surface/aggregator/controller.h ++++ b/drivers/platform/surface/aggregator/controller.h +@@ -2,7 +2,7 @@ + /* + * Main SSAM/SSH controller structure and functionality. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index 8dc2c267bcd6..5d780e55f4a1 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -7,7 +7,7 @@ + * Handles communication via requests as well as enabling, disabling, and + * relaying of events. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h +index 1221f642dda1..e562958ffdf0 100644 +--- a/drivers/platform/surface/aggregator/ssh_msgb.h ++++ b/drivers/platform/surface/aggregator/ssh_msgb.h +@@ -2,7 +2,7 @@ + /* + * SSH message builder functions. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H +diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c +index 15d96eac6811..5e08049fc3ac 100644 +--- a/drivers/platform/surface/aggregator/ssh_packet_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c +@@ -2,7 +2,7 @@ + /* + * SSH packet transport layer. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h +index e8757d03f279..2eb329f0b91a 100644 +--- a/drivers/platform/surface/aggregator/ssh_packet_layer.h ++++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h +@@ -2,7 +2,7 @@ + /* + * SSH packet transport layer. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H +diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c +index e2dead8de94a..b77912f8f13b 100644 +--- a/drivers/platform/surface/aggregator/ssh_parser.c ++++ b/drivers/platform/surface/aggregator/ssh_parser.c +@@ -2,7 +2,7 @@ + /* + * SSH message parser. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h +index 63c38d350988..3bd6e180fd16 100644 +--- a/drivers/platform/surface/aggregator/ssh_parser.h ++++ b/drivers/platform/surface/aggregator/ssh_parser.h +@@ -2,7 +2,7 @@ + /* + * SSH message parser. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H +diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c +index 52a83a8fcf82..bfe1aaf38065 100644 +--- a/drivers/platform/surface/aggregator/ssh_request_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_request_layer.c +@@ -2,7 +2,7 @@ + /* + * SSH request transport layer. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h +index cb35815858d1..9c3cbae2d4bd 100644 +--- a/drivers/platform/surface/aggregator/ssh_request_layer.h ++++ b/drivers/platform/surface/aggregator/ssh_request_layer.h +@@ -2,7 +2,7 @@ + /* + * SSH request transport layer. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H +diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h +index eb332bb53ae4..de64cf169060 100644 +--- a/drivers/platform/surface/aggregator/trace.h ++++ b/drivers/platform/surface/aggregator/trace.h +@@ -2,7 +2,7 @@ + /* + * Trace points for SSAM/SSH. + * +- * Copyright (C) 2020 Maximilian Luz ++ * Copyright (C) 2020-2021 Maximilian Luz + */ + + #undef TRACE_SYSTEM +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index 7965bdc669c5..068e1982ad37 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -6,7 +6,7 @@ + * managing access and communication to and from the SSAM EC, as well as main + * communication structures and definitions. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 6ff9c58b3e17..f636c5310321 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -7,7 +7,7 @@ + * Provides support for non-platform/non-ACPI SSAM clients via dedicated + * subsystem. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H +diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h +index 64276fbfa1d5..c3de43edcffa 100644 +--- a/include/linux/surface_aggregator/serial_hub.h ++++ b/include/linux/surface_aggregator/serial_hub.h +@@ -6,7 +6,7 @@ + * Surface System Aggregator Module (SSAM). Provides the interface for basic + * packet- and request-based communication with the SSAM EC via SSH. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2021 Maximilian Luz + */ + + #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H +-- +2.32.0 + +From 3ef2675ab9aea4ff11b29e0250165bd1cbcdda46 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:52 +0200 +Subject: [PATCH] platform/surface: aggregator_cdev: Add support for forwarding + events to user-space + +Currently, debugging unknown events requires writing a custom driver. +This is somewhat difficult, slow to adapt, and not entirely +user-friendly for quickly trying to figure out things on devices of some +third-party user. We can do better. We already have a user-space +interface intended for debugging SAM EC requests, so let's add support +for receiving events to that. + +This commit provides support for receiving events by reading from the +controller file. It additionally introduces two new IOCTLs to control +which event categories will be forwarded. Specifically, a user-space +client can specify which target categories it wants to receive events +from by registering the corresponding notifier(s) via the IOCTLs and +after that, read the received events by reading from the controller +device. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210604134755.535590-5-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../userspace-api/ioctl/ioctl-number.rst | 2 +- + .../surface/surface_aggregator_cdev.c | 460 +++++++++++++++++- + include/uapi/linux/surface_aggregator/cdev.h | 41 +- + 3 files changed, 477 insertions(+), 26 deletions(-) + +diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst +index 9bfc2b510c64..1409e40e6345 100644 +--- a/Documentation/userspace-api/ioctl/ioctl-number.rst ++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst +@@ -325,7 +325,7 @@ Code Seq# Include File Comments + 0xA3 90-9F linux/dtlk.h + 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem + 0xA4 00-1F uapi/asm/sgx.h +-0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator ++0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator + + 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver + +diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c +index 79e28fab7e40..dcda377896b7 100644 +--- a/drivers/platform/surface/surface_aggregator_cdev.c ++++ b/drivers/platform/surface/surface_aggregator_cdev.c +@@ -3,29 +3,69 @@ + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator + * misc device. Intended for debugging and development. + * +- * Copyright (C) 2020 Maximilian Luz ++ * Copyright (C) 2020-2021 Maximilian Luz + */ + + #include ++#include + #include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include ++#include + + #include + #include ++#include + + #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + ++ ++/* -- Main structures. ------------------------------------------------------ */ ++ ++enum ssam_cdev_device_state { ++ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), ++}; ++ + struct ssam_cdev { + struct kref kref; + struct rw_semaphore lock; ++ ++ struct device *dev; + struct ssam_controller *ctrl; + struct miscdevice mdev; ++ unsigned long flags; ++ ++ struct rw_semaphore client_lock; /* Guards client list. */ ++ struct list_head client_list; ++}; ++ ++struct ssam_cdev_client; ++ ++struct ssam_cdev_notifier { ++ struct ssam_cdev_client *client; ++ struct ssam_event_notifier nf; ++}; ++ ++struct ssam_cdev_client { ++ struct ssam_cdev *cdev; ++ struct list_head node; ++ ++ struct mutex notifier_lock; /* Guards notifier access for registration */ ++ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; ++ ++ struct mutex read_lock; /* Guards FIFO buffer read access */ ++ struct mutex write_lock; /* Guards FIFO buffer write access */ ++ DECLARE_KFIFO(buffer, u8, 4096); ++ ++ wait_queue_head_t waitq; ++ struct fasync_struct *fasync; + }; + + static void __ssam_cdev_release(struct kref *kref) +@@ -47,24 +87,169 @@ static void ssam_cdev_put(struct ssam_cdev *cdev) + kref_put(&cdev->kref, __ssam_cdev_release); + } + +-static int ssam_cdev_device_open(struct inode *inode, struct file *filp) ++ ++/* -- Notifier handling. ---------------------------------------------------- */ ++ ++static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) + { +- struct miscdevice *mdev = filp->private_data; +- struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); ++ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); ++ struct ssam_cdev_client *client = cdev_nf->client; ++ struct ssam_cdev_event event; ++ size_t n = struct_size(&event, data, in->length); ++ ++ /* Translate event. */ ++ event.target_category = in->target_category; ++ event.target_id = in->target_id; ++ event.command_id = in->command_id; ++ event.instance_id = in->instance_id; ++ event.length = in->length; ++ ++ mutex_lock(&client->write_lock); ++ ++ /* Make sure we have enough space. */ ++ if (kfifo_avail(&client->buffer) < n) { ++ dev_warn(client->cdev->dev, ++ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", ++ in->target_category, in->target_id, in->command_id, in->instance_id); ++ mutex_unlock(&client->write_lock); ++ return 0; ++ } + +- filp->private_data = ssam_cdev_get(cdev); +- return stream_open(inode, filp); ++ /* Copy event header and payload. */ ++ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); ++ kfifo_in(&client->buffer, &in->data[0], in->length); ++ ++ mutex_unlock(&client->write_lock); ++ ++ /* Notify waiting readers. */ ++ kill_fasync(&client->fasync, SIGIO, POLL_IN); ++ wake_up_interruptible(&client->waitq); ++ ++ /* ++ * Don't mark events as handled, this is the job of a proper driver and ++ * not the debugging interface. ++ */ ++ return 0; + } + +-static int ssam_cdev_device_release(struct inode *inode, struct file *filp) ++static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) + { +- ssam_cdev_put(filp->private_data); +- return 0; ++ const u16 rqid = ssh_tc_to_rqid(tc); ++ const u16 event = ssh_rqid_to_event(rqid); ++ struct ssam_cdev_notifier *nf; ++ int status; ++ ++ /* Validate notifier target category. */ ++ if (!ssh_rqid_is_event(rqid)) ++ return -EINVAL; ++ ++ mutex_lock(&client->notifier_lock); ++ ++ /* Check if the notifier has already been registered. */ ++ if (client->notifier[event]) { ++ mutex_unlock(&client->notifier_lock); ++ return -EEXIST; ++ } ++ ++ /* Allocate new notifier. */ ++ nf = kzalloc(sizeof(*nf), GFP_KERNEL); ++ if (!nf) { ++ mutex_unlock(&client->notifier_lock); ++ return -ENOMEM; ++ } ++ ++ /* ++ * Create a dummy notifier with the minimal required fields for ++ * observer registration. Note that we can skip fully specifying event ++ * and registry here as we do not need any matching and use silent ++ * registration, which does not enable the corresponding event. ++ */ ++ nf->client = client; ++ nf->nf.base.fn = ssam_cdev_notifier; ++ nf->nf.base.priority = priority; ++ nf->nf.event.id.target_category = tc; ++ nf->nf.event.mask = 0; /* Do not do any matching. */ ++ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; ++ ++ /* Register notifier. */ ++ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); ++ if (status) ++ kfree(nf); ++ else ++ client->notifier[event] = nf; ++ ++ mutex_unlock(&client->notifier_lock); ++ return status; + } + +-static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) ++static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) ++{ ++ const u16 rqid = ssh_tc_to_rqid(tc); ++ const u16 event = ssh_rqid_to_event(rqid); ++ int status; ++ ++ /* Validate notifier target category. */ ++ if (!ssh_rqid_is_event(rqid)) ++ return -EINVAL; ++ ++ mutex_lock(&client->notifier_lock); ++ ++ /* Check if the notifier is currently registered. */ ++ if (!client->notifier[event]) { ++ mutex_unlock(&client->notifier_lock); ++ return -ENOENT; ++ } ++ ++ /* Unregister and free notifier. */ ++ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); ++ kfree(client->notifier[event]); ++ client->notifier[event] = NULL; ++ ++ mutex_unlock(&client->notifier_lock); ++ return status; ++} ++ ++static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) ++{ ++ int i; ++ ++ down_read(&client->cdev->lock); ++ ++ /* ++ * This function may be used during shutdown, thus we need to test for ++ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. ++ */ ++ if (client->cdev->ctrl) { ++ for (i = 0; i < SSH_NUM_EVENTS; i++) ++ ssam_cdev_notifier_unregister(client, i + 1); ++ ++ } else { ++ int count = 0; ++ ++ /* ++ * Device has been shut down. Any notifier remaining is a bug, ++ * so warn about that as this would otherwise hardly be ++ * noticeable. Nevertheless, free them as well. ++ */ ++ mutex_lock(&client->notifier_lock); ++ for (i = 0; i < SSH_NUM_EVENTS; i++) { ++ count += !!(client->notifier[i]); ++ kfree(client->notifier[i]); ++ client->notifier[i] = NULL; ++ } ++ mutex_unlock(&client->notifier_lock); ++ ++ WARN_ON(count > 0); ++ } ++ ++ up_read(&client->cdev->lock); ++} ++ ++ ++/* -- IOCTL functions. ------------------------------------------------------ */ ++ ++static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) + { +- struct ssam_cdev_request __user *r; + struct ssam_cdev_request rqst; + struct ssam_request spec = {}; + struct ssam_response rsp = {}; +@@ -72,7 +257,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) + void __user *rspdata; + int status = 0, ret = 0, tmp; + +- r = (struct ssam_cdev_request __user *)arg; + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; +@@ -152,7 +336,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) + } + + /* Perform request. */ +- status = ssam_request_sync(cdev->ctrl, &spec, &rsp); ++ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); + if (status) + goto out; + +@@ -177,48 +361,247 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) + return ret; + } + +-static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, ++static long ssam_cdev_notif_register(struct ssam_cdev_client *client, ++ const struct ssam_cdev_notifier_desc __user *d) ++{ ++ struct ssam_cdev_notifier_desc desc; ++ long ret; ++ ++ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); ++ if (ret) ++ return ret; ++ ++ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); ++} ++ ++static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, ++ const struct ssam_cdev_notifier_desc __user *d) ++{ ++ struct ssam_cdev_notifier_desc desc; ++ long ret; ++ ++ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); ++ if (ret) ++ return ret; ++ ++ return ssam_cdev_notifier_unregister(client, desc.target_category); ++} ++ ++ ++/* -- File operations. ------------------------------------------------------ */ ++ ++static int ssam_cdev_device_open(struct inode *inode, struct file *filp) ++{ ++ struct miscdevice *mdev = filp->private_data; ++ struct ssam_cdev_client *client; ++ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); ++ ++ /* Initialize client */ ++ client = vzalloc(sizeof(*client)); ++ if (!client) ++ return -ENOMEM; ++ ++ client->cdev = ssam_cdev_get(cdev); ++ ++ INIT_LIST_HEAD(&client->node); ++ ++ mutex_init(&client->notifier_lock); ++ ++ mutex_init(&client->read_lock); ++ mutex_init(&client->write_lock); ++ INIT_KFIFO(client->buffer); ++ init_waitqueue_head(&client->waitq); ++ ++ filp->private_data = client; ++ ++ /* Attach client. */ ++ down_write(&cdev->client_lock); ++ ++ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { ++ up_write(&cdev->client_lock); ++ mutex_destroy(&client->write_lock); ++ mutex_destroy(&client->read_lock); ++ mutex_destroy(&client->notifier_lock); ++ ssam_cdev_put(client->cdev); ++ vfree(client); ++ return -ENODEV; ++ } ++ list_add_tail(&client->node, &cdev->client_list); ++ ++ up_write(&cdev->client_lock); ++ ++ stream_open(inode, filp); ++ return 0; ++} ++ ++static int ssam_cdev_device_release(struct inode *inode, struct file *filp) ++{ ++ struct ssam_cdev_client *client = filp->private_data; ++ ++ /* Force-unregister all remaining notifiers of this client. */ ++ ssam_cdev_notifier_unregister_all(client); ++ ++ /* Detach client. */ ++ down_write(&client->cdev->client_lock); ++ list_del(&client->node); ++ up_write(&client->cdev->client_lock); ++ ++ /* Free client. */ ++ mutex_destroy(&client->write_lock); ++ mutex_destroy(&client->read_lock); ++ ++ mutex_destroy(&client->notifier_lock); ++ ++ ssam_cdev_put(client->cdev); ++ vfree(client); ++ ++ return 0; ++} ++ ++static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, + unsigned long arg) + { + switch (cmd) { + case SSAM_CDEV_REQUEST: +- return ssam_cdev_request(cdev, arg); ++ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); ++ ++ case SSAM_CDEV_NOTIF_REGISTER: ++ return ssam_cdev_notif_register(client, ++ (struct ssam_cdev_notifier_desc __user *)arg); ++ ++ case SSAM_CDEV_NOTIF_UNREGISTER: ++ return ssam_cdev_notif_unregister(client, ++ (struct ssam_cdev_notifier_desc __user *)arg); + + default: + return -ENOTTY; + } + } + +-static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, +- unsigned long arg) ++static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) + { +- struct ssam_cdev *cdev = file->private_data; ++ struct ssam_cdev_client *client = file->private_data; + long status; + + /* Ensure that controller is valid for as long as we need it. */ ++ if (down_read_killable(&client->cdev->lock)) ++ return -ERESTARTSYS; ++ ++ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { ++ up_read(&client->cdev->lock); ++ return -ENODEV; ++ } ++ ++ status = __ssam_cdev_device_ioctl(client, cmd, arg); ++ ++ up_read(&client->cdev->lock); ++ return status; ++} ++ ++static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) ++{ ++ struct ssam_cdev_client *client = file->private_data; ++ struct ssam_cdev *cdev = client->cdev; ++ unsigned int copied; ++ int status = 0; ++ + if (down_read_killable(&cdev->lock)) + return -ERESTARTSYS; + +- if (!cdev->ctrl) { ++ /* Make sure we're not shut down. */ ++ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { + up_read(&cdev->lock); + return -ENODEV; + } + +- status = __ssam_cdev_device_ioctl(cdev, cmd, arg); ++ do { ++ /* Check availability, wait if necessary. */ ++ if (kfifo_is_empty(&client->buffer)) { ++ up_read(&cdev->lock); ++ ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ status = wait_event_interruptible(client->waitq, ++ !kfifo_is_empty(&client->buffer) || ++ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, ++ &cdev->flags)); ++ if (status < 0) ++ return status; ++ ++ if (down_read_killable(&cdev->lock)) ++ return -ERESTARTSYS; ++ ++ /* Need to check that we're not shut down again. */ ++ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { ++ up_read(&cdev->lock); ++ return -ENODEV; ++ } ++ } ++ ++ /* Try to read from FIFO. */ ++ if (mutex_lock_interruptible(&client->read_lock)) { ++ up_read(&cdev->lock); ++ return -ERESTARTSYS; ++ } ++ ++ status = kfifo_to_user(&client->buffer, buf, count, &copied); ++ mutex_unlock(&client->read_lock); ++ ++ if (status < 0) { ++ up_read(&cdev->lock); ++ return status; ++ } ++ ++ /* We might not have gotten anything, check this here. */ ++ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { ++ up_read(&cdev->lock); ++ return -EAGAIN; ++ } ++ } while (copied == 0); + + up_read(&cdev->lock); +- return status; ++ return copied; ++} ++ ++static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) ++{ ++ struct ssam_cdev_client *client = file->private_data; ++ __poll_t events = 0; ++ ++ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) ++ return EPOLLHUP | EPOLLERR; ++ ++ poll_wait(file, &client->waitq, pt); ++ ++ if (!kfifo_is_empty(&client->buffer)) ++ events |= EPOLLIN | EPOLLRDNORM; ++ ++ return events; ++} ++ ++static int ssam_cdev_fasync(int fd, struct file *file, int on) ++{ ++ struct ssam_cdev_client *client = file->private_data; ++ ++ return fasync_helper(fd, file, on, &client->fasync); + } + + static const struct file_operations ssam_controller_fops = { + .owner = THIS_MODULE, + .open = ssam_cdev_device_open, + .release = ssam_cdev_device_release, ++ .read = ssam_cdev_read, ++ .poll = ssam_cdev_poll, ++ .fasync = ssam_cdev_fasync, + .unlocked_ioctl = ssam_cdev_device_ioctl, + .compat_ioctl = ssam_cdev_device_ioctl, +- .llseek = noop_llseek, ++ .llseek = no_llseek, + }; + ++ ++/* -- Device and driver setup ----------------------------------------------- */ ++ + static int ssam_dbg_device_probe(struct platform_device *pdev) + { + struct ssam_controller *ctrl; +@@ -236,6 +619,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) + kref_init(&cdev->kref); + init_rwsem(&cdev->lock); + cdev->ctrl = ctrl; ++ cdev->dev = &pdev->dev; + + cdev->mdev.parent = &pdev->dev; + cdev->mdev.minor = MISC_DYNAMIC_MINOR; +@@ -243,6 +627,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) + cdev->mdev.nodename = "surface/aggregator"; + cdev->mdev.fops = &ssam_controller_fops; + ++ init_rwsem(&cdev->client_lock); ++ INIT_LIST_HEAD(&cdev->client_list); ++ + status = misc_register(&cdev->mdev); + if (status) { + kfree(cdev); +@@ -256,8 +643,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) + static int ssam_dbg_device_remove(struct platform_device *pdev) + { + struct ssam_cdev *cdev = platform_get_drvdata(pdev); ++ struct ssam_cdev_client *client; + +- misc_deregister(&cdev->mdev); ++ /* ++ * Mark device as shut-down. Prevent new clients from being added and ++ * new operations from being executed. ++ */ ++ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); ++ ++ down_write(&cdev->client_lock); ++ ++ /* Remove all notifiers registered by us. */ ++ list_for_each_entry(client, &cdev->client_list, node) { ++ ssam_cdev_notifier_unregister_all(client); ++ } ++ ++ /* Wake up async clients. */ ++ list_for_each_entry(client, &cdev->client_list, node) { ++ kill_fasync(&client->fasync, SIGIO, POLL_HUP); ++ } ++ ++ /* Wake up blocking clients. */ ++ list_for_each_entry(client, &cdev->client_list, node) { ++ wake_up_interruptible(&client->waitq); ++ } ++ ++ up_write(&cdev->client_lock); + + /* + * The controller is only guaranteed to be valid for as long as the +@@ -266,8 +677,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev) + */ + down_write(&cdev->lock); + cdev->ctrl = NULL; ++ cdev->dev = NULL; + up_write(&cdev->lock); + ++ misc_deregister(&cdev->mdev); ++ + ssam_cdev_put(cdev); + return 0; + } +diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h +index fbcce04abfe9..4f393fafc235 100644 +--- a/include/uapi/linux/surface_aggregator/cdev.h ++++ b/include/uapi/linux/surface_aggregator/cdev.h +@@ -6,7 +6,7 @@ + * device. This device provides direct user-space access to the SSAM EC. + * Intended for debugging and development. + * +- * Copyright (C) 2020 Maximilian Luz ++ * Copyright (C) 2020-2021 Maximilian Luz + */ + + #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H +@@ -73,6 +73,43 @@ struct ssam_cdev_request { + } response; + } __attribute__((__packed__)); + +-#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) ++/** ++ * struct ssam_cdev_notifier_desc - Notifier descriptor. ++ * @priority: Priority value determining the order in which notifier ++ * callbacks will be called. A higher value means higher ++ * priority, i.e. the associated callback will be executed ++ * earlier than other (lower priority) callbacks. ++ * @target_category: The event target category for which this notifier should ++ * receive events. ++ * ++ * Specifies the notifier that should be registered or unregistered, ++ * specifically with which priority and for which target category of events. ++ */ ++struct ssam_cdev_notifier_desc { ++ __s32 priority; ++ __u8 target_category; ++} __attribute__((__packed__)); ++ ++/** ++ * struct ssam_cdev_event - SSAM event sent by the EC. ++ * @target_category: Target category of the event source. See &enum ssam_ssh_tc. ++ * @target_id: Target ID of the event source. ++ * @command_id: Command ID of the event. ++ * @instance_id: Instance ID of the event source. ++ * @length: Length of the event payload in bytes. ++ * @data: Event payload data. ++ */ ++struct ssam_cdev_event { ++ __u8 target_category; ++ __u8 target_id; ++ __u8 command_id; ++ __u8 instance_id; ++ __u16 length; ++ __u8 data[]; ++} __attribute__((__packed__)); ++ ++#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) ++#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) ++#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) + + #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ +-- +2.32.0 + +From 57beefc4a8f10cc0a26f6d9299fff0574a0c1c95 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:53 +0200 +Subject: [PATCH] platform/surface: aggregator_cdev: Allow enabling of events + from user-space + +While events can already be enabled and disabled via the generic request +IOCTL, this bypasses the internal reference counting mechanism of the +controller. Due to that, disabling an event will turn it off regardless +of any other client having requested said event, which may break +functionality of that client. + +To solve this, add IOCTLs wrapping the ssam_controller_event_enable() +and ssam_controller_event_disable() functions, which have been +previously introduced for this specific purpose. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210604134755.535590-6-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++ + include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++ + 2 files changed, 90 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c +index dcda377896b7..7b86b36eaaa0 100644 +--- a/drivers/platform/surface/surface_aggregator_cdev.c ++++ b/drivers/platform/surface/surface_aggregator_cdev.c +@@ -387,6 +387,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, + return ssam_cdev_notifier_unregister(client, desc.target_category); + } + ++static long ssam_cdev_event_enable(struct ssam_cdev_client *client, ++ const struct ssam_cdev_event_desc __user *d) ++{ ++ struct ssam_cdev_event_desc desc; ++ struct ssam_event_registry reg; ++ struct ssam_event_id id; ++ long ret; ++ ++ /* Read descriptor from user-space. */ ++ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); ++ if (ret) ++ return ret; ++ ++ /* Translate descriptor. */ ++ reg.target_category = desc.reg.target_category; ++ reg.target_id = desc.reg.target_id; ++ reg.cid_enable = desc.reg.cid_enable; ++ reg.cid_disable = desc.reg.cid_disable; ++ ++ id.target_category = desc.id.target_category; ++ id.instance = desc.id.instance; ++ ++ /* Disable event. */ ++ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); ++} ++ ++static long ssam_cdev_event_disable(struct ssam_cdev_client *client, ++ const struct ssam_cdev_event_desc __user *d) ++{ ++ struct ssam_cdev_event_desc desc; ++ struct ssam_event_registry reg; ++ struct ssam_event_id id; ++ long ret; ++ ++ /* Read descriptor from user-space. */ ++ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); ++ if (ret) ++ return ret; ++ ++ /* Translate descriptor. */ ++ reg.target_category = desc.reg.target_category; ++ reg.target_id = desc.reg.target_id; ++ reg.cid_enable = desc.reg.cid_enable; ++ reg.cid_disable = desc.reg.cid_disable; ++ ++ id.target_category = desc.id.target_category; ++ id.instance = desc.id.instance; ++ ++ /* Disable event. */ ++ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); ++} ++ + + /* -- File operations. ------------------------------------------------------ */ + +@@ -473,6 +525,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i + return ssam_cdev_notif_unregister(client, + (struct ssam_cdev_notifier_desc __user *)arg); + ++ case SSAM_CDEV_EVENT_ENABLE: ++ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); ++ ++ case SSAM_CDEV_EVENT_DISABLE: ++ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); ++ + default: + return -ENOTTY; + } +diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h +index 4f393fafc235..08f46b60b151 100644 +--- a/include/uapi/linux/surface_aggregator/cdev.h ++++ b/include/uapi/linux/surface_aggregator/cdev.h +@@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc { + __u8 target_category; + } __attribute__((__packed__)); + ++/** ++ * struct ssam_cdev_event_desc - Event descriptor. ++ * @reg: Registry via which the event will be enabled/disabled. ++ * @reg.target_category: Target category for the event registry requests. ++ * @reg.target_id: Target ID for the event registry requests. ++ * @reg.cid_enable: Command ID for the event-enable request. ++ * @reg.cid_disable: Command ID for the event-disable request. ++ * @id: ID specifying the event. ++ * @id.target_category: Target category of the event source. ++ * @id.instance: Instance ID of the event source. ++ * @flags: Flags used for enabling the event. ++ * ++ * Specifies which event should be enabled/disabled and how to do that. ++ */ ++struct ssam_cdev_event_desc { ++ struct { ++ __u8 target_category; ++ __u8 target_id; ++ __u8 cid_enable; ++ __u8 cid_disable; ++ } reg; ++ ++ struct { ++ __u8 target_category; ++ __u8 instance; ++ } id; ++ ++ __u8 flags; ++} __attribute__((__packed__)); ++ + /** + * struct ssam_cdev_event - SSAM event sent by the EC. + * @target_category: Target category of the event source. See &enum ssam_ssh_tc. +@@ -111,5 +141,7 @@ struct ssam_cdev_event { + #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) + #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) + #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) ++#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) ++#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) + + #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ +-- +2.32.0 + +From b26eb043920cf30a9c6dad8b02ebd78adcf1fb2c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 15:47:54 +0200 +Subject: [PATCH] platform/surface: aggregator_cdev: Add lockdep support + +Mark functions with locking requirements via the corresponding lockdep +calls for debugging and documentary purposes. + +Signed-off-by: Maximilian Luz +Reviewed-by: Hans de Goede +Link: https://lore.kernel.org/r/20210604134755.535590-7-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c +index 7b86b36eaaa0..30fb50fde450 100644 +--- a/drivers/platform/surface/surface_aggregator_cdev.c ++++ b/drivers/platform/surface/surface_aggregator_cdev.c +@@ -139,6 +139,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, i + struct ssam_cdev_notifier *nf; + int status; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + /* Validate notifier target category. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; +@@ -188,6 +190,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) + const u16 event = ssh_rqid_to_event(rqid); + int status; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + /* Validate notifier target category. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; +@@ -257,6 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ + void __user *rspdata; + int status = 0, ret = 0, tmp; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; +@@ -367,6 +373,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client, + struct ssam_cdev_notifier_desc desc; + long ret; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; +@@ -380,6 +388,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, + struct ssam_cdev_notifier_desc desc; + long ret; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; +@@ -395,6 +405,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client, + struct ssam_event_id id; + long ret; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + /* Read descriptor from user-space. */ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) +@@ -421,6 +433,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client, + struct ssam_event_id id; + long ret; + ++ lockdep_assert_held_read(&client->cdev->lock); ++ + /* Read descriptor from user-space. */ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) +@@ -513,6 +527,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp) + static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, + unsigned long arg) + { ++ lockdep_assert_held_read(&client->cdev->lock); ++ + switch (cmd) { + case SSAM_CDEV_REQUEST: + return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); +-- +2.32.0 + +From e41af53103f8422234d1ce3091b496a8a3d9b6e0 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 2 Jun 2021 20:07:47 +0200 +Subject: [PATCH] docs: driver-api: Update Surface Aggregator user-space + interface documentation + +Update the controller-device user-space interface (cdev) documentation +for the newly introduced IOCTLs and event interface. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++- + 1 file changed, 122 insertions(+), 5 deletions(-) + +diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst +index 248c1372d879..0134a841a079 100644 +--- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst ++++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst +@@ -1,9 +1,8 @@ + .. SPDX-License-Identifier: GPL-2.0+ + +-.. |u8| replace:: :c:type:`u8 ` +-.. |u16| replace:: :c:type:`u16 ` + .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` + .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` ++.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` + + ============================== + User-Space EC Interface (cdev) +@@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in + A small python library and scripts for accessing this interface can be found + at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. + ++.. contents:: ++ ++ ++Receiving Events ++================ ++ ++Events can be received by reading from the device-file. The are represented by ++the |ssam_cdev_event| datatype. ++ ++Before events are available to be read, however, the desired notifiers must be ++registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in ++essence, callbacks, called when the EC sends an event. They are, in this ++interface, associated with a specific target category and device-file-instance. ++They forward any event of this category to the buffer of the corresponding ++instance, from which it can then be read. ++ ++Notifiers themselves do not enable events on the EC. Thus, it may additionally ++be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While ++notifiers work per-client (i.e. per-device-file-instance), events are enabled ++globally, for the EC and all of its clients (regardless of userspace or ++non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` ++IOCTLs take care of reference counting the events, such that an event is ++enabled as long as there is a client that has requested it. ++ ++Note that enabled events are not automatically disabled once the client ++instance is closed. Therefore any client process (or group of processes) should ++balance their event enable calls with the corresponding event disable calls. It ++is, however, perfectly valid to enable and disable events on different client ++instances. For example, it is valid to set up notifiers and read events on ++client instance ``A``, enable those events on instance ``B`` (note that these ++will also be received by A since events are enabled/disabled globally), and ++after no more events are desired, disable the previously enabled events via ++instance ``C``. ++ + + Controller IOCTLs + ================= +@@ -45,9 +78,33 @@ The following IOCTLs are provided: + - ``REQUEST`` + - Perform synchronous SAM request. + ++ * - ``0xA5`` ++ - ``2`` ++ - ``W`` ++ - ``NOTIF_REGISTER`` ++ - Register event notifier. + +-``REQUEST`` +------------ ++ * - ``0xA5`` ++ - ``3`` ++ - ``W`` ++ - ``NOTIF_UNREGISTER`` ++ - Unregister event notifier. ++ ++ * - ``0xA5`` ++ - ``4`` ++ - ``W`` ++ - ``EVENT_ENABLE`` ++ - Enable event source. ++ ++ * - ``0xA5`` ++ - ``5`` ++ - ``W`` ++ - ``EVENT_DISABLE`` ++ - Disable event source. ++ ++ ++``SSAM_CDEV_REQUEST`` ++--------------------- + + Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. + +@@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from + inside the IOCTL, but the request ``status`` member may still be negative in + case the actual execution of the request failed after it has been submitted. + +-A full definition of the argument struct is provided below: ++A full definition of the argument struct is provided below. ++ ++``SSAM_CDEV_NOTIF_REGISTER`` ++---------------------------- ++ ++Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. ++ ++Register a notifier for the event target category specified in the given ++notifier description with the specified priority. Notifiers registration is ++required to receive events, but does not enable events themselves. After a ++notifier for a specific target category has been registered, all events of that ++category will be forwarded to the userspace client and can then be read from ++the device file instance. Note that events may have to be enabled, e.g. via the ++``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. ++ ++Only one notifier can be registered per target category and client instance. If ++a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. ++ ++Notifiers will automatically be removed when the device file instance is ++closed. ++ ++``SSAM_CDEV_NOTIF_UNREGISTER`` ++------------------------------ ++ ++Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. ++ ++Unregisters the notifier associated with the specified target category. The ++priority field will be ignored by this IOCTL. If no notifier has been ++registered for this client instance and the given category, this IOCTL will ++fail with ``-ENOENT``. ++ ++``SSAM_CDEV_EVENT_ENABLE`` ++-------------------------- ++ ++Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. ++ ++Enable the event associated with the given event descriptor. ++ ++Note that this call will not register a notifier itself, it will only enable ++events on the controller. If you want to receive events by reading from the ++device file, you will need to register the corresponding notifier(s) on that ++instance. ++ ++Events are not automatically disabled when the device file is closed. This must ++be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. ++ ++``SSAM_CDEV_EVENT_DISABLE`` ++--------------------------- ++ ++Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. ++ ++Disable the event associated with the given event descriptor. ++ ++Note that this will not unregister any notifiers. Events may still be received ++and forwarded to user-space after this call. The only safe way of stopping ++events from being received is unregistering all previously registered ++notifiers. ++ ++ ++Structures and Enums ++==================== + + .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h +-- +2.32.0 + +From 60ac33ea0f8194f891b9808d7d7215297de0c63c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 23:09:06 +0200 +Subject: [PATCH] platform/surface: aggregator: Do not return uninitialized + value + +The status variable in ssam_nf_refcount_disable_free() is only set when +the reference count equals zero. Otherwise, it is returned +uninitialized. Fix this by always initializing status to zero. + +Reported-by: kernel test robot +Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers") +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210604210907.25738-2-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 6646f4d6e10d..634399387d76 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + const struct ssam_event_registry reg = entry->key.reg; + const struct ssam_event_id id = entry->key.id; + struct ssam_nf *nf = &ctrl->cplt.event.notif; +- int status; ++ int status = 0; + + lockdep_assert_held(&nf->lock); + +-- +2.32.0 + +From 7640650e61d9f4fba894e321dfe7c9184b3b8a29 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 4 Jun 2021 23:09:07 +0200 +Subject: [PATCH] platform/surface: aggregator: Drop unnecessary variable + initialization + +The status variable in ssam_controller_event_disable() is always set, no +need to initialize it. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210604210907.25738-3-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/controller.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 634399387d76..b8c377b3f932 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, + u16 rqid = ssh_tc_to_rqid(id.target_category); + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct ssam_nf_refcount_entry *entry; +- int status = 0; ++ int status; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; +-- +2.32.0 + +From 4e9280059d06eca593d3da0ccc25250796676908 Mon Sep 17 00:00:00 2001 +From: Baokun Li +Date: Wed, 9 Jun 2021 15:26:38 +0800 +Subject: [PATCH] platform/surface: aggregator: Use list_move_tail instead of + list_del/list_add_tail in ssh_request_layer.c + +Using list_move_tail() instead of list_del() + list_add_tail() in ssh_request_layer.c. + +Reported-by: Hulk Robot +Signed-off-by: Baokun Li +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210609072638.1358174-1-libaokun1@huawei.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../platform/surface/aggregator/ssh_request_layer.c | 10 +++------- + 1 file changed, 3 insertions(+), 7 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c +index bfe1aaf38065..790f7f0eee98 100644 +--- a/drivers/platform/surface/aggregator/ssh_request_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_request_layer.c +@@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + + atomic_dec(&rtl->pending.count); +- list_del(&r->node); +- +- list_add_tail(&r->node, &claimed); ++ list_move_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + +@@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); + +- list_del(&r->node); +- list_add_tail(&r->node, &claimed); ++ list_move_tail(&r->node, &claimed); + } + spin_unlock(&rtl->queue.lock); + +@@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) + smp_mb__before_atomic(); + clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); + +- list_del(&r->node); +- list_add_tail(&r->node, &claimed); ++ list_move_tail(&r->node, &claimed); + } + spin_unlock(&rtl->pending.lock); + } +-- +2.32.0 + +From d73fd93fdf881de16ce199225ce97f3c4ce602c5 Mon Sep 17 00:00:00 2001 +From: Baokun Li +Date: Wed, 9 Jun 2021 15:24:48 +0800 +Subject: [PATCH] platform/surface: aggregator: Use list_move_tail instead of + list_del/list_add_tail in ssh_packet_layer.c + +Using list_move_tail() instead of list_del() + list_add_tail() in ssh_packet_layer.c. + +Reported-by: Hulk Robot +Signed-off-by: Baokun Li +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/20210609072448.1357524-1-libaokun1@huawei.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/ssh_packet_layer.c | 10 +++------- + 1 file changed, 3 insertions(+), 7 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c +index 5e08049fc3ac..8a4451c1ffe5 100644 +--- a/drivers/platform/surface/aggregator/ssh_packet_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c +@@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + + atomic_dec(&ptl->pending.count); +- list_del(&p->pending_node); +- +- list_add_tail(&p->pending_node, &claimed); ++ list_move_tail(&p->pending_node, &claimed); + } + + spin_unlock(&ptl->pending.lock); +@@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); + +- list_del(&p->queue_node); +- list_add_tail(&p->queue_node, &complete_q); ++ list_move_tail(&p->queue_node, &complete_q); + } + spin_unlock(&ptl->queue.lock); + +@@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) + smp_mb__before_atomic(); + clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); + +- list_del(&p->pending_node); +- list_add_tail(&p->pending_node, &complete_q); ++ list_move_tail(&p->pending_node, &complete_q); + } + atomic_set(&ptl->pending.count, 0); + spin_unlock(&ptl->pending.lock); +-- +2.32.0 + diff --git a/patches/5.13/0007-surface-hotplug.patch b/patches/5.13/0007-surface-hotplug.patch new file mode 100644 index 000000000..22cfc23a3 --- /dev/null +++ b/patches/5.13/0007-surface-hotplug.patch @@ -0,0 +1,44 @@ +From dc902c77340127510f708d4619a82136d4672b86 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 2 Jul 2021 15:51:07 +0200 +Subject: [PATCH] Revert "Revert "PCI: PM: Do not read power state in + pci_enable_device_flags()"" + +This reverts commit 4d6035f9bf4ea12776322746a216e856dfe46698. + +Patchset: surface-hotplug +--- + drivers/pci/pci.c | 16 +++------------- + 1 file changed, 3 insertions(+), 13 deletions(-) + +diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c +index 8d4ebe095d0c..b717680377a9 100644 +--- a/drivers/pci/pci.c ++++ b/drivers/pci/pci.c +@@ -1900,20 +1900,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags) + int err; + int i, bars = 0; + +- /* +- * Power state could be unknown at this point, either due to a fresh +- * boot or a device removal call. So get the current power state +- * so that things like MSI message writing will behave as expected +- * (e.g. if the device really is in D0 at enable time). +- */ +- if (dev->pm_cap) { +- u16 pmcsr; +- pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr); +- dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK); +- } +- +- if (atomic_inc_return(&dev->enable_cnt) > 1) ++ if (atomic_inc_return(&dev->enable_cnt) > 1) { ++ pci_update_current_state(dev, dev->current_state); + return 0; /* already enabled */ ++ } + + bridge = pci_upstream_bridge(dev); + if (bridge) +-- +2.32.0 + diff --git a/patches/5.13/0008-surface-typecover.patch b/patches/5.13/0008-surface-typecover.patch new file mode 100644 index 000000000..4d52afc74 --- /dev/null +++ b/patches/5.13/0008-surface-typecover.patch @@ -0,0 +1,233 @@ +From 952b0184baebea91936f24626f6142826a69e0fe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 2e4fb76c45f3..d7a27d891fba 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -168,6 +175,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -210,6 +219,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + + #define MT_DEFAULT_MAXCONTACT 10 + #define MT_MAX_MAXCONTACT 250 +@@ -378,6 +388,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_SEPARATE_APP_REPORT, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1690,6 +1710,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1713,6 +1796,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1742,15 +1828,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1801,6 +1891,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2158,6 +2249,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.32.0 + diff --git a/patches/5.13/0009-cameras.patch b/patches/5.13/0009-cameras.patch new file mode 100644 index 000000000..75d289ea4 --- /dev/null +++ b/patches/5.13/0009-cameras.patch @@ -0,0 +1,5876 @@ +From e2f96f74015cf5e7b76dd0682cd820537d084855 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:02 +0100 +Subject: [PATCH] ACPI: scan: Extend acpi_walk_dep_device_list() + +The acpi_walk_dep_device_list() function is not as generic as its +name implies, serving only to decrement the dependency count for each +dependent device of the input. + +Extend it to accept a callback which can be applied to all the +dependencies in acpi_dep_list. + +Replace all existing calls to the function with calls to a wrapper, +passing a callback that applies the same dependency reduction. + +Reviewed-by: Andy Shevchenko +Acked-by: Maximilian Luz # for platform/surface parts +Signed-off-by: Daniel Scally +[ rjw: Changelog edits ] +Signed-off-by: Rafael J. Wysocki +Patchset: cameras +--- + drivers/acpi/ec.c | 2 +- + drivers/acpi/pmic/intel_pmic_chtdc_ti.c | 2 +- + drivers/acpi/scan.c | 69 ++++++++++++++----- + drivers/gpio/gpiolib-acpi.c | 10 +-- + drivers/i2c/i2c-core-acpi.c | 8 +-- + drivers/platform/surface/aggregator/core.c | 6 +- + drivers/platform/surface/surface3_power.c | 22 +++--- + .../platform/surface/surface_acpi_notify.c | 7 +- + include/acpi/acpi_bus.h | 7 ++ + include/linux/acpi.h | 4 +- + 10 files changed, 90 insertions(+), 47 deletions(-) + +diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c +index 87c3b4a099b9..e629e891d1bb 100644 +--- a/drivers/acpi/ec.c ++++ b/drivers/acpi/ec.c +@@ -1629,7 +1629,7 @@ static int acpi_ec_add(struct acpi_device *device) + WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr); + + /* Reprobe devices depending on the EC */ +- acpi_walk_dep_device_list(ec->handle); ++ acpi_dev_clear_dependencies(device); + + acpi_handle_debug(ec->handle, "enumerated.\n"); + return 0; +diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c +index a5101b07611a..fef7831d0d63 100644 +--- a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c ++++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c +@@ -117,7 +117,7 @@ static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev) + return err; + + /* Re-enumerate devices depending on PMIC */ +- acpi_walk_dep_device_list(ACPI_HANDLE(pdev->dev.parent)); ++ acpi_dev_clear_dependencies(ACPI_COMPANION(pdev->dev.parent)); + return 0; + } + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 438df8da6d12..607aeea9a210 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -47,12 +47,6 @@ static DEFINE_MUTEX(acpi_hp_context_lock); + */ + static u64 spcr_uart_addr; + +-struct acpi_dep_data { +- struct list_head node; +- acpi_handle supplier; +- acpi_handle consumer; +-}; +- + void acpi_scan_lock_acquire(void) + { + mutex_lock(&acpi_scan_lock); +@@ -2107,30 +2101,69 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) + device->handler->hotplug.notify_online(device); + } + +-void acpi_walk_dep_device_list(acpi_handle handle) ++static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) + { +- struct acpi_dep_data *dep, *tmp; + struct acpi_device *adev; + ++ acpi_bus_get_device(dep->consumer, &adev); ++ ++ if (adev) { ++ adev->dep_unmet--; ++ if (!adev->dep_unmet) ++ acpi_bus_attach(adev, true); ++ } ++ ++ list_del(&dep->node); ++ kfree(dep); ++ ++ return 0; ++} ++ ++/** ++ * acpi_walk_dep_device_list - Apply a callback to every entry in acpi_dep_list ++ * @handle: The ACPI handle of the supplier device ++ * @callback: Pointer to the callback function to apply ++ * @data: Pointer to some data to pass to the callback ++ * ++ * The return value of the callback determines this function's behaviour. If 0 ++ * is returned we continue to iterate over acpi_dep_list. If a positive value ++ * is returned then the loop is broken but this function returns 0. If a ++ * negative value is returned by the callback then the loop is broken and that ++ * value is returned as the final error. ++ */ ++int acpi_walk_dep_device_list(acpi_handle handle, ++ int (*callback)(struct acpi_dep_data *, void *), ++ void *data) ++{ ++ struct acpi_dep_data *dep, *tmp; ++ int ret; ++ + mutex_lock(&acpi_dep_list_lock); + list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) { + if (dep->supplier == handle) { +- acpi_bus_get_device(dep->consumer, &adev); +- +- if (adev) { +- adev->dep_unmet--; +- if (!adev->dep_unmet) +- acpi_bus_attach(adev, true); +- } +- +- list_del(&dep->node); +- kfree(dep); ++ ret = callback(dep, data); ++ if (ret) ++ break; + } + } + mutex_unlock(&acpi_dep_list_lock); ++ ++ return ret > 0 ? 0 : ret; + } + EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list); + ++/** ++ * acpi_dev_clear_dependencies - Inform consumers that the device is now active ++ * @supplier: Pointer to the supplier &struct acpi_device ++ * ++ * Clear dependencies on the given device. ++ */ ++void acpi_dev_clear_dependencies(struct acpi_device *supplier) ++{ ++ acpi_walk_dep_device_list(supplier->handle, acpi_scan_clear_dep, NULL); ++} ++EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); ++ + /** + * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. + * @handle: Root of the namespace scope to scan. +diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c +index 3ef22a3c104d..5b4111e4be3f 100644 +--- a/drivers/gpio/gpiolib-acpi.c ++++ b/drivers/gpio/gpiolib-acpi.c +@@ -1233,14 +1233,14 @@ static void acpi_gpiochip_scan_gpios(struct acpi_gpio_chip *achip) + void acpi_gpiochip_add(struct gpio_chip *chip) + { + struct acpi_gpio_chip *acpi_gpio; +- acpi_handle handle; ++ struct acpi_device *adev; + acpi_status status; + + if (!chip || !chip->parent) + return; + +- handle = ACPI_HANDLE(chip->parent); +- if (!handle) ++ adev = ACPI_COMPANION(chip->parent); ++ if (!adev) + return; + + acpi_gpio = kzalloc(sizeof(*acpi_gpio), GFP_KERNEL); +@@ -1254,7 +1254,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) + INIT_LIST_HEAD(&acpi_gpio->events); + INIT_LIST_HEAD(&acpi_gpio->deferred_req_irqs_list_entry); + +- status = acpi_attach_data(handle, acpi_gpio_chip_dh, acpi_gpio); ++ status = acpi_attach_data(adev->handle, acpi_gpio_chip_dh, acpi_gpio); + if (ACPI_FAILURE(status)) { + dev_err(chip->parent, "Failed to attach ACPI GPIO chip\n"); + kfree(acpi_gpio); +@@ -1263,7 +1263,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip) + + acpi_gpiochip_request_regions(acpi_gpio); + acpi_gpiochip_scan_gpios(acpi_gpio); +- acpi_walk_dep_device_list(handle); ++ acpi_dev_clear_dependencies(adev); + } + + void acpi_gpiochip_remove(struct gpio_chip *chip) +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index deceed0d76c6..13eb5ac82729 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -259,8 +259,8 @@ static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level, + */ + void i2c_acpi_register_devices(struct i2c_adapter *adap) + { ++ struct acpi_device *adev; + acpi_status status; +- acpi_handle handle; + + if (!has_acpi_companion(&adap->dev)) + return; +@@ -275,11 +275,11 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap) + if (!adap->dev.parent) + return; + +- handle = ACPI_HANDLE(adap->dev.parent); +- if (!handle) ++ adev = ACPI_COMPANION(adap->dev.parent); ++ if (!adev) + return; + +- acpi_walk_dep_device_list(handle); ++ acpi_dev_clear_dependencies(adev); + } + + static const struct acpi_device_id i2c_acpi_force_400khz_device_ids[] = { +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index 5d780e55f4a1..279d9df19c01 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -621,8 +621,8 @@ static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { + + static int ssam_serial_hub_probe(struct serdev_device *serdev) + { ++ struct acpi_device *ssh = ACPI_COMPANION(&serdev->dev); + struct ssam_controller *ctrl; +- acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); + acpi_status astatus; + int status; + +@@ -652,7 +652,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + if (status) + goto err_devopen; + +- astatus = ssam_serdev_setup_via_acpi(ssh, serdev); ++ astatus = ssam_serdev_setup_via_acpi(ssh->handle, serdev); + if (ACPI_FAILURE(astatus)) { + status = -ENXIO; + goto err_devinit; +@@ -706,7 +706,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev) + * For now let's thus default power/wakeup to false. + */ + device_set_wakeup_capable(&serdev->dev, true); +- acpi_walk_dep_device_list(ssh); ++ acpi_dev_clear_dependencies(ssh); + + return 0; + +diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c +index cc4f9cba6856..dea82aa1abd4 100644 +--- a/drivers/platform/surface/surface3_power.c ++++ b/drivers/platform/surface/surface3_power.c +@@ -446,12 +446,12 @@ mshw0011_space_handler(u32 function, acpi_physical_address command, + + static int mshw0011_install_space_handler(struct i2c_client *client) + { +- acpi_handle handle; ++ struct acpi_device *adev; + struct mshw0011_handler_data *data; + acpi_status status; + +- handle = ACPI_HANDLE(&client->dev); +- if (!handle) ++ adev = ACPI_COMPANION(&client->dev); ++ if (!adev) + return -ENODEV; + + data = kzalloc(sizeof(struct mshw0011_handler_data), +@@ -460,25 +460,25 @@ static int mshw0011_install_space_handler(struct i2c_client *client) + return -ENOMEM; + + data->client = client; +- status = acpi_bus_attach_private_data(handle, (void *)data); ++ status = acpi_bus_attach_private_data(adev->handle, (void *)data); + if (ACPI_FAILURE(status)) { + kfree(data); + return -ENOMEM; + } + +- status = acpi_install_address_space_handler(handle, +- ACPI_ADR_SPACE_GSBUS, +- &mshw0011_space_handler, +- NULL, +- data); ++ status = acpi_install_address_space_handler(adev->handle, ++ ACPI_ADR_SPACE_GSBUS, ++ &mshw0011_space_handler, ++ NULL, ++ data); + if (ACPI_FAILURE(status)) { + dev_err(&client->dev, "Error installing i2c space handler\n"); +- acpi_bus_detach_private_data(handle); ++ acpi_bus_detach_private_data(adev->handle); + kfree(data); + return -ENOMEM; + } + +- acpi_walk_dep_device_list(handle); ++ acpi_dev_clear_dependencies(adev); + return 0; + } + +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +index ef9c1f8e8336..8339988d95c1 100644 +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -798,7 +798,7 @@ static int san_consumer_links_setup(struct platform_device *pdev) + + static int san_probe(struct platform_device *pdev) + { +- acpi_handle san = ACPI_HANDLE(&pdev->dev); ++ struct acpi_device *san = ACPI_COMPANION(&pdev->dev); + struct ssam_controller *ctrl; + struct san_data *data; + acpi_status astatus; +@@ -821,7 +821,8 @@ static int san_probe(struct platform_device *pdev) + + platform_set_drvdata(pdev, data); + +- astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, ++ astatus = acpi_install_address_space_handler(san->handle, ++ ACPI_ADR_SPACE_GSBUS, + &san_opreg_handler, NULL, + &data->info); + if (ACPI_FAILURE(astatus)) +@@ -835,7 +836,7 @@ static int san_probe(struct platform_device *pdev) + if (status) + goto err_install_dev; + +- acpi_walk_dep_device_list(san); ++ acpi_dev_clear_dependencies(san); + return 0; + + err_install_dev: +diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h +index 3a82faac5767..0b2c4f170f4d 100644 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -280,6 +280,12 @@ struct acpi_device_power { + struct acpi_device_power_state states[ACPI_D_STATE_COUNT]; /* Power states (D0-D3Cold) */ + }; + ++struct acpi_dep_data { ++ struct list_head node; ++ acpi_handle supplier; ++ acpi_handle consumer; ++}; ++ + /* Performance Management */ + + struct acpi_device_perf_flags { +@@ -685,6 +691,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) + + bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); + ++void acpi_dev_clear_dependencies(struct acpi_device *supplier); + struct acpi_device * + acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); + struct acpi_device * +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +index c60745f657e9..170b9bebdb2b 100644 +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -666,7 +666,9 @@ extern bool acpi_driver_match_device(struct device *dev, + const struct device_driver *drv); + int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *); + int acpi_device_modalias(struct device *, char *, int); +-void acpi_walk_dep_device_list(acpi_handle handle); ++int acpi_walk_dep_device_list(acpi_handle handle, ++ int (*callback)(struct acpi_dep_data *, void *), ++ void *data); + + struct platform_device *acpi_create_platform_device(struct acpi_device *, + struct property_entry *); +-- +2.32.0 + +From 1b2cec071b7e0069eecdf0484ce8f00f13db73d0 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:03 +0100 +Subject: [PATCH] ACPI: scan: Add function to fetch dependent of ACPI device + +In some ACPI tables we encounter, devices use the _DEP method to assert +a dependence on other ACPI devices as opposed to the OpRegions that the +specification intends. + +We need to be able to find those devices "from" the dependee, so add +a callback and a wrapper to walk over the acpi_dep_list and return +the dependent ACPI device. + +Reviewed-by: Andy Shevchenko +Signed-off-by: Daniel Scally +Signed-off-by: Rafael J. Wysocki +Patchset: cameras +--- + drivers/acpi/scan.c | 35 +++++++++++++++++++++++++++++++++++ + include/acpi/acpi_bus.h | 1 + + 2 files changed, 36 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 607aeea9a210..7f36295579ce 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2101,6 +2101,20 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass) + device->handler->hotplug.notify_online(device); + } + ++static int acpi_dev_get_first_consumer_dev_cb(struct acpi_dep_data *dep, void *data) ++{ ++ struct acpi_device *adev; ++ ++ adev = acpi_bus_get_acpi_device(dep->consumer); ++ if (!adev) ++ /* If we don't find an adev then we want to continue parsing */ ++ return 0; ++ ++ *(struct acpi_device **)data = adev; ++ ++ return 1; ++} ++ + static int acpi_scan_clear_dep(struct acpi_dep_data *dep, void *data) + { + struct acpi_device *adev; +@@ -2164,6 +2178,27 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) + } + EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); + ++/** ++ * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier ++ * @supplier: Pointer to the dependee device ++ * ++ * Returns the first &struct acpi_device which declares itself dependent on ++ * @supplier via the _DEP buffer, parsed from the acpi_dep_list. ++ * ++ * The caller is responsible for putting the reference to adev when it is no ++ * longer needed. ++ */ ++struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier) ++{ ++ struct acpi_device *adev = NULL; ++ ++ acpi_walk_dep_device_list(supplier->handle, ++ acpi_dev_get_first_consumer_dev_cb, &adev); ++ ++ return adev; ++} ++EXPORT_SYMBOL_GPL(acpi_dev_get_first_consumer_dev); ++ + /** + * acpi_bus_scan - Add ACPI device node objects in a given namespace scope. + * @handle: Root of the namespace scope to scan. +diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h +index 0b2c4f170f4d..4bed30e61c5b 100644 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -692,6 +692,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev) + bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2); + + void acpi_dev_clear_dependencies(struct acpi_device *supplier); ++struct acpi_device *acpi_dev_get_first_consumer_dev(struct acpi_device *supplier); + struct acpi_device * + acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv); + struct acpi_device * +-- +2.32.0 + +From d42df2176c2630a5fca42e8b5800329abc3d92ac Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:04 +0100 +Subject: [PATCH] gpiolib: acpi: Introduce acpi_get_and_request_gpiod() helper + +We need to be able to translate GPIO resources in an ACPI device's _CRS +into GPIO descriptor array. Those are represented in _CRS as a pathname +to a GPIO device plus the pin's index number: the acpi_get_gpiod() +function is perfect for that purpose. + +As it's currently only used internally within the GPIO layer, provide and +export a wrapper function that additionally holds a reference to the GPIO +device. + +Reviewed-by: Andy Shevchenko +Signed-off-by: Daniel Scally +Signed-off-by: Andy Shevchenko +Patchset: cameras +--- + drivers/gpio/gpiolib-acpi.c | 28 ++++++++++++++++++++++++++++ + include/linux/gpio/consumer.h | 2 ++ + 2 files changed, 30 insertions(+) + +diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c +index 5b4111e4be3f..b5acb2c50836 100644 +--- a/drivers/gpio/gpiolib-acpi.c ++++ b/drivers/gpio/gpiolib-acpi.c +@@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) + return gpiochip_get_desc(chip, pin); + } + ++/** ++ * acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and ++ * hold a refcount to the GPIO device. ++ * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") ++ * @pin: ACPI GPIO pin number (0-based, controller-relative) ++ * @label: Label to pass to gpiod_request() ++ * ++ * This function is a simple pass-through to acpi_get_gpiod(), except that ++ * as it is intended for use outside of the GPIO layer (in a similar fashion to ++ * gpiod_get_index() for example) it also holds a reference to the GPIO device. ++ */ ++struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label) ++{ ++ struct gpio_desc *gpio; ++ int ret; ++ ++ gpio = acpi_get_gpiod(path, pin); ++ if (IS_ERR(gpio)) ++ return gpio; ++ ++ ret = gpiod_request(gpio, label); ++ if (ret) ++ return ERR_PTR(ret); ++ ++ return gpio; ++} ++EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod); ++ + static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) + { + struct acpi_gpio_event *event = data; +diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h +index c73b25bc9213..566feb56601f 100644 +--- a/include/linux/gpio/consumer.h ++++ b/include/linux/gpio/consumer.h +@@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, + const struct acpi_gpio_mapping *gpios); + void devm_acpi_dev_remove_driver_gpios(struct device *dev); + ++struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label); ++ + #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ + + struct acpi_device; +-- +2.32.0 + +From 5512d9fd2db7f8b2eec25d7edbeac3980816e5e6 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:05 +0100 +Subject: [PATCH] gpiolib: acpi: Add acpi_gpio_get_io_resource() + +Add a function to verify that a given ACPI resource represents a GpioIo() +type of resource, and return it if so. + +Reviewed-by: Andy Shevchenko +Signed-off-by: Daniel Scally +Signed-off-by: Andy Shevchenko +Patchset: cameras +--- + drivers/gpio/gpiolib-acpi.c | 23 +++++++++++++++++++++++ + include/linux/acpi.h | 7 +++++++ + 2 files changed, 30 insertions(+) + +diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c +index b5acb2c50836..411525ac4cc4 100644 +--- a/drivers/gpio/gpiolib-acpi.c ++++ b/drivers/gpio/gpiolib-acpi.c +@@ -196,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, + } + EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); + ++/** ++ * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO ++ * I/O resource or return False if not. ++ * @ares: Pointer to the ACPI resource to fetch ++ * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer ++ */ ++bool acpi_gpio_get_io_resource(struct acpi_resource *ares, ++ struct acpi_resource_gpio **agpio) ++{ ++ struct acpi_resource_gpio *gpio; ++ ++ if (ares->type != ACPI_RESOURCE_TYPE_GPIO) ++ return false; ++ ++ gpio = &ares->data.gpio; ++ if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) ++ return false; ++ ++ *agpio = gpio; ++ return true; ++} ++EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource); ++ + static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, + struct acpi_gpio_event *event) + { +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +index 170b9bebdb2b..e8ba7063c000 100644 +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -1098,6 +1098,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c + #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) + bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, + struct acpi_resource_gpio **agpio); ++bool acpi_gpio_get_io_resource(struct acpi_resource *ares, ++ struct acpi_resource_gpio **agpio); + int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); + #else + static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, +@@ -1105,6 +1107,11 @@ static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, + { + return false; + } ++static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, ++ struct acpi_resource_gpio **agpio) ++{ ++ return false; ++} + static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, + const char *name, int index) + { +-- +2.32.0 + +From 60e4b245124183d33b750624152e7e16ce87dda3 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:06 +0100 +Subject: [PATCH] platform/x86: Add intel_skl_int3472 driver + +ACPI devices with _HID INT3472 are currently matched to the tps68470 +driver, however this does not cover all situations in which that _HID +occurs. We've encountered three possibilities: + +1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing +a physical TPS68470 device) that requires a GPIO and OpRegion driver +2. On devices designed for Windows, an ACPI device with _HID INT3472 +(again representing a physical TPS68470 device) which requires GPIO, +Clock and Regulator drivers. +3. On other devices designed for Windows, an ACPI device with _HID +INT3472 which does **not** represent a physical TPS68470, and is instead +used as a dummy device to group some system GPIO lines which are meant +to be consumed by the sensor that is dependent on this entry. + +This commit adds a new module, registering a platform driver to deal +with the 3rd scenario plus an i2c driver to deal with #1 and #2, by +querying the CLDB buffer found against INT3472 entries to determine +which is most appropriate. + +Suggested-by: Laurent Pinchart +Signed-off-by: Daniel Scally +Link: https://lore.kernel.org/r/20210603224007.120560-6-djrscally@gmail.com +[hdegoede@redhat.com Make skl_int3472_tps68470_calc_type() static] +Signed-off-by: Hans de Goede +Patchset: cameras +--- + MAINTAINERS | 5 + + drivers/platform/x86/Kconfig | 2 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/intel-int3472/Kconfig | 30 ++ + drivers/platform/x86/intel-int3472/Makefile | 5 + + .../intel_skl_int3472_clk_and_regulator.c | 196 ++++++++ + .../intel-int3472/intel_skl_int3472_common.c | 106 +++++ + .../intel-int3472/intel_skl_int3472_common.h | 118 +++++ + .../intel_skl_int3472_discrete.c | 417 ++++++++++++++++++ + .../intel_skl_int3472_tps68470.c | 137 ++++++ + 10 files changed, 1017 insertions(+) + create mode 100644 drivers/platform/x86/intel-int3472/Kconfig + create mode 100644 drivers/platform/x86/intel-int3472/Makefile + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c + create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 0cce91cd5624..1db7311d78a6 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -9386,6 +9386,11 @@ S: Maintained + F: arch/x86/include/asm/intel_scu_ipc.h + F: drivers/platform/x86/intel_scu_* + ++INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER ++M: Daniel Scally ++S: Maintained ++F: drivers/platform/x86/intel-int3472/ ++ + INTEL SPEED SELECT TECHNOLOGY + M: Srinivas Pandruvada + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 60592fb88e7a..88134aaacefc 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -697,6 +697,8 @@ config INTEL_CHT_INT33FE + device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m + for Type-C device. + ++source "drivers/platform/x86/intel-int3472/Kconfig" ++ + config INTEL_HID_EVENT + tristate "INTEL HID Event" + depends on ACPI +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index dcc8cdb95b4d..c0612c02d037 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -76,6 +76,7 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o + obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o + obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o + obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o ++obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ + obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o + + # MSI +diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig +new file mode 100644 +index 000000000000..c112878e833b +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/Kconfig +@@ -0,0 +1,30 @@ ++config INTEL_SKL_INT3472 ++ tristate "Intel SkyLake ACPI INT3472 Driver" ++ depends on ACPI ++ depends on COMMON_CLK && CLKDEV_LOOKUP ++ depends on I2C ++ depends on GPIOLIB ++ depends on REGULATOR ++ select MFD_CORE ++ select REGMAP_I2C ++ help ++ This driver adds power controller support for the Intel SkyCam ++ devices found on the Intel SkyLake platforms. ++ ++ The INT3472 is a camera power controller, a logical device found on ++ Intel Skylake-based systems that can map to different hardware ++ devices depending on the platform. On machines designed for Chrome OS ++ it maps to a TPS68470 camera PMIC. On machines designed for Windows, ++ it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a ++ set of discrete GPIOs and power gates. ++ ++ If your device was designed for Chrome OS, this driver will provide ++ an ACPI OpRegion, which must be available before any of the devices ++ using it are probed. For this reason, you should select Y if your ++ device was designed for ChromeOS. For the same reason the ++ I2C_DESIGNWARE_PLATFORM option must be set to Y too. ++ ++ Say Y or M here if you have a SkyLake device designed for use ++ with Windows or ChromeOS. Say N here if you are not sure. ++ ++ The module will be named "intel-skl-int3472". +diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile +new file mode 100644 +index 000000000000..48bd97f0a04e +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/Makefile +@@ -0,0 +1,5 @@ ++obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o ++intel_skl_int3472-objs := intel_skl_int3472_common.o \ ++ intel_skl_int3472_discrete.o \ ++ intel_skl_int3472_tps68470.o \ ++ intel_skl_int3472_clk_and_regulator.o +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +new file mode 100644 +index 000000000000..ceee860e2c07 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +@@ -0,0 +1,196 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++/* ++ * The regulators have to have .ops to be valid, but the only ops we actually ++ * support are .enable and .disable which are handled via .ena_gpiod. Pass an ++ * empty struct to clear the check without lying about capabilities. ++ */ ++static const struct regulator_ops int3472_gpio_regulator_ops; ++ ++static int skl_int3472_clk_prepare(struct clk_hw *hw) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ ++ gpiod_set_value_cansleep(clk->ena_gpio, 1); ++ gpiod_set_value_cansleep(clk->led_gpio, 1); ++ ++ return 0; ++} ++ ++static void skl_int3472_clk_unprepare(struct clk_hw *hw) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ ++ gpiod_set_value_cansleep(clk->ena_gpio, 0); ++ gpiod_set_value_cansleep(clk->led_gpio, 0); ++} ++ ++static int skl_int3472_clk_enable(struct clk_hw *hw) ++{ ++ /* ++ * We're just turning a GPIO on to enable the clock, which operation ++ * has the potential to sleep. Given .enable() cannot sleep, but ++ * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, ++ * nothing to do here. ++ */ ++ return 0; ++} ++ ++static void skl_int3472_clk_disable(struct clk_hw *hw) ++{ ++ /* Likewise, nothing to do here... */ ++} ++ ++static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) ++{ ++ union acpi_object *obj; ++ unsigned int freq; ++ ++ obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); ++ if (IS_ERR(obj)) ++ return 0; /* report rate as 0 on error */ ++ ++ if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { ++ dev_err(int3472->dev, "The buffer is too small\n"); ++ kfree(obj); ++ return 0; ++ } ++ ++ freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); ++ ++ kfree(obj); ++ return freq; ++} ++ ++static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, ++ unsigned long parent_rate) ++{ ++ struct int3472_gpio_clock *clk = to_int3472_clk(hw); ++ ++ return clk->frequency; ++} ++ ++static const struct clk_ops skl_int3472_clock_ops = { ++ .prepare = skl_int3472_clk_prepare, ++ .unprepare = skl_int3472_clk_unprepare, ++ .enable = skl_int3472_clk_enable, ++ .disable = skl_int3472_clk_disable, ++ .recalc_rate = skl_int3472_clk_recalc_rate, ++}; ++ ++int skl_int3472_register_clock(struct int3472_discrete_device *int3472) ++{ ++ struct clk_init_data init = { ++ .ops = &skl_int3472_clock_ops, ++ .flags = CLK_GET_RATE_NOCACHE, ++ }; ++ int ret; ++ ++ init.name = kasprintf(GFP_KERNEL, "%s-clk", ++ acpi_dev_name(int3472->adev)); ++ if (!init.name) ++ return -ENOMEM; ++ ++ int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); ++ ++ int3472->clock.clk_hw.init = &init; ++ int3472->clock.clk = clk_register(&int3472->adev->dev, ++ &int3472->clock.clk_hw); ++ if (IS_ERR(int3472->clock.clk)) { ++ ret = PTR_ERR(int3472->clock.clk); ++ goto out_free_init_name; ++ } ++ ++ int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, ++ int3472->sensor_name); ++ if (!int3472->clock.cl) { ++ ret = -ENOMEM; ++ goto err_unregister_clk; ++ } ++ ++ kfree(init.name); ++ return 0; ++ ++err_unregister_clk: ++ clk_unregister(int3472->clock.clk); ++out_free_init_name: ++ kfree(init.name); ++ ++ return ret; ++} ++ ++int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ const struct int3472_sensor_config *sensor_config; ++ struct regulator_consumer_supply supply_map; ++ struct regulator_init_data init_data = { }; ++ struct regulator_config cfg = { }; ++ int ret; ++ ++ sensor_config = int3472->sensor_config; ++ if (IS_ERR(sensor_config)) { ++ dev_err(int3472->dev, "No sensor module config\n"); ++ return PTR_ERR(sensor_config); ++ } ++ ++ if (!sensor_config->supply_map.supply) { ++ dev_err(int3472->dev, "No supply name defined\n"); ++ return -ENODEV; ++ } ++ ++ init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; ++ init_data.num_consumer_supplies = 1; ++ supply_map = sensor_config->supply_map; ++ supply_map.dev_name = int3472->sensor_name; ++ init_data.consumer_supplies = &supply_map; ++ ++ snprintf(int3472->regulator.regulator_name, ++ sizeof(int3472->regulator.regulator_name), "%s-regulator", ++ acpi_dev_name(int3472->adev)); ++ snprintf(int3472->regulator.supply_name, ++ GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); ++ ++ int3472->regulator.rdesc = INT3472_REGULATOR( ++ int3472->regulator.regulator_name, ++ int3472->regulator.supply_name, ++ &int3472_gpio_regulator_ops); ++ ++ int3472->regulator.gpio = acpi_get_and_request_gpiod(path, ++ ares->data.gpio.pin_table[0], ++ "int3472,regulator"); ++ if (IS_ERR(int3472->regulator.gpio)) { ++ dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); ++ return PTR_ERR(int3472->regulator.gpio); ++ } ++ ++ cfg.dev = &int3472->adev->dev; ++ cfg.init_data = &init_data; ++ cfg.ena_gpiod = int3472->regulator.gpio; ++ ++ int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, ++ &cfg); ++ if (IS_ERR(int3472->regulator.rdev)) { ++ ret = PTR_ERR(int3472->regulator.rdev); ++ goto err_free_gpio; ++ } ++ ++ return 0; ++ ++err_free_gpio: ++ gpiod_put(int3472->regulator.gpio); ++ ++ return ret; ++} +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +new file mode 100644 +index 000000000000..497e74fba75f +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +@@ -0,0 +1,106 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) ++{ ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ acpi_handle handle = adev->handle; ++ union acpi_object *obj; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(handle, id, NULL, &buffer); ++ if (ACPI_FAILURE(status)) ++ return ERR_PTR(-ENODEV); ++ ++ obj = buffer.pointer; ++ if (!obj) ++ return ERR_PTR(-ENODEV); ++ ++ if (obj->type != ACPI_TYPE_BUFFER) { ++ acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); ++ kfree(obj); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return obj; ++} ++ ++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) ++{ ++ union acpi_object *obj; ++ int ret; ++ ++ obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); ++ if (IS_ERR(obj)) ++ return PTR_ERR(obj); ++ ++ if (obj->buffer.length > sizeof(*cldb)) { ++ acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); ++ ret = -EINVAL; ++ goto out_free_obj; ++ } ++ ++ memcpy(cldb, obj->buffer.pointer, obj->buffer.length); ++ ret = 0; ++ ++out_free_obj: ++ kfree(obj); ++ return ret; ++} ++ ++static const struct acpi_device_id int3472_device_id[] = { ++ { "INT3472", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, int3472_device_id); ++ ++static struct platform_driver int3472_discrete = { ++ .driver = { ++ .name = "int3472-discrete", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe = skl_int3472_discrete_probe, ++ .remove = skl_int3472_discrete_remove, ++}; ++ ++static struct i2c_driver int3472_tps68470 = { ++ .driver = { ++ .name = "int3472-tps68470", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe_new = skl_int3472_tps68470_probe, ++}; ++ ++static int skl_int3472_init(void) ++{ ++ int ret; ++ ++ ret = platform_driver_register(&int3472_discrete); ++ if (ret) ++ return ret; ++ ++ ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); ++ if (ret) ++ platform_driver_unregister(&int3472_discrete); ++ ++ return ret; ++} ++module_init(skl_int3472_init); ++ ++static void skl_int3472_exit(void) ++{ ++ platform_driver_unregister(&int3472_discrete); ++ i2c_del_driver(&int3472_tps68470); ++} ++module_exit(skl_int3472_exit); ++ ++MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +new file mode 100644 +index 000000000000..6fdf78584219 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +@@ -0,0 +1,118 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* Author: Dan Scally */ ++ ++#ifndef _INTEL_SKL_INT3472_H ++#define _INTEL_SKL_INT3472_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ ++#ifndef I2C_DEV_NAME_FORMAT ++#define I2C_DEV_NAME_FORMAT "i2c-%s" ++#endif ++ ++/* PMIC GPIO Types */ ++#define INT3472_GPIO_TYPE_RESET 0x00 ++#define INT3472_GPIO_TYPE_POWERDOWN 0x01 ++#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b ++#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c ++#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d ++ ++#define INT3472_PDEV_MAX_NAME_LEN 23 ++#define INT3472_MAX_SENSOR_GPIOS 3 ++ ++#define GPIO_REGULATOR_NAME_LENGTH 21 ++#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 ++ ++#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 ++ ++#define INT3472_REGULATOR(_name, _supply, _ops) \ ++ (const struct regulator_desc) { \ ++ .name = _name, \ ++ .supply_name = _supply, \ ++ .type = REGULATOR_VOLTAGE, \ ++ .ops = _ops, \ ++ .owner = THIS_MODULE, \ ++ } ++ ++#define to_int3472_clk(hw) \ ++ container_of(hw, struct int3472_gpio_clock, clk_hw) ++ ++#define to_int3472_device(clk) \ ++ container_of(clk, struct int3472_discrete_device, clock) ++ ++struct acpi_device; ++struct i2c_client; ++struct platform_device; ++ ++struct int3472_cldb { ++ u8 version; ++ /* ++ * control logic type ++ * 0: UNKNOWN ++ * 1: DISCRETE(CRD-D) ++ * 2: PMIC TPS68470 ++ * 3: PMIC uP6641 ++ */ ++ u8 control_logic_type; ++ u8 control_logic_id; ++ u8 sensor_card_sku; ++ u8 reserved[28]; ++}; ++ ++struct int3472_gpio_function_remap { ++ const char *documented; ++ const char *actual; ++}; ++ ++struct int3472_sensor_config { ++ const char *sensor_module_name; ++ struct regulator_consumer_supply supply_map; ++ const struct int3472_gpio_function_remap *function_maps; ++}; ++ ++struct int3472_discrete_device { ++ struct acpi_device *adev; ++ struct device *dev; ++ struct acpi_device *sensor; ++ const char *sensor_name; ++ ++ const struct int3472_sensor_config *sensor_config; ++ ++ struct int3472_gpio_regulator { ++ char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; ++ char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; ++ struct gpio_desc *gpio; ++ struct regulator_dev *rdev; ++ struct regulator_desc rdesc; ++ } regulator; ++ ++ struct int3472_gpio_clock { ++ struct clk *clk; ++ struct clk_hw clk_hw; ++ struct clk_lookup *cl; ++ struct gpio_desc *ena_gpio; ++ struct gpio_desc *led_gpio; ++ u32 frequency; ++ } clock; ++ ++ unsigned int ngpios; /* how many GPIOs have we seen */ ++ unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ ++ struct gpiod_lookup_table gpios; ++}; ++ ++int skl_int3472_discrete_probe(struct platform_device *pdev); ++int skl_int3472_discrete_remove(struct platform_device *pdev); ++int skl_int3472_tps68470_probe(struct i2c_client *client); ++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, ++ char *id); ++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); ++int skl_int3472_register_clock(struct int3472_discrete_device *int3472); ++int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares); ++ ++#endif +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +new file mode 100644 +index 000000000000..8c18dbff1c43 +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -0,0 +1,417 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++/* ++ * 79234640-9e10-4fea-a5c1-b5aa8b19756f ++ * This _DSM GUID returns information about the GPIO lines mapped to a ++ * discrete INT3472 device. Function number 1 returns a count of the GPIO ++ * lines that are mapped. Subsequent functions return 32 bit ints encoding ++ * information about the GPIO line, including its purpose. ++ */ ++static const guid_t int3472_gpio_guid = ++ GUID_INIT(0x79234640, 0x9e10, 0x4fea, ++ 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); ++ ++/* ++ * 822ace8f-2814-4174-a56b-5f029fe079ee ++ * This _DSM GUID returns a string from the sensor device, which acts as a ++ * module identifier. ++ */ ++static const guid_t cio2_sensor_module_guid = ++ GUID_INIT(0x822ace8f, 0x2814, 0x4174, ++ 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); ++ ++/* ++ * Here follows platform specific mapping information that we can pass to ++ * the functions mapping resources to the sensors. Where the sensors have ++ * a power enable pin defined in DSDT we need to provide a supply name so ++ * the sensor drivers can find the regulator. The device name will be derived ++ * from the sensor's ACPI device within the code. Optionally, we can provide a ++ * NULL terminated array of function name mappings to deal with any platform ++ * specific deviations from the documented behaviour of GPIOs. ++ * ++ * Map a GPIO function name to NULL to prevent the driver from mapping that ++ * GPIO at all. ++ */ ++ ++static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { ++ { "reset", NULL }, ++ { "powerdown", "reset" }, ++ { } ++}; ++ ++static const struct int3472_sensor_config int3472_sensor_configs[] = { ++ /* Lenovo Miix 510-12ISK - OV2680, Front */ ++ { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, ++ /* Lenovo Miix 510-12ISK - OV5648, Rear */ ++ { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, ++ /* Surface Go 1&2 - OV5693, Front */ ++ { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, ++}; ++ ++static const struct int3472_sensor_config * ++skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) ++{ ++ union acpi_object *obj; ++ unsigned int i; ++ ++ obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, ++ &cio2_sensor_module_guid, 0x00, ++ 0x01, NULL, ACPI_TYPE_STRING); ++ ++ if (!obj) { ++ dev_err(int3472->dev, ++ "Failed to get sensor module string from _DSM\n"); ++ return ERR_PTR(-ENODEV); ++ } ++ ++ if (obj->string.type != ACPI_TYPE_STRING) { ++ dev_err(int3472->dev, ++ "Sensor _DSM returned a non-string value\n"); ++ ++ ACPI_FREE(obj); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { ++ if (!strcmp(int3472_sensor_configs[i].sensor_module_name, ++ obj->string.pointer)) ++ break; ++ } ++ ++ ACPI_FREE(obj); ++ ++ if (i >= ARRAY_SIZE(int3472_sensor_configs)) ++ return ERR_PTR(-EINVAL); ++ ++ return &int3472_sensor_configs[i]; ++} ++ ++static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares, ++ const char *func, u32 polarity) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ const struct int3472_sensor_config *sensor_config; ++ struct gpiod_lookup *table_entry; ++ struct acpi_device *adev; ++ acpi_handle handle; ++ acpi_status status; ++ int ret; ++ ++ if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { ++ dev_warn(int3472->dev, "Too many GPIOs mapped\n"); ++ return -EINVAL; ++ } ++ ++ sensor_config = int3472->sensor_config; ++ if (!IS_ERR(sensor_config) && sensor_config->function_maps) { ++ const struct int3472_gpio_function_remap *remap; ++ ++ for (remap = sensor_config->function_maps; remap->documented; remap++) { ++ if (!strcmp(func, remap->documented)) { ++ func = remap->actual; ++ break; ++ } ++ } ++ } ++ ++ /* Functions mapped to NULL should not be mapped to the sensor */ ++ if (!func) ++ return 0; ++ ++ status = acpi_get_handle(NULL, path, &handle); ++ if (ACPI_FAILURE(status)) ++ return -EINVAL; ++ ++ ret = acpi_bus_get_device(handle, &adev); ++ if (ret) ++ return -ENODEV; ++ ++ table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; ++ table_entry->key = acpi_dev_name(adev); ++ table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; ++ table_entry->con_id = func; ++ table_entry->idx = 0; ++ table_entry->flags = polarity; ++ ++ int3472->n_sensor_gpios++; ++ ++ return 0; ++} ++ ++static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, ++ struct acpi_resource *ares, u8 type) ++{ ++ char *path = ares->data.gpio.resource_source.string_ptr; ++ struct gpio_desc *gpio; ++ ++ switch (type) { ++ case INT3472_GPIO_TYPE_CLK_ENABLE: ++ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], ++ "int3472,clk-enable"); ++ if (IS_ERR(gpio)) ++ return (PTR_ERR(gpio)); ++ ++ int3472->clock.ena_gpio = gpio; ++ break; ++ case INT3472_GPIO_TYPE_PRIVACY_LED: ++ gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], ++ "int3472,privacy-led"); ++ if (IS_ERR(gpio)) ++ return (PTR_ERR(gpio)); ++ ++ int3472->clock.led_gpio = gpio; ++ break; ++ default: ++ dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); ++ break; ++ } ++ ++ return 0; ++} ++ ++/** ++ * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor ++ * @ares: A pointer to a &struct acpi_resource ++ * @data: A pointer to a &struct int3472_discrete_device ++ * ++ * This function handles GPIO resources that are against an INT3472 ++ * ACPI device, by checking the value of the corresponding _DSM entry. ++ * This will return a 32bit int, where the lowest byte represents the ++ * function of the GPIO pin: ++ * ++ * 0x00 Reset ++ * 0x01 Power down ++ * 0x0b Power enable ++ * 0x0c Clock enable ++ * 0x0d Privacy LED ++ * ++ * There are some known platform specific quirks where that does not quite ++ * hold up; for example where a pin with type 0x01 (Power down) is mapped to ++ * a sensor pin that performs a reset function or entries in _CRS and _DSM that ++ * do not actually correspond to a physical connection. These will be handled ++ * by the mapping sub-functions. ++ * ++ * GPIOs will either be mapped directly to the sensor device or else used ++ * to create clocks and regulators via the usual frameworks. ++ * ++ * Return: ++ * * 1 - To continue the loop ++ * * 0 - When all resources found are handled properly. ++ * * -EINVAL - If the resource is not a GPIO IO resource ++ * * -ENODEV - If the resource has no corresponding _DSM entry ++ * * -Other - Errors propagated from one of the sub-functions. ++ */ ++static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, ++ void *data) ++{ ++ struct int3472_discrete_device *int3472 = data; ++ struct acpi_resource_gpio *agpio; ++ union acpi_object *obj; ++ const char *err_msg; ++ int ret; ++ u8 type; ++ ++ if (!acpi_gpio_get_io_resource(ares, &agpio)) ++ return 1; ++ ++ /* ++ * ngpios + 2 because the index of this _DSM function is 1-based and ++ * the first function is just a count. ++ */ ++ obj = acpi_evaluate_dsm_typed(int3472->adev->handle, ++ &int3472_gpio_guid, 0x00, ++ int3472->ngpios + 2, ++ NULL, ACPI_TYPE_INTEGER); ++ ++ if (!obj) { ++ dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", ++ ares->data.gpio.pin_table[0]); ++ return 1; ++ } ++ ++ type = obj->integer.value & 0xff; ++ ++ switch (type) { ++ case INT3472_GPIO_TYPE_RESET: ++ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", ++ GPIO_ACTIVE_LOW); ++ if (ret) ++ err_msg = "Failed to map reset pin to sensor\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_POWERDOWN: ++ ret = skl_int3472_map_gpio_to_sensor(int3472, ares, ++ "powerdown", ++ GPIO_ACTIVE_LOW); ++ if (ret) ++ err_msg = "Failed to map powerdown pin to sensor\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_CLK_ENABLE: ++ case INT3472_GPIO_TYPE_PRIVACY_LED: ++ ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); ++ if (ret) ++ err_msg = "Failed to map GPIO to clock\n"; ++ ++ break; ++ case INT3472_GPIO_TYPE_POWER_ENABLE: ++ ret = skl_int3472_register_regulator(int3472, ares); ++ if (ret) ++ err_msg = "Failed to map regulator to sensor\n"; ++ ++ break; ++ default: ++ dev_warn(int3472->dev, ++ "GPIO type 0x%02x unknown; the sensor may not work\n", ++ type); ++ ret = 1; ++ break; ++ } ++ ++ int3472->ngpios++; ++ ACPI_FREE(obj); ++ ++ if (ret) ++ return dev_err_probe(int3472->dev, ret, err_msg); ++ ++ return 0; ++} ++ ++static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) ++{ ++ LIST_HEAD(resource_list); ++ int ret; ++ ++ /* ++ * No error check, because not having a sensor config is not necessarily ++ * a failure mode. ++ */ ++ int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); ++ ++ ret = acpi_dev_get_resources(int3472->adev, &resource_list, ++ skl_int3472_handle_gpio_resources, ++ int3472); ++ if (ret) ++ goto out_free_res_list; ++ ++ /* ++ * If we find no clock enable GPIO pin then the privacy LED won't work. ++ * We've never seen that situation, but it's possible. Warn the user so ++ * it's clear what's happened. ++ */ ++ if (int3472->clock.ena_gpio) { ++ ret = skl_int3472_register_clock(int3472); ++ if (ret) ++ goto out_free_res_list; ++ } else { ++ if (int3472->clock.led_gpio) ++ dev_warn(int3472->dev, ++ "No clk GPIO. The privacy LED won't work\n"); ++ } ++ ++ int3472->gpios.dev_id = int3472->sensor_name; ++ gpiod_add_lookup_table(&int3472->gpios); ++ ++out_free_res_list: ++ acpi_dev_free_resource_list(&resource_list); ++ ++ return ret; ++} ++ ++int skl_int3472_discrete_probe(struct platform_device *pdev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); ++ struct int3472_discrete_device *int3472; ++ struct int3472_cldb cldb; ++ int ret; ++ ++ ret = skl_int3472_fill_cldb(adev, &cldb); ++ if (ret) { ++ dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); ++ return ret; ++ } ++ ++ if (cldb.control_logic_type != 1) { ++ dev_err(&pdev->dev, "Unsupported control logic type %u\n", ++ cldb.control_logic_type); ++ return -EINVAL; ++ } ++ ++ /* Max num GPIOs we've seen plus a terminator */ ++ int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, ++ INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); ++ if (!int3472) ++ return -ENOMEM; ++ ++ int3472->adev = adev; ++ int3472->dev = &pdev->dev; ++ platform_set_drvdata(pdev, int3472); ++ ++ int3472->sensor = acpi_dev_get_first_consumer_dev(adev); ++ if (!int3472->sensor) { ++ dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); ++ return -ENODEV; ++ } ++ ++ int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, ++ I2C_DEV_NAME_FORMAT, ++ acpi_dev_name(int3472->sensor)); ++ if (!int3472->sensor_name) { ++ ret = -ENOMEM; ++ goto err_put_sensor; ++ } ++ ++ /* ++ * Initialising this list means we can call gpiod_remove_lookup_table() ++ * in failure paths without issue. ++ */ ++ INIT_LIST_HEAD(&int3472->gpios.list); ++ ++ ret = skl_int3472_parse_crs(int3472); ++ if (ret) { ++ skl_int3472_discrete_remove(pdev); ++ return ret; ++ } ++ ++ return 0; ++ ++err_put_sensor: ++ acpi_dev_put(int3472->sensor); ++ ++ return ret; ++} ++ ++int skl_int3472_discrete_remove(struct platform_device *pdev) ++{ ++ struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); ++ ++ gpiod_remove_lookup_table(&int3472->gpios); ++ regulator_unregister(int3472->regulator.rdev); ++ clk_unregister(int3472->clock.clk); ++ ++ if (int3472->clock.cl) ++ clkdev_drop(int3472->clock.cl); ++ ++ gpiod_put(int3472->regulator.gpio); ++ gpiod_put(int3472->clock.ena_gpio); ++ gpiod_put(int3472->clock.led_gpio); ++ ++ return 0; ++} +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +new file mode 100644 +index 000000000000..c05b4cf502fe +--- /dev/null ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +@@ -0,0 +1,137 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "intel_skl_int3472_common.h" ++ ++#define DESIGNED_FOR_CHROMEOS 1 ++#define DESIGNED_FOR_WINDOWS 2 ++ ++static const struct mfd_cell tps68470_cros[] = { ++ { .name = "tps68470-gpio" }, ++ { .name = "tps68470_pmic_opregion" }, ++}; ++ ++static const struct mfd_cell tps68470_win[] = { ++ { .name = "tps68470-gpio" }, ++ { .name = "tps68470-clk" }, ++ { .name = "tps68470-regulator" }, ++}; ++ ++static const struct regmap_config tps68470_regmap_config = { ++ .reg_bits = 8, ++ .val_bits = 8, ++ .max_register = TPS68470_REG_MAX, ++}; ++ ++static int tps68470_chip_init(struct device *dev, struct regmap *regmap) ++{ ++ unsigned int version; ++ int ret; ++ ++ /* Force software reset */ ++ ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); ++ if (ret) ++ return ret; ++ ++ ret = regmap_read(regmap, TPS68470_REG_REVID, &version); ++ if (ret) { ++ dev_err(dev, "Failed to read revision register: %d\n", ret); ++ return ret; ++ } ++ ++ dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); ++ ++ return 0; ++} ++ ++/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for ++ * @adev: A pointer to a &struct acpi_device ++ * ++ * Check CLDB buffer against the PMIC's adev. If present, then we check ++ * the value of control_logic_type field and follow one of the ++ * following scenarios: ++ * ++ * 1. No CLDB - likely ACPI tables designed for ChromeOS. We ++ * create platform devices for the GPIOs and OpRegion drivers. ++ * ++ * 2. CLDB, with control_logic_type = 2 - probably ACPI tables ++ * made for Windows 2-in-1 platforms. Register pdevs for GPIO, ++ * Clock and Regulator drivers to bind to. ++ * ++ * 3. Any other value in control_logic_type, we should never have ++ * gotten to this point; fail probe and return. ++ * ++ * Return: ++ * * 1 Device intended for ChromeOS ++ * * 2 Device intended for Windows ++ * * -EINVAL Where @adev has an object named CLDB but it does not conform to ++ * our expectations ++ */ ++static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) ++{ ++ struct int3472_cldb cldb = { 0 }; ++ int ret; ++ ++ /* ++ * A CLDB buffer that exists, but which does not match our expectations ++ * should trigger an error so we don't blindly continue. ++ */ ++ ret = skl_int3472_fill_cldb(adev, &cldb); ++ if (ret && ret != -ENODEV) ++ return ret; ++ ++ if (ret) ++ return DESIGNED_FOR_CHROMEOS; ++ ++ if (cldb.control_logic_type != 2) ++ return -EINVAL; ++ ++ return DESIGNED_FOR_WINDOWS; ++} ++ ++int skl_int3472_tps68470_probe(struct i2c_client *client) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(&client->dev); ++ struct regmap *regmap; ++ int device_type; ++ int ret; ++ ++ regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); ++ if (IS_ERR(regmap)) { ++ dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); ++ return PTR_ERR(regmap); ++ } ++ ++ i2c_set_clientdata(client, regmap); ++ ++ ret = tps68470_chip_init(&client->dev, regmap); ++ if (ret < 0) { ++ dev_err(&client->dev, "TPS68470 init error %d\n", ret); ++ return ret; ++ } ++ ++ device_type = skl_int3472_tps68470_calc_type(adev); ++ switch (device_type) { ++ case DESIGNED_FOR_WINDOWS: ++ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, ++ tps68470_win, ARRAY_SIZE(tps68470_win), ++ NULL, 0, NULL); ++ break; ++ case DESIGNED_FOR_CHROMEOS: ++ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, ++ tps68470_cros, ARRAY_SIZE(tps68470_cros), ++ NULL, 0, NULL); ++ break; ++ default: ++ dev_err(&client->dev, "Failed to add MFD devices\n"); ++ return device_type; ++ } ++ ++ return ret; ++} +-- +2.32.0 + +From 1cbdca0dd169f231cb04b062ffde1eda5550a704 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 3 Jun 2021 23:40:07 +0100 +Subject: [PATCH] mfd: tps68470: Remove tps68470 MFD driver + +This driver only covered one scenario in which ACPI devices with _HID +INT3472 are found, and its functionality has been taken over by the +intel-skl-int3472 module, so remove it. + +Acked-by: Andy Shevchenko +Acked-by: Lee Jones +Reviewed-by: Laurent Pinchart +Signed-off-by: Daniel Scally +Link: https://lore.kernel.org/r/20210603224007.120560-7-djrscally@gmail.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/pmic/Kconfig | 2 +- + drivers/gpio/Kconfig | 2 +- + drivers/mfd/Kconfig | 18 -------- + drivers/mfd/Makefile | 1 - + drivers/mfd/tps68470.c | 97 --------------------------------------- + 5 files changed, 2 insertions(+), 118 deletions(-) + delete mode 100644 drivers/mfd/tps68470.c + +diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig +index 56bbcb2ce61b..f84b8f6038dc 100644 +--- a/drivers/acpi/pmic/Kconfig ++++ b/drivers/acpi/pmic/Kconfig +@@ -52,7 +52,7 @@ endif # PMIC_OPREGION + + config TPS68470_PMIC_OPREGION + bool "ACPI operation region support for TPS68470 PMIC" +- depends on MFD_TPS68470 ++ depends on INTEL_SKL_INT3472 + help + This config adds ACPI operation region support for TI TPS68470 PMIC. + TPS68470 device is an advanced power management unit that powers +diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig +index 3c69b785cb79..63b84ba161dc 100644 +--- a/drivers/gpio/Kconfig ++++ b/drivers/gpio/Kconfig +@@ -1367,7 +1367,7 @@ config GPIO_TPS65912 + + config GPIO_TPS68470 + bool "TPS68470 GPIO" +- depends on MFD_TPS68470 ++ depends on INTEL_SKL_INT3472 + help + Select this option to enable GPIO driver for the TPS68470 + chip family. +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 5c408c1dc58c..ace0d1b8f55c 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -1500,24 +1500,6 @@ config MFD_TPS65217 + This driver can also be built as a module. If so, the module + will be called tps65217. + +-config MFD_TPS68470 +- bool "TI TPS68470 Power Management / LED chips" +- depends on ACPI && PCI && I2C=y +- depends on I2C_DESIGNWARE_PLATFORM=y +- select MFD_CORE +- select REGMAP_I2C +- help +- If you say yes here you get support for the TPS68470 series of +- Power Management / LED chips. +- +- These include voltage regulators, LEDs and other features +- that are often used in portable devices. +- +- This option is a bool as it provides an ACPI operation +- region, which must be available before any of the devices +- using this are probed. This option also configures the +- designware-i2c driver to be built-in, for the same reason. +- + config MFD_TI_LP873X + tristate "TI LP873X Power Management IC" + depends on I2C +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 4f6d2b8a5f76..8b322d89a0c5 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o + obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o + obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o + obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o +-obj-$(CONFIG_MFD_TPS68470) += tps68470.o + obj-$(CONFIG_MFD_TPS80031) += tps80031.o + obj-$(CONFIG_MENELAUS) += menelaus.o + +diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c +deleted file mode 100644 +index 4a4df4ffd18c..000000000000 +--- a/drivers/mfd/tps68470.c ++++ /dev/null +@@ -1,97 +0,0 @@ +-// SPDX-License-Identifier: GPL-2.0 +-/* +- * TPS68470 chip Parent driver +- * +- * Copyright (C) 2017 Intel Corporation +- * +- * Authors: +- * Rajmohan Mani +- * Tianshu Qiu +- * Jian Xu Zheng +- * Yuning Pu +- */ +- +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +-static const struct mfd_cell tps68470s[] = { +- { .name = "tps68470-gpio" }, +- { .name = "tps68470_pmic_opregion" }, +-}; +- +-static const struct regmap_config tps68470_regmap_config = { +- .reg_bits = 8, +- .val_bits = 8, +- .max_register = TPS68470_REG_MAX, +-}; +- +-static int tps68470_chip_init(struct device *dev, struct regmap *regmap) +-{ +- unsigned int version; +- int ret; +- +- /* Force software reset */ +- ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); +- if (ret) +- return ret; +- +- ret = regmap_read(regmap, TPS68470_REG_REVID, &version); +- if (ret) { +- dev_err(dev, "Failed to read revision register: %d\n", ret); +- return ret; +- } +- +- dev_info(dev, "TPS68470 REVID: 0x%x\n", version); +- +- return 0; +-} +- +-static int tps68470_probe(struct i2c_client *client) +-{ +- struct device *dev = &client->dev; +- struct regmap *regmap; +- int ret; +- +- regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); +- if (IS_ERR(regmap)) { +- dev_err(dev, "devm_regmap_init_i2c Error %ld\n", +- PTR_ERR(regmap)); +- return PTR_ERR(regmap); +- } +- +- i2c_set_clientdata(client, regmap); +- +- ret = tps68470_chip_init(dev, regmap); +- if (ret < 0) { +- dev_err(dev, "TPS68470 Init Error %d\n", ret); +- return ret; +- } +- +- ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, +- ARRAY_SIZE(tps68470s), NULL, 0, NULL); +- if (ret < 0) { +- dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); +- return ret; +- } +- +- return 0; +-} +- +-static const struct acpi_device_id tps68470_acpi_ids[] = { +- {"INT3472"}, +- {}, +-}; +- +-static struct i2c_driver tps68470_driver = { +- .driver = { +- .name = "tps68470", +- .acpi_match_table = tps68470_acpi_ids, +- }, +- .probe_new = tps68470_probe, +-}; +-builtin_i2c_driver(tps68470_driver); +-- +2.32.0 + +From f13e17ff4576afb3001b174ac8b4b524ab93543f Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:10 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Free ACPI device resources + after use + +We may free ACPI device resources immediately after use. +Refactor skl_int3472_parse_crs() accordingly. + +Signed-off-by: Andy Shevchenko +Reviewed-by: Daniel Scally +Tested-by: Daniel Scally +Link: https://lore.kernel.org/r/20210618125516.53510-2-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + .../x86/intel-int3472/intel_skl_int3472_discrete.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +index 8c18dbff1c43..48a00a1f4fb6 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -308,8 +308,10 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) + ret = acpi_dev_get_resources(int3472->adev, &resource_list, + skl_int3472_handle_gpio_resources, + int3472); +- if (ret) +- goto out_free_res_list; ++ if (ret < 0) ++ return ret; ++ ++ acpi_dev_free_resource_list(&resource_list); + + /* + * If we find no clock enable GPIO pin then the privacy LED won't work. +@@ -319,7 +321,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) + if (int3472->clock.ena_gpio) { + ret = skl_int3472_register_clock(int3472); + if (ret) +- goto out_free_res_list; ++ return ret; + } else { + if (int3472->clock.led_gpio) + dev_warn(int3472->dev, +@@ -329,10 +331,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) + int3472->gpios.dev_id = int3472->sensor_name; + gpiod_add_lookup_table(&int3472->gpios); + +-out_free_res_list: +- acpi_dev_free_resource_list(&resource_list); +- +- return ret; ++ return 0; + } + + int skl_int3472_discrete_probe(struct platform_device *pdev) +-- +2.32.0 + +From 09f9e91d7d377c8b5506ad6c49cea57ea1c4981a Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:11 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Fix dependencies (drop + CLKDEV_LOOKUP) + +Besides the fact that COMMON_CLK selects CLKDEV_LOOKUP, the latter +is going to be removed from clock framework. + +Reviewed-by: Daniel Scally +Signed-off-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20210618125516.53510-3-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel-int3472/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig +index c112878e833b..62e5d4cf9ee5 100644 +--- a/drivers/platform/x86/intel-int3472/Kconfig ++++ b/drivers/platform/x86/intel-int3472/Kconfig +@@ -1,7 +1,7 @@ + config INTEL_SKL_INT3472 + tristate "Intel SkyLake ACPI INT3472 Driver" + depends on ACPI +- depends on COMMON_CLK && CLKDEV_LOOKUP ++ depends on COMMON_CLK + depends on I2C + depends on GPIOLIB + depends on REGULATOR +-- +2.32.0 + +From a43840fc14c54451d42e2db54c4fccbe05ecd64f Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:12 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Use ACPI GPIO resource + directly + +When we call acpi_gpio_get_io_resource(), the output will be +the pointer to the ACPI GPIO resource. Use it directly instead of +dereferencing the generic resource. + +Signed-off-by: Andy Shevchenko +Reviewed-by: Daniel Scally +Tested-by: Daniel Scally +Link: https://lore.kernel.org/r/20210618125516.53510-4-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + .../intel_skl_int3472_clk_and_regulator.c | 7 ++--- + .../intel-int3472/intel_skl_int3472_common.h | 2 +- + .../intel_skl_int3472_discrete.c | 28 +++++++++---------- + 3 files changed, 17 insertions(+), 20 deletions(-) + +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +index ceee860e2c07..49ea1e86c193 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +@@ -131,10 +131,10 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472) + } + + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, +- struct acpi_resource *ares) ++ struct acpi_resource_gpio *agpio) + { +- char *path = ares->data.gpio.resource_source.string_ptr; + const struct int3472_sensor_config *sensor_config; ++ char *path = agpio->resource_source.string_ptr; + struct regulator_consumer_supply supply_map; + struct regulator_init_data init_data = { }; + struct regulator_config cfg = { }; +@@ -168,8 +168,7 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + int3472->regulator.supply_name, + &int3472_gpio_regulator_ops); + +- int3472->regulator.gpio = acpi_get_and_request_gpiod(path, +- ares->data.gpio.pin_table[0], ++ int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], + "int3472,regulator"); + if (IS_ERR(int3472->regulator.gpio)) { + dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +index 6fdf78584219..765e01ec1604 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +@@ -113,6 +113,6 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, + int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); + int skl_int3472_register_clock(struct int3472_discrete_device *int3472); + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, +- struct acpi_resource *ares); ++ struct acpi_resource_gpio *agpio); + + #endif +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +index 48a00a1f4fb6..fd681d2a73fe 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -103,11 +103,11 @@ skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) + } + + static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, +- struct acpi_resource *ares, ++ struct acpi_resource_gpio *agpio, + const char *func, u32 polarity) + { +- char *path = ares->data.gpio.resource_source.string_ptr; + const struct int3472_sensor_config *sensor_config; ++ char *path = agpio->resource_source.string_ptr; + struct gpiod_lookup *table_entry; + struct acpi_device *adev; + acpi_handle handle; +@@ -145,7 +145,7 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + + table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; + table_entry->key = acpi_dev_name(adev); +- table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; ++ table_entry->chip_hwnum = agpio->pin_table[0]; + table_entry->con_id = func; + table_entry->idx = 0; + table_entry->flags = polarity; +@@ -156,23 +156,22 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + } + + static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, +- struct acpi_resource *ares, u8 type) ++ struct acpi_resource_gpio *agpio, u8 type) + { +- char *path = ares->data.gpio.resource_source.string_ptr; ++ char *path = agpio->resource_source.string_ptr; ++ u16 pin = agpio->pin_table[0]; + struct gpio_desc *gpio; + + switch (type) { + case INT3472_GPIO_TYPE_CLK_ENABLE: +- gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], +- "int3472,clk-enable"); ++ gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + + int3472->clock.ena_gpio = gpio; + break; + case INT3472_GPIO_TYPE_PRIVACY_LED: +- gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], +- "int3472,privacy-led"); ++ gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + +@@ -242,7 +241,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + + if (!obj) { + dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", +- ares->data.gpio.pin_table[0]); ++ agpio->pin_table[0]); + return 1; + } + +@@ -250,15 +249,14 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + + switch (type) { + case INT3472_GPIO_TYPE_RESET: +- ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", ++ ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map reset pin to sensor\n"; + + break; + case INT3472_GPIO_TYPE_POWERDOWN: +- ret = skl_int3472_map_gpio_to_sensor(int3472, ares, +- "powerdown", ++ ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map powerdown pin to sensor\n"; +@@ -266,13 +264,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + break; + case INT3472_GPIO_TYPE_CLK_ENABLE: + case INT3472_GPIO_TYPE_PRIVACY_LED: +- ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); ++ ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); + if (ret) + err_msg = "Failed to map GPIO to clock\n"; + + break; + case INT3472_GPIO_TYPE_POWER_ENABLE: +- ret = skl_int3472_register_regulator(int3472, ares); ++ ret = skl_int3472_register_regulator(int3472, agpio); + if (ret) + err_msg = "Failed to map regulator to sensor\n"; + +-- +2.32.0 + +From 974314d679603b02858356fb28762901d8292cac Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:13 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Provide + skl_int3472_unregister_regulator() + +For the sake of APIs to be properly layered provide +skl_int3472_unregister_regulator(). + +Signed-off-by: Andy Shevchenko +Reviewed-by: Daniel Scally +Tested-by: Daniel Scally +Link: https://lore.kernel.org/r/20210618125516.53510-5-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ + .../platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ + .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 4 ++-- + 3 files changed, 10 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +index 49ea1e86c193..60c7128f44ee 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +@@ -193,3 +193,9 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + + return ret; + } ++ ++void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) ++{ ++ regulator_unregister(int3472->regulator.rdev); ++ gpiod_put(int3472->regulator.gpio); ++} +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +index 765e01ec1604..50f73c6eab44 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +@@ -112,7 +112,9 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, + char *id); + int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); + int skl_int3472_register_clock(struct int3472_discrete_device *int3472); ++ + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio); ++void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); + + #endif +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +index fd681d2a73fe..2638d375e226 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -400,15 +400,15 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) + struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); + + gpiod_remove_lookup_table(&int3472->gpios); +- regulator_unregister(int3472->regulator.rdev); + clk_unregister(int3472->clock.clk); + + if (int3472->clock.cl) + clkdev_drop(int3472->clock.cl); + +- gpiod_put(int3472->regulator.gpio); + gpiod_put(int3472->clock.ena_gpio); + gpiod_put(int3472->clock.led_gpio); + ++ skl_int3472_unregister_regulator(int3472); ++ + return 0; + } +-- +2.32.0 + +From 4d7b2d994ef8aa2bbbcdc1c0c10cf92acdbce3aa Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:14 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Provide + skl_int3472_unregister_clock() + +For the sake of APIs to be properly layered provide +skl_int3472_unregister_clock(). + +Signed-off-by: Andy Shevchenko +Reviewed-by: Daniel Scally +Tested-by: Daniel Scally +Link: https://lore.kernel.org/r/20210618125516.53510-6-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ + .../platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ + .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 5 ++--- + 3 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +index 60c7128f44ee..1700e7557a82 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +@@ -130,6 +130,12 @@ int skl_int3472_register_clock(struct int3472_discrete_device *int3472) + return ret; + } + ++void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) ++{ ++ clkdev_drop(int3472->clock.cl); ++ clk_unregister(int3472->clock.clk); ++} ++ + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio) + { +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +index 50f73c6eab44..714fde73b524 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +@@ -111,7 +111,9 @@ int skl_int3472_tps68470_probe(struct i2c_client *client); + union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, + char *id); + int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); ++ + int skl_int3472_register_clock(struct int3472_discrete_device *int3472); ++void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); + + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio); +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +index 2638d375e226..17c6fe830765 100644 +--- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +@@ -400,10 +400,9 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) + struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); + + gpiod_remove_lookup_table(&int3472->gpios); +- clk_unregister(int3472->clock.clk); + +- if (int3472->clock.cl) +- clkdev_drop(int3472->clock.cl); ++ if (int3472->clock.ena_gpio) ++ skl_int3472_unregister_clock(int3472); + + gpiod_put(int3472->clock.ena_gpio); + gpiod_put(int3472->clock.led_gpio); +-- +2.32.0 + +From 2c8f3b4e7134b49f4fb7cda79d478c8088310fd9 Mon Sep 17 00:00:00 2001 +From: Andy Shevchenko +Date: Fri, 18 Jun 2021 15:55:15 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Move to intel/ subfolder + +Start collecting Intel x86 related drivers in its own subfolder. +Move intel_skl_int3472 first. + +Signed-off-by: Andy Shevchenko +Link: https://lore.kernel.org/r/20210618125516.53510-7-andriy.shevchenko@linux.intel.com +Signed-off-by: Hans de Goede +Patchset: cameras +--- + MAINTAINERS | 2 +- + drivers/platform/x86/Kconfig | 4 ++-- + drivers/platform/x86/Makefile | 3 ++- + drivers/platform/x86/intel/Kconfig | 21 +++++++++++++++++++ + drivers/platform/x86/intel/Makefile | 7 +++++++ + .../{intel-int3472 => intel/int3472}/Kconfig | 0 + .../{intel-int3472 => intel/int3472}/Makefile | 0 + .../intel_skl_int3472_clk_and_regulator.c | 0 + .../int3472}/intel_skl_int3472_common.c | 0 + .../int3472}/intel_skl_int3472_common.h | 0 + .../int3472}/intel_skl_int3472_discrete.c | 0 + .../int3472}/intel_skl_int3472_tps68470.c | 0 + 12 files changed, 33 insertions(+), 4 deletions(-) + create mode 100644 drivers/platform/x86/intel/Kconfig + create mode 100644 drivers/platform/x86/intel/Makefile + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/Kconfig (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/Makefile (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/intel_skl_int3472_clk_and_regulator.c (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/intel_skl_int3472_common.c (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/intel_skl_int3472_common.h (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/intel_skl_int3472_discrete.c (100%) + rename drivers/platform/x86/{intel-int3472 => intel/int3472}/intel_skl_int3472_tps68470.c (100%) + +diff --git a/MAINTAINERS b/MAINTAINERS +index 1db7311d78a6..bdafd166d80d 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -9389,7 +9389,7 @@ F: drivers/platform/x86/intel_scu_* + INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER + M: Daniel Scally + S: Maintained +-F: drivers/platform/x86/intel-int3472/ ++F: drivers/platform/x86/intel/int3472/ + + INTEL SPEED SELECT TECHNOLOGY + M: Srinivas Pandruvada +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 88134aaacefc..607b57478d78 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -639,6 +639,8 @@ config THINKPAD_ACPI_HOTKEY_POLL + If you are not sure, say Y here. The driver enables polling only if + it is strictly necessary to do so. + ++source "drivers/platform/x86/intel/Kconfig" ++ + config INTEL_ATOMISP2_LED + tristate "Intel AtomISP2 camera LED driver" + depends on GPIOLIB && LEDS_GPIO +@@ -697,8 +699,6 @@ config INTEL_CHT_INT33FE + device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m + for Type-C device. + +-source "drivers/platform/x86/intel-int3472/Kconfig" +- + config INTEL_HID_EVENT + tristate "INTEL HID Event" + depends on ACPI +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index c0612c02d037..67491ac827c4 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -66,6 +66,8 @@ obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + + # Intel ++obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ ++ + obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o + obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o + obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o +@@ -76,7 +78,6 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o + obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o + obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o + obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +-obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ + obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o + + # MSI +diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig +new file mode 100644 +index 000000000000..33f2dab03d3d +--- /dev/null ++++ b/drivers/platform/x86/intel/Kconfig +@@ -0,0 +1,21 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++# ++# Intel x86 Platform Specific Drivers ++# ++ ++menuconfig X86_PLATFORM_DRIVERS_INTEL ++ bool "Intel x86 Platform Specific Device Drivers" ++ default y ++ help ++ Say Y here to get to see options for device drivers for ++ various Intel x86 platforms, including vendor-specific ++ drivers. This option alone does not add any kernel code. ++ ++ If you say N, all options in this submenu will be skipped ++ and disabled. ++ ++if X86_PLATFORM_DRIVERS_INTEL ++ ++source "drivers/platform/x86/intel/int3472/Kconfig" ++ ++endif # X86_PLATFORM_DRIVERS_INTEL +diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile +new file mode 100644 +index 000000000000..3ac795d810f1 +--- /dev/null ++++ b/drivers/platform/x86/intel/Makefile +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: GPL-2.0 ++# ++# Makefile for drivers/platform/x86/intel ++# Intel x86 Platform-Specific Drivers ++# ++ ++obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ +diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel/int3472/Kconfig +similarity index 100% +rename from drivers/platform/x86/intel-int3472/Kconfig +rename to drivers/platform/x86/intel/int3472/Kconfig +diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile +similarity index 100% +rename from drivers/platform/x86/intel-int3472/Makefile +rename to drivers/platform/x86/intel/int3472/Makefile +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c +similarity index 100% +rename from drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +rename to drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c +similarity index 100% +rename from drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +rename to drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h +similarity index 100% +rename from drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +rename to drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +similarity index 100% +rename from drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +rename to drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c +similarity index 100% +rename from drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +rename to drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c +-- +2.32.0 + +From 2ca8dd619a7eb521e4289549aa8e6a3ab7cf8971 Mon Sep 17 00:00:00 2001 +From: Dan Carpenter +Date: Fri, 25 Jun 2021 16:01:04 +0300 +Subject: [PATCH] platform/x86: intel_skl_int3472: Uninitialized variable in + skl_int3472_handle_gpio_resources() + +This function returns negative error codes, zero (to indicate that +everything has been completed successfully) and one (to indicate that +more resources need to be handled still). + +This code prints an uninitialized error message when the function +returns one which potentially leads to an Oops. + +Fixes: 5de691bffe57 ("platform/x86: Add intel_skl_int3472 driver") +Signed-off-by: Dan Carpenter +Reviewed-by: Daniel Scally +Link: https://lore.kernel.org/r/YNXTkLNtiTDlFlZa@mwanda +Signed-off-by: Hans de Goede +Patchset: cameras +--- + .../platform/x86/intel/int3472/intel_skl_int3472_discrete.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +index 17c6fe830765..9fe0a2527e1c 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +@@ -286,10 +286,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + int3472->ngpios++; + ACPI_FREE(obj); + +- if (ret) ++ if (ret < 0) + return dev_err_probe(int3472->dev, ret, err_msg); + +- return 0; ++ return ret; + } + + static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) +-- +2.32.0 + +From fe04b16bb1e8cd203adfcae167d8854c493d8e64 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 5 Apr 2021 23:56:53 +0100 +Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops + +The .suspend() and .resume() runtime_pm operations for the ipu3-cio2 +driver currently do not handle the sensor's stream. Setting .s_stream() on +or off for the sensor subdev means that sensors will pause and resume the +stream at the appropriate time even if their drivers don't implement those +operations. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index fecef85bd62e..9dafb9470708 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct cio2_device *cio2 = pci_get_drvdata(pci_dev); + struct cio2_queue *q = cio2->cur_queue; ++ int r; + + dev_dbg(dev, "cio2 suspend\n"); + if (!cio2->streaming) + return 0; + + /* Stop stream */ ++ r = v4l2_subdev_call(q->sensor, video, s_stream, 0); ++ if (r) { ++ dev_err(dev, "failed to stop sensor streaming\n"); ++ return r; ++ } ++ + cio2_hw_exit(cio2, q); + synchronize_irq(pci_dev->irq); + +@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev) + } + + r = cio2_hw_init(cio2, q); +- if (r) ++ if (r) { + dev_err(dev, "fail to init cio2 hw\n"); ++ return r; ++ } ++ ++ r = v4l2_subdev_call(q->sensor, video, s_stream, 1); ++ if (r) ++ dev_err(dev, "fail to start sensor streaming\n"); + + return r; + } +-- +2.32.0 + +From b507643e34d1b8768a48540f6919fbbcc409607c Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 5 Apr 2021 23:56:54 +0100 +Subject: [PATCH] media: i2c: Add support for ov5693 sensor + +The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The +chip is capable of a single lane configuration, but currently only two +lanes are supported. + +Most of the sensor's features are supported, with the main exception +being the lens correction algorithm. + +The driver provides all mandatory, optional and recommended V4L2 controls +for maximum compatibility with libcamera. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + MAINTAINERS | 7 + + drivers/media/i2c/Kconfig | 11 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++ + 4 files changed, 1576 insertions(+) + create mode 100644 drivers/media/i2c/ov5693.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index bdafd166d80d..123962e41739 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13563,6 +13563,13 @@ S: Maintained + T: git git://linuxtv.org/media_tree.git + F: drivers/media/i2c/ov5675.c + ++OMNIVISION OV5693 SENSOR DRIVER ++M: Daniel Scally ++L: linux-media@vger.kernel.org ++S: Maintained ++T: git git://linuxtv.org/media_tree.git ++F: drivers/media/i2c/ov5693.c ++ + OMNIVISION OV5695 SENSOR DRIVER + M: Shunqian Zheng + L: linux-media@vger.kernel.org +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 462c0e059754..2893e74af99a 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -999,6 +999,17 @@ config VIDEO_OV5675 + To compile this driver as a module, choose M here: the + module will be called ov5675. + ++config VIDEO_OV5693 ++ tristate "OmniVision OV5693 sensor support" ++ depends on I2C && VIDEO_V4L2 ++ select V4L2_FWNODE ++ help ++ This is a Video4Linux2 sensor driver for the OmniVision ++ OV5693 camera. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called ov5693. ++ + config VIDEO_OV5695 + tristate "OmniVision OV5695 sensor support" + depends on I2C && VIDEO_V4L2 +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index 0c067beca066..7d0884bc89f1 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o + obj-$(CONFIG_VIDEO_OV5648) += ov5648.o + obj-$(CONFIG_VIDEO_OV5670) += ov5670.o + obj-$(CONFIG_VIDEO_OV5675) += ov5675.o ++obj-$(CONFIG_VIDEO_OV5693) += ov5693.o + obj-$(CONFIG_VIDEO_OV5695) += ov5695.o + obj-$(CONFIG_VIDEO_OV6650) += ov6650.o + obj-$(CONFIG_VIDEO_OV7251) += ov7251.o +diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c +new file mode 100644 +index 000000000000..276f625d4d23 +--- /dev/null ++++ b/drivers/media/i2c/ov5693.c +@@ -0,0 +1,1557 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Copyright (c) 2013 Intel Corporation. All Rights Reserved. ++ * ++ * Adapted from the atomisp-ov5693 driver, with contributions from: ++ * ++ * Daniel Scally ++ * Jean-Michel Hautbois ++ * Fabian Wuthrich ++ * Tsuchiya Yuto ++ * Jordan Hand ++ * Jake Day ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* System Control */ ++#define OV5693_SW_RESET_REG 0x0103 ++#define OV5693_SW_STREAM_REG 0x0100 ++#define OV5693_START_STREAMING 0x01 ++#define OV5693_STOP_STREAMING 0x00 ++#define OV5693_SW_RESET 0x01 ++ ++#define OV5693_REG_CHIP_ID_H 0x300a ++#define OV5693_REG_CHIP_ID_L 0x300b ++/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */ ++#define OV5693_CHIP_ID 0x5690 ++ ++/* Exposure */ ++#define OV5693_EXPOSURE_L_CTRL_HH_REG 0x3500 ++#define OV5693_EXPOSURE_L_CTRL_H_REG 0x3501 ++#define OV5693_EXPOSURE_L_CTRL_L_REG 0x3502 ++#define OV5693_EXPOSURE_CTRL_HH(v) (((v) & GENMASK(14, 12)) >> 12) ++#define OV5693_EXPOSURE_CTRL_H(v) (((v) & GENMASK(11, 4)) >> 4) ++#define OV5693_EXPOSURE_CTRL_L(v) (((v) & GENMASK(3, 0)) << 4) ++#define OV5693_INTEGRATION_TIME_MARGIN 8 ++#define OV5693_EXPOSURE_MIN 1 ++#define OV5693_EXPOSURE_STEP 1 ++ ++/* Analogue Gain */ ++#define OV5693_GAIN_CTRL_H_REG 0x350a ++#define OV5693_GAIN_CTRL_H(v) (((v) >> 4) & GENMASK(2, 0)) ++#define OV5693_GAIN_CTRL_L_REG 0x350b ++#define OV5693_GAIN_CTRL_L(v) (((v) << 4) & GENMASK(7, 4)) ++#define OV5693_GAIN_MIN 1 ++#define OV5693_GAIN_MAX 127 ++#define OV5693_GAIN_DEF 8 ++#define OV5693_GAIN_STEP 1 ++ ++/* Digital Gain */ ++#define OV5693_MWB_RED_GAIN_H_REG 0x3400 ++#define OV5693_MWB_RED_GAIN_L_REG 0x3401 ++#define OV5693_MWB_GREEN_GAIN_H_REG 0x3402 ++#define OV5693_MWB_GREEN_GAIN_L_REG 0x3403 ++#define OV5693_MWB_BLUE_GAIN_H_REG 0x3404 ++#define OV5693_MWB_BLUE_GAIN_L_REG 0x3405 ++#define OV5693_MWB_GAIN_H_CTRL(v) (((v) >> 8) & GENMASK(3, 0)) ++#define OV5693_MWB_GAIN_L_CTRL(v) ((v) & GENMASK(7, 0)) ++#define OV5693_MWB_GAIN_MAX 0x0fff ++#define OV5693_DIGITAL_GAIN_MIN 1 ++#define OV5693_DIGITAL_GAIN_MAX 4095 ++#define OV5693_DIGITAL_GAIN_DEF 1024 ++#define OV5693_DIGITAL_GAIN_STEP 1 ++ ++/* Timing and Format */ ++#define OV5693_CROP_START_X_H_REG 0x3800 ++#define OV5693_CROP_START_X_H(v) (((v) & GENMASK(12, 8)) >> 8) ++#define OV5693_CROP_START_X_L_REG 0x3801 ++#define OV5693_CROP_START_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_START_Y_H_REG 0x3802 ++#define OV5693_CROP_START_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) ++#define OV5693_CROP_START_Y_L_REG 0x3803 ++#define OV5693_CROP_START_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_END_X_H_REG 0x3804 ++#define OV5693_CROP_END_X_H(v) (((v) & GENMASK(12, 8)) >> 8) ++#define OV5693_CROP_END_X_L_REG 0x3805 ++#define OV5693_CROP_END_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_CROP_END_Y_H_REG 0x3806 ++#define OV5693_CROP_END_Y_H(v) (((v) & GENMASK(11, 8)) >> 8) ++#define OV5693_CROP_END_Y_L_REG 0x3807 ++#define OV5693_CROP_END_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OUTPUT_SIZE_X_H_REG 0x3808 ++#define OV5693_OUTPUT_SIZE_X_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OUTPUT_SIZE_X_L_REG 0x3809 ++#define OV5693_OUTPUT_SIZE_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OUTPUT_SIZE_Y_H_REG 0x380a ++#define OV5693_OUTPUT_SIZE_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OUTPUT_SIZE_Y_L_REG 0x380b ++#define OV5693_OUTPUT_SIZE_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_TIMING_HTS_H_REG 0x380c ++#define OV5693_TIMING_HTS_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_TIMING_HTS_L_REG 0x380d ++#define OV5693_TIMING_HTS_L(v) ((v) & GENMASK(7, 0)) ++#define OV5693_FIXED_PPL 2688U ++ ++#define OV5693_TIMING_VTS_H_REG 0x380e ++#define OV5693_TIMING_VTS_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_TIMING_VTS_L_REG 0x380f ++#define OV5693_TIMING_VTS_L(v) ((v) & GENMASK(7, 0)) ++#define OV5693_TIMING_MAX_VTS 0xffff ++#define OV5693_TIMING_MIN_VTS 0x04 ++ ++#define OV5693_OFFSET_START_X_H_REG 0x3810 ++#define OV5693_OFFSET_START_X_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OFFSET_START_X_L_REG 0x3811 ++#define OV5693_OFFSET_START_X_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_OFFSET_START_Y_H_REG 0x3812 ++#define OV5693_OFFSET_START_Y_H(v) (((v) & GENMASK(15, 8)) >> 8) ++#define OV5693_OFFSET_START_Y_L_REG 0x3813 ++#define OV5693_OFFSET_START_Y_L(v) ((v) & GENMASK(7, 0)) ++ ++#define OV5693_SUB_INC_X_REG 0x3814 ++#define OV5693_SUB_INC_Y_REG 0x3815 ++ ++#define OV5693_FORMAT1_REG 0x3820 ++#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) ++#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) ++#define OV5693_FORMAT1_VBIN_EN BIT(0) ++#define OV5693_FORMAT2_REG 0x3821 ++#define OV5693_FORMAT2_HDR_EN BIT(7) ++#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN BIT(2) ++#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN BIT(1) ++#define OV5693_FORMAT2_HBIN_EN BIT(0) ++ ++#define OV5693_ISP_CTRL2_REG 0x5002 ++#define OV5693_ISP_SCALE_ENABLE BIT(7) ++ ++/* Pixel Array */ ++#define OV5693_NATIVE_WIDTH 2624 ++#define OV5693_NATIVE_HEIGHT 1956 ++#define OV5693_NATIVE_START_LEFT 0 ++#define OV5693_NATIVE_START_TOP 0 ++#define OV5693_ACTIVE_WIDTH 2592 ++#define OV5693_ACTIVE_HEIGHT 1944 ++#define OV5693_ACTIVE_START_LEFT 16 ++#define OV5693_ACTIVE_START_TOP 6 ++#define OV5693_MIN_CROP_WIDTH 2 ++#define OV5693_MIN_CROP_HEIGHT 2 ++ ++/* Test Pattern */ ++#define OV5693_TEST_PATTERN_REG 0x5e00 ++#define OV5693_TEST_PATTERN_ENABLE BIT(7) ++#define OV5693_TEST_PATTERN_ROLLING BIT(6) ++#define OV5693_TEST_PATTERN_RANDOM 0x01 ++#define OV5693_TEST_PATTERN_BARS 0x00 ++ ++/* System Frequencies */ ++#define OV5693_XVCLK_FREQ 19200000 ++#define OV5693_LINK_FREQ_400MHZ 400000000 ++#define OV5693_PIXEL_RATE 160000000 ++ ++/* Miscellaneous */ ++#define OV5693_NUM_SUPPLIES 2 ++ ++#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd) ++ ++struct ov5693_reg { ++ u16 reg; ++ u8 val; ++}; ++ ++struct ov5693_reg_list { ++ u32 num_regs; ++ const struct ov5693_reg *regs; ++}; ++ ++struct ov5693_device { ++ struct i2c_client *client; ++ struct device *dev; ++ ++ /* Protect against concurrent changes to controls */ ++ struct mutex lock; ++ ++ struct gpio_desc *reset; ++ struct gpio_desc *powerdown; ++ struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES]; ++ struct clk *clk; ++ ++ struct ov5693_mode { ++ struct v4l2_rect crop; ++ struct v4l2_mbus_framefmt format; ++ bool binning_x; ++ bool binning_y; ++ unsigned int inc_x_odd; ++ unsigned int inc_y_odd; ++ unsigned int vts; ++ } mode; ++ bool streaming; ++ ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ ++ struct ov5693_v4l2_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *exposure; ++ struct v4l2_ctrl *analogue_gain; ++ struct v4l2_ctrl *digital_gain; ++ struct v4l2_ctrl *hflip; ++ struct v4l2_ctrl *vflip; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *test_pattern; ++ } ctrls; ++}; ++ ++static const struct ov5693_reg ov5693_global_regs[] = { ++ {0x3016, 0xf0}, ++ {0x3017, 0xf0}, ++ {0x3018, 0xf0}, ++ {0x3022, 0x01}, ++ {0x3028, 0x44}, ++ {0x3098, 0x02}, ++ {0x3099, 0x19}, ++ {0x309a, 0x02}, ++ {0x309b, 0x01}, ++ {0x309c, 0x00}, ++ {0x30a0, 0xd2}, ++ {0x30a2, 0x01}, ++ {0x30b2, 0x00}, ++ {0x30b3, 0x7d}, ++ {0x30b4, 0x03}, ++ {0x30b5, 0x04}, ++ {0x30b6, 0x01}, ++ {0x3104, 0x21}, ++ {0x3106, 0x00}, ++ {0x3406, 0x01}, ++ {0x3503, 0x07}, ++ {0x350b, 0x40}, ++ {0x3601, 0x0a}, ++ {0x3602, 0x38}, ++ {0x3612, 0x80}, ++ {0x3620, 0x54}, ++ {0x3621, 0xc7}, ++ {0x3622, 0x0f}, ++ {0x3625, 0x10}, ++ {0x3630, 0x55}, ++ {0x3631, 0xf4}, ++ {0x3632, 0x00}, ++ {0x3633, 0x34}, ++ {0x3634, 0x02}, ++ {0x364d, 0x0d}, ++ {0x364f, 0xdd}, ++ {0x3660, 0x04}, ++ {0x3662, 0x10}, ++ {0x3663, 0xf1}, ++ {0x3665, 0x00}, ++ {0x3666, 0x20}, ++ {0x3667, 0x00}, ++ {0x366a, 0x80}, ++ {0x3680, 0xe0}, ++ {0x3681, 0x00}, ++ {0x3700, 0x42}, ++ {0x3701, 0x14}, ++ {0x3702, 0xa0}, ++ {0x3703, 0xd8}, ++ {0x3704, 0x78}, ++ {0x3705, 0x02}, ++ {0x370a, 0x00}, ++ {0x370b, 0x20}, ++ {0x370c, 0x0c}, ++ {0x370d, 0x11}, ++ {0x370e, 0x00}, ++ {0x370f, 0x40}, ++ {0x3710, 0x00}, ++ {0x371a, 0x1c}, ++ {0x371b, 0x05}, ++ {0x371c, 0x01}, ++ {0x371e, 0xa1}, ++ {0x371f, 0x0c}, ++ {0x3721, 0x00}, ++ {0x3724, 0x10}, ++ {0x3726, 0x00}, ++ {0x372a, 0x01}, ++ {0x3730, 0x10}, ++ {0x3738, 0x22}, ++ {0x3739, 0xe5}, ++ {0x373a, 0x50}, ++ {0x373b, 0x02}, ++ {0x373c, 0x41}, ++ {0x373f, 0x02}, ++ {0x3740, 0x42}, ++ {0x3741, 0x02}, ++ {0x3742, 0x18}, ++ {0x3743, 0x01}, ++ {0x3744, 0x02}, ++ {0x3747, 0x10}, ++ {0x374c, 0x04}, ++ {0x3751, 0xf0}, ++ {0x3752, 0x00}, ++ {0x3753, 0x00}, ++ {0x3754, 0xc0}, ++ {0x3755, 0x00}, ++ {0x3756, 0x1a}, ++ {0x3758, 0x00}, ++ {0x3759, 0x0f}, ++ {0x376b, 0x44}, ++ {0x375c, 0x04}, ++ {0x3774, 0x10}, ++ {0x3776, 0x00}, ++ {0x377f, 0x08}, ++ {0x3780, 0x22}, ++ {0x3781, 0x0c}, ++ {0x3784, 0x2c}, ++ {0x3785, 0x1e}, ++ {0x378f, 0xf5}, ++ {0x3791, 0xb0}, ++ {0x3795, 0x00}, ++ {0x3796, 0x64}, ++ {0x3797, 0x11}, ++ {0x3798, 0x30}, ++ {0x3799, 0x41}, ++ {0x379a, 0x07}, ++ {0x379b, 0xb0}, ++ {0x379c, 0x0c}, ++ {0x3a04, 0x06}, ++ {0x3a05, 0x14}, ++ {0x3e07, 0x20}, ++ {0x4000, 0x08}, ++ {0x4001, 0x04}, ++ {0x4004, 0x08}, ++ {0x4006, 0x20}, ++ {0x4008, 0x24}, ++ {0x4009, 0x10}, ++ {0x4058, 0x00}, ++ {0x4101, 0xb2}, ++ {0x4307, 0x31}, ++ {0x4511, 0x05}, ++ {0x4512, 0x01}, ++ {0x481f, 0x30}, ++ {0x4826, 0x2c}, ++ {0x4d02, 0xfd}, ++ {0x4d03, 0xf5}, ++ {0x4d04, 0x0c}, ++ {0x4d05, 0xcc}, ++ {0x4837, 0x0a}, ++ {0x5003, 0x20}, ++ {0x5013, 0x00}, ++ {0x5842, 0x01}, ++ {0x5843, 0x2b}, ++ {0x5844, 0x01}, ++ {0x5845, 0x92}, ++ {0x5846, 0x01}, ++ {0x5847, 0x8f}, ++ {0x5848, 0x01}, ++ {0x5849, 0x0c}, ++ {0x5e10, 0x0c}, ++ {0x3820, 0x00}, ++ {0x3821, 0x1e}, ++ {0x5041, 0x14} ++}; ++ ++static const struct ov5693_reg_list ov5693_global_setting = { ++ .num_regs = ARRAY_SIZE(ov5693_global_regs), ++ .regs = ov5693_global_regs, ++}; ++ ++static const struct v4l2_rect ov5693_default_crop = { ++ .left = OV5693_ACTIVE_START_LEFT, ++ .top = OV5693_ACTIVE_START_TOP, ++ .width = OV5693_ACTIVE_WIDTH, ++ .height = OV5693_ACTIVE_HEIGHT, ++}; ++ ++static const struct v4l2_mbus_framefmt ov5693_default_fmt = { ++ .width = OV5693_ACTIVE_WIDTH, ++ .height = OV5693_ACTIVE_HEIGHT, ++ .code = MEDIA_BUS_FMT_SBGGR10_1X10, ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ OV5693_LINK_FREQ_400MHZ ++}; ++ ++static const char * const ov5693_supply_names[] = { ++ "avdd", ++ "dovdd", ++}; ++ ++static const char * const ov5693_test_pattern_menu[] = { ++ "Disabled", ++ "Random Data", ++ "Colour Bars", ++ "Colour Bars with Rolling Bar" ++}; ++ ++static const u8 ov5693_test_pattern_bits[] = { ++ 0, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS, ++ OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS | ++ OV5693_TEST_PATTERN_ROLLING, ++}; ++ ++/* I2C I/O Operations */ ++ ++static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value) ++{ ++ struct i2c_client *client = ov5693->client; ++ struct i2c_msg msgs[2]; ++ u8 addr_buf[2]; ++ u8 data_buf; ++ int ret; ++ ++ put_unaligned_be16(addr, addr_buf); ++ ++ /* Write register address */ ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = ARRAY_SIZE(addr_buf); ++ msgs[0].buf = addr_buf; ++ ++ /* Read register value */ ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = 1; ++ msgs[1].buf = &data_buf; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return -EIO; ++ ++ *value = data_buf; ++ ++ return 0; ++} ++ ++static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value, ++ int *error) ++{ ++ unsigned char data[3] = { addr >> 8, addr & 0xff, value }; ++ int ret; ++ ++ if (*error < 0) ++ return; ++ ++ ret = i2c_master_send(ov5693->client, data, sizeof(data)); ++ if (ret < 0) { ++ dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n", ++ addr, ret); ++ *error = ret; ++ } ++} ++ ++static int ov5693_write_reg_array(struct ov5693_device *ov5693, ++ const struct ov5693_reg_list *reglist) ++{ ++ unsigned int i; ++ int ret = 0; ++ ++ for (i = 0; i < reglist->num_regs; i++) ++ ov5693_write_reg(ov5693, reglist->regs[i].reg, ++ reglist->regs[i].val, &ret); ++ ++ return ret; ++} ++ ++static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address, ++ u16 mask, u16 bits) ++{ ++ u8 value = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, address, &value); ++ if (ret) ++ return ret; ++ ++ value &= ~mask; ++ value |= bits; ++ ++ ov5693_write_reg(ov5693, address, value, &ret); ++ ++ return ret; ++} ++ ++/* V4L2 Controls Functions */ ++ ++static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable) ++{ ++ u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN | ++ OV5693_FORMAT1_FLIP_VERT_SENSOR_EN; ++ int ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits, ++ enable ? bits : 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable) ++{ ++ u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN | ++ OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN; ++ int ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits, ++ enable ? bits : 0); ++ if (ret) ++ return ret; ++ ++ return 0; ++} ++ ++static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value) ++{ ++ u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l); ++ if (ret) ++ return ret; ++ ++ /* The lowest 4 bits are unsupported fractional bits */ ++ *value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4; ++ ++ return 0; ++} ++ ++static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, ++ OV5693_EXPOSURE_CTRL_HH(exposure), &ret); ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, ++ OV5693_EXPOSURE_CTRL_H(exposure), &ret); ++ ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, ++ OV5693_EXPOSURE_CTRL_L(exposure), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain) ++{ ++ u8 gain_l = 0, gain_h = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l); ++ if (ret) ++ return ret; ++ ++ /* As with exposure, the lowest 4 bits are fractional bits. */ ++ *gain = ((gain_h << 8) | gain_l) >> 4; ++ ++ return ret; ++} ++ ++static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG, ++ OV5693_MWB_GAIN_H_CTRL(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG, ++ OV5693_MWB_GAIN_L_CTRL(gain), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG, ++ OV5693_GAIN_CTRL_L(gain), &ret); ++ ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG, ++ OV5693_GAIN_CTRL_H(gain), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank) ++{ ++ u16 vts = ov5693->mode.format.height + vblank; ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, ++ OV5693_TIMING_VTS_H(vts), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, ++ OV5693_TIMING_VTS_L(vts), &ret); ++ ++ return ret; ++} ++ ++static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG, ++ ov5693_test_pattern_bits[idx], &ret); ++ ++ return ret; ++} ++ ++static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ov5693_device *ov5693 = ++ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); ++ int ret = 0; ++ ++ /* If VBLANK is altered we need to update exposure to compensate */ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ int exposure_max; ++ ++ exposure_max = ov5693->mode.format.height + ctrl->val - ++ OV5693_INTEGRATION_TIME_MARGIN; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ++ ov5693->ctrls.exposure->minimum, ++ exposure_max, ++ ov5693->ctrls.exposure->step, ++ min(ov5693->ctrls.exposure->val, exposure_max)); ++ } ++ ++ /* Only apply changes to the controls if the device is powered up */ ++ if (!pm_runtime_get_if_in_use(ov5693->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_EXPOSURE: ++ ret = ov5693_exposure_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov5693_analog_gain_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = ov5693_digital_gain_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_HFLIP: ++ ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val); ++ break; ++ case V4L2_CID_VFLIP: ++ ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val); ++ break; ++ case V4L2_CID_VBLANK: ++ ret = ov5693_vts_configure(ov5693, ctrl->val); ++ break; ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov5693_test_pattern_configure(ov5693, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ pm_runtime_put(ov5693->dev); ++ ++ return ret; ++} ++ ++static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ov5693_device *ov5693 = ++ container_of(ctrl->handler, struct ov5693_device, ctrls.handler); ++ ++ switch (ctrl->id) { ++ case V4L2_CID_EXPOSURE_ABSOLUTE: ++ return ov5693_get_exposure(ov5693, &ctrl->val); ++ case V4L2_CID_AUTOGAIN: ++ return ov5693_get_gain(ov5693, &ctrl->val); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct v4l2_ctrl_ops ov5693_ctrl_ops = { ++ .s_ctrl = ov5693_s_ctrl, ++ .g_volatile_ctrl = ov5693_g_volatile_ctrl ++}; ++ ++/* System Control Functions */ ++ ++static int ov5693_mode_configure(struct ov5693_device *ov5693) ++{ ++ const struct ov5693_mode *mode = &ov5693->mode; ++ int ret = 0; ++ ++ /* Crop Start X */ ++ ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG, ++ OV5693_CROP_START_X_H(mode->crop.left), &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG, ++ OV5693_CROP_START_X_L(mode->crop.left), &ret); ++ ++ /* Offset X */ ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG, ++ OV5693_OFFSET_START_X_H(0), &ret); ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG, ++ OV5693_OFFSET_START_X_L(0), &ret); ++ ++ /* Output Size X */ ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG, ++ OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret); ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG, ++ OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret); ++ ++ /* Crop End X */ ++ ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG, ++ OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width), ++ &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG, ++ OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width), ++ &ret); ++ ++ /* Horizontal Total Size */ ++ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG, ++ OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG, ++ OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret); ++ ++ /* Crop Start Y */ ++ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG, ++ OV5693_CROP_START_Y_H(mode->crop.top), &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG, ++ OV5693_CROP_START_Y_L(mode->crop.top), &ret); ++ ++ /* Offset Y */ ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG, ++ OV5693_OFFSET_START_Y_H(0), &ret); ++ ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG, ++ OV5693_OFFSET_START_Y_L(0), &ret); ++ ++ /* Output Size Y */ ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG, ++ OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret); ++ ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG, ++ OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret); ++ ++ /* Crop End Y */ ++ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG, ++ OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height), ++ &ret); ++ ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG, ++ OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height), ++ &ret); ++ ++ /* Vertical Total Size */ ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG, ++ OV5693_TIMING_VTS_H(mode->vts), &ret); ++ ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG, ++ OV5693_TIMING_VTS_L(mode->vts), &ret); ++ ++ /* Subsample X increase */ ++ ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG, ++ ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret); ++ /* Subsample Y increase */ ++ ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG, ++ ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret); ++ ++ /* Binning */ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, ++ OV5693_FORMAT1_VBIN_EN, ++ mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, ++ OV5693_FORMAT2_HBIN_EN, ++ mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0); ++ ++ return ret; ++} ++ ++static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG, ++ standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING, ++ &ret); ++ ++ return ret; ++} ++ ++static int ov5693_sw_reset(struct ov5693_device *ov5693) ++{ ++ int ret = 0; ++ ++ ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret); ++ ++ return ret; ++} ++ ++static int ov5693_sensor_init(struct ov5693_device *ov5693) ++{ ++ int ret = 0; ++ ++ ret = ov5693_sw_reset(ov5693); ++ if (ret) { ++ dev_err(ov5693->dev, "%s software reset error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting); ++ if (ret) { ++ dev_err(ov5693->dev, "%s global settings error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_mode_configure(ov5693); ++ if (ret) { ++ dev_err(ov5693->dev, "%s mode configure error\n", __func__); ++ return ret; ++ } ++ ++ ret = ov5693_sw_standby(ov5693, true); ++ if (ret) ++ dev_err(ov5693->dev, "%s software standby error\n", __func__); ++ ++ return ret; ++} ++ ++static void ov5693_sensor_powerdown(struct ov5693_device *ov5693) ++{ ++ gpiod_set_value_cansleep(ov5693->reset, 1); ++ gpiod_set_value_cansleep(ov5693->powerdown, 1); ++ ++ regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies); ++ ++ clk_disable_unprepare(ov5693->clk); ++} ++ ++static int ov5693_sensor_powerup(struct ov5693_device *ov5693) ++{ ++ int ret; ++ ++ gpiod_set_value_cansleep(ov5693->reset, 1); ++ gpiod_set_value_cansleep(ov5693->powerdown, 1); ++ ++ ret = clk_prepare_enable(ov5693->clk); ++ if (ret) { ++ dev_err(ov5693->dev, "Failed to enable clk\n"); ++ goto fail_power; ++ } ++ ++ ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies); ++ if (ret) { ++ dev_err(ov5693->dev, "Failed to enable regulators\n"); ++ goto fail_power; ++ } ++ ++ gpiod_set_value_cansleep(ov5693->powerdown, 0); ++ gpiod_set_value_cansleep(ov5693->reset, 0); ++ ++ usleep_range(5000, 7500); ++ ++ return 0; ++ ++fail_power: ++ ov5693_sensor_powerdown(ov5693); ++ return ret; ++} ++ ++static int __maybe_unused ov5693_sensor_suspend(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ ov5693_sensor_powerdown(ov5693); ++ ++ return 0; ++} ++ ++static int __maybe_unused ov5693_sensor_resume(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ int ret; ++ ++ mutex_lock(&ov5693->lock); ++ ++ ret = ov5693_sensor_powerup(ov5693); ++ if (ret) ++ goto out_unlock; ++ ++ ret = ov5693_sensor_init(ov5693); ++ if (ret) { ++ dev_err(dev, "ov5693 sensor init failure\n"); ++ goto err_power; ++ } ++ ++ goto out_unlock; ++ ++err_power: ++ ov5693_sensor_powerdown(ov5693); ++out_unlock: ++ mutex_unlock(&ov5693->lock); ++ return ret; ++} ++ ++static int ov5693_detect(struct ov5693_device *ov5693) ++{ ++ u8 id_l = 0, id_h = 0; ++ u16 id = 0; ++ int ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l); ++ if (ret) ++ return ret; ++ ++ id = (id_h << 8) | id_l; ++ ++ if (id != OV5693_CHIP_ID) { ++ dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id); ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++ ++/* V4L2 Framework callbacks */ ++ ++static unsigned int __ov5693_calc_vts(u32 height) ++{ ++ /* ++ * We need to set a sensible default VTS for whatever format height we ++ * happen to be given from set_fmt(). This function just targets ++ * an even multiple of 30fps. ++ */ ++ ++ unsigned int tgt_fps; ++ ++ tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30); ++ ++ return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2); ++} ++ ++static struct v4l2_mbus_framefmt * ++__ov5693_get_pad_format(struct ov5693_device *ov5693, ++ struct v4l2_subdev_pad_config *cfg, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_format(&ov5693->sd, cfg, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &ov5693->mode.format; ++ default: ++ return NULL; ++ } ++} ++ ++static struct v4l2_rect * ++__ov5693_get_pad_crop(struct ov5693_device *ov5693, ++ struct v4l2_subdev_pad_config *cfg, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &ov5693->mode.crop; ++ } ++ ++ return NULL; ++} ++ ++static int ov5693_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ format->format = ov5693->mode.format; ++ ++ return 0; ++} ++ ++static int ov5693_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_format *format) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ const struct v4l2_rect *crop; ++ struct v4l2_mbus_framefmt *fmt; ++ unsigned int hratio, vratio; ++ unsigned int width, height; ++ unsigned int hblank; ++ int exposure_max; ++ int ret = 0; ++ ++ crop = __ov5693_get_pad_crop(ov5693, cfg, format->pad, format->which); ++ ++ /* ++ * Align to two to simplify the binning calculations below, and clamp ++ * the requested format at the crop rectangle ++ */ ++ width = clamp_t(unsigned int, ALIGN(format->format.width, 2), ++ OV5693_MIN_CROP_WIDTH, crop->width); ++ height = clamp_t(unsigned int, ALIGN(format->format.height, 2), ++ OV5693_MIN_CROP_HEIGHT, crop->height); ++ ++ /* ++ * We can only support setting either the dimensions of the crop rect ++ * or those dimensions binned (separately) by a factor of two. ++ */ ++ hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2); ++ vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2); ++ ++ fmt = __ov5693_get_pad_format(ov5693, cfg, format->pad, format->which); ++ ++ fmt->width = crop->width / hratio; ++ fmt->height = crop->height / vratio; ++ fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ ++ format->format = *fmt; ++ ++ if (format->which == V4L2_SUBDEV_FORMAT_TRY) ++ return ret; ++ ++ mutex_lock(&ov5693->lock); ++ ++ ov5693->mode.binning_x = hratio > 1 ? true : false; ++ ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1; ++ ov5693->mode.binning_y = vratio > 1 ? true : false; ++ ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1; ++ ++ ov5693->mode.vts = __ov5693_calc_vts(fmt->height); ++ ++ __v4l2_ctrl_modify_range(ov5693->ctrls.vblank, ++ OV5693_TIMING_MIN_VTS, ++ OV5693_TIMING_MAX_VTS - fmt->height, ++ 1, ov5693->mode.vts - fmt->height); ++ __v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank, ++ ov5693->mode.vts - fmt->height); ++ ++ hblank = OV5693_FIXED_PPL - fmt->width; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1, ++ hblank); ++ ++ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; ++ __v4l2_ctrl_modify_range(ov5693->ctrls.exposure, ++ ov5693->ctrls.exposure->minimum, exposure_max, ++ ov5693->ctrls.exposure->step, ++ min(ov5693->ctrls.exposure->val, exposure_max)); ++ ++ mutex_unlock(&ov5693->lock); ++ return ret; ++} ++ ++static int ov5693_get_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ mutex_lock(&ov5693->lock); ++ sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad, ++ sel->which); ++ mutex_unlock(&ov5693->lock); ++ break; ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = OV5693_NATIVE_WIDTH; ++ sel->r.height = OV5693_NATIVE_HEIGHT; ++ break; ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ sel->r.top = OV5693_ACTIVE_START_TOP; ++ sel->r.left = OV5693_ACTIVE_START_LEFT; ++ sel->r.width = OV5693_ACTIVE_WIDTH; ++ sel->r.height = OV5693_ACTIVE_HEIGHT; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ov5693_set_selection(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ struct v4l2_mbus_framefmt *format; ++ struct v4l2_rect *__crop; ++ struct v4l2_rect rect; ++ ++ if (sel->target != V4L2_SEL_TGT_CROP) ++ return -EINVAL; ++ ++ /* ++ * Clamp the boundaries of the crop rectangle to the size of the sensor ++ * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't ++ * disrupted. ++ */ ++ rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT, ++ OV5693_NATIVE_WIDTH); ++ rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP, ++ OV5693_NATIVE_HEIGHT); ++ rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2), ++ OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH); ++ rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2), ++ OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT); ++ ++ /* Make sure the crop rectangle isn't outside the bounds of the array */ ++ rect.width = min_t(unsigned int, rect.width, ++ OV5693_NATIVE_WIDTH - rect.left); ++ rect.height = min_t(unsigned int, rect.height, ++ OV5693_NATIVE_HEIGHT - rect.top); ++ ++ __crop = __ov5693_get_pad_crop(ov5693, cfg, sel->pad, sel->which); ++ ++ if (rect.width != __crop->width || rect.height != __crop->height) { ++ /* ++ * Reset the output image size if the crop rectangle size has ++ * been modified. ++ */ ++ format = __ov5693_get_pad_format(ov5693, cfg, sel->pad, sel->which); ++ format->width = rect.width; ++ format->height = rect.height; ++ } ++ ++ *__crop = rect; ++ sel->r = rect; ++ ++ return 0; ++} ++ ++static int ov5693_s_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ int ret; ++ ++ if (enable) { ++ ret = pm_runtime_get_sync(ov5693->dev); ++ if (ret < 0) ++ goto err_power_down; ++ ++ ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler); ++ if (ret) ++ goto err_power_down; ++ } ++ ++ mutex_lock(&ov5693->lock); ++ ret = ov5693_sw_standby(ov5693, !enable); ++ mutex_unlock(&ov5693->lock); ++ ++ if (ret) ++ goto err_power_down; ++ ov5693->streaming = !!enable; ++ ++ if (!enable) ++ pm_runtime_put(ov5693->dev); ++ ++ return 0; ++err_power_down: ++ pm_runtime_put_noidle(ov5693->dev); ++ return ret; ++} ++ ++static int ov5693_g_frame_interval(struct v4l2_subdev *sd, ++ struct v4l2_subdev_frame_interval *interval) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height + ++ ov5693->ctrls.vblank->val); ++ unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize); ++ ++ interval->interval.numerator = 1; ++ interval->interval.denominator = fps; ++ ++ return 0; ++} ++ ++static int ov5693_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ /* Only a single mbus format is supported */ ++ if (code->index > 0) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SBGGR10_1X10; ++ return 0; ++} ++ ++static int ov5693_enum_frame_size(struct v4l2_subdev *sd, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ struct v4l2_rect *__crop; ++ ++ if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10) ++ return -EINVAL; ++ ++ __crop = __ov5693_get_pad_crop(ov5693, cfg, fse->pad, fse->which); ++ if (!__crop) ++ return -EINVAL; ++ ++ fse->min_width = __crop->width / (fse->index + 1); ++ fse->min_height = __crop->height / (fse->index + 1); ++ fse->max_width = fse->min_width; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops ov5693_video_ops = { ++ .s_stream = ov5693_s_stream, ++ .g_frame_interval = ov5693_g_frame_interval, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov5693_pad_ops = { ++ .enum_mbus_code = ov5693_enum_mbus_code, ++ .enum_frame_size = ov5693_enum_frame_size, ++ .get_fmt = ov5693_get_fmt, ++ .set_fmt = ov5693_set_fmt, ++ .get_selection = ov5693_get_selection, ++ .set_selection = ov5693_set_selection, ++}; ++ ++static const struct v4l2_subdev_ops ov5693_ops = { ++ .video = &ov5693_video_ops, ++ .pad = &ov5693_pad_ops, ++}; ++ ++/* Sensor and Driver Configuration Functions */ ++ ++static int ov5693_init_controls(struct ov5693_device *ov5693) ++{ ++ const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops; ++ struct v4l2_fwnode_device_properties props; ++ int vblank_max, vblank_def; ++ int exposure_max; ++ int hblank; ++ int ret; ++ ++ ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12); ++ if (ret) ++ return ret; ++ ++ /* link freq */ ++ ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler, ++ NULL, V4L2_CID_LINK_FREQ, ++ 0, 0, link_freq_menu_items); ++ if (ov5693->ctrls.link_freq) ++ ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ /* pixel rate */ ++ ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL, ++ V4L2_CID_PIXEL_RATE, 0, ++ OV5693_PIXEL_RATE, 1, ++ OV5693_PIXEL_RATE); ++ ++ /* Exposure */ ++ exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN; ++ ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_EXPOSURE, ++ OV5693_EXPOSURE_MIN, ++ exposure_max, ++ OV5693_EXPOSURE_STEP, ++ exposure_max); ++ ++ /* Gain */ ++ ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ++ ops, V4L2_CID_ANALOGUE_GAIN, ++ OV5693_GAIN_MIN, ++ OV5693_GAIN_MAX, ++ OV5693_GAIN_STEP, ++ OV5693_GAIN_DEF); ++ ++ ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_DIGITAL_GAIN, ++ OV5693_DIGITAL_GAIN_MIN, ++ OV5693_DIGITAL_GAIN_MAX, ++ OV5693_DIGITAL_GAIN_STEP, ++ OV5693_DIGITAL_GAIN_DEF); ++ ++ /* Flip */ ++ ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_HFLIP, 0, 1, 1, 0); ++ ++ ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_VFLIP, 0, 1, 1, 0); ++ ++ hblank = OV5693_FIXED_PPL - ov5693->mode.format.width; ++ ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_HBLANK, hblank, ++ hblank, 1, hblank); ++ ++ if (ov5693->ctrls.hblank) ++ ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height; ++ vblank_def = ov5693->mode.vts - ov5693->mode.format.height; ++ ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops, ++ V4L2_CID_VBLANK, ++ OV5693_TIMING_MIN_VTS, ++ vblank_max, 1, vblank_def); ++ ++ ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items( ++ &ov5693->ctrls.handler, ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(ov5693_test_pattern_menu) - 1, ++ 0, 0, ov5693_test_pattern_menu); ++ ++ if (ov5693->ctrls.handler.error) { ++ dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n"); ++ ret = ov5693->ctrls.handler.error; ++ goto err_free_handler; ++ } ++ ++ /* set properties from fwnode (e.g. rotation, orientation) */ ++ ret = v4l2_fwnode_device_parse(ov5693->dev, &props); ++ if (ret) ++ goto err_free_handler; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops, ++ &props); ++ if (ret) ++ goto err_free_handler; ++ ++ /* Use same lock for controls as for everything else. */ ++ ov5693->ctrls.handler.lock = &ov5693->lock; ++ ov5693->sd.ctrl_handler = &ov5693->ctrls.handler; ++ ++ return 0; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ return ret; ++} ++ ++static int ov5693_configure_gpios(struct ov5693_device *ov5693) ++{ ++ ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov5693->reset)) { ++ dev_err(ov5693->dev, "Error fetching reset GPIO\n"); ++ return PTR_ERR(ov5693->reset); ++ } ++ ++ ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(ov5693->powerdown)) { ++ dev_err(ov5693->dev, "Error fetching powerdown GPIO\n"); ++ return PTR_ERR(ov5693->powerdown); ++ } ++ ++ return 0; ++} ++ ++static int ov5693_get_regulators(struct ov5693_device *ov5693) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < OV5693_NUM_SUPPLIES; i++) ++ ov5693->supplies[i].supply = ov5693_supply_names[i]; ++ ++ return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES, ++ ov5693->supplies); ++} ++ ++static int ov5693_probe(struct i2c_client *client) ++{ ++ struct fwnode_handle *fwnode = dev_fwnode(&client->dev); ++ struct fwnode_handle *endpoint; ++ struct ov5693_device *ov5693; ++ u32 clk_rate; ++ int ret = 0; ++ ++ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); ++ if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary)) ++ endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); ++ if (!endpoint) ++ return -EPROBE_DEFER; ++ ++ ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL); ++ if (!ov5693) ++ return -ENOMEM; ++ ++ ov5693->client = client; ++ ov5693->dev = &client->dev; ++ ++ mutex_init(&ov5693->lock); ++ ++ v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops); ++ ++ ov5693->clk = devm_clk_get(&client->dev, "xvclk"); ++ if (IS_ERR(ov5693->clk)) { ++ dev_err(&client->dev, "Error getting clock\n"); ++ return PTR_ERR(ov5693->clk); ++ } ++ ++ clk_rate = clk_get_rate(ov5693->clk); ++ if (clk_rate != OV5693_XVCLK_FREQ) { ++ dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n", ++ clk_rate, OV5693_XVCLK_FREQ); ++ return -EINVAL; ++ } ++ ++ ret = ov5693_configure_gpios(ov5693); ++ if (ret) ++ return ret; ++ ++ ret = ov5693_get_regulators(ov5693); ++ if (ret) { ++ dev_err(&client->dev, "Error fetching regulators\n"); ++ return ret; ++ } ++ ++ ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ ov5693->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ++ ov5693->mode.crop = ov5693_default_crop; ++ ov5693->mode.format = ov5693_default_fmt; ++ ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height); ++ ++ ret = ov5693_init_controls(ov5693); ++ if (ret) ++ return ret; ++ ++ ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad); ++ if (ret) ++ goto err_ctrl_handler_free; ++ ++ /* ++ * We need the driver to work in the event that pm runtime is disable in ++ * the kernel, so power up and verify the chip now. In the event that ++ * runtime pm is disabled this will leave the chip on, so that streaming ++ * will work. ++ */ ++ ++ ret = ov5693_sensor_powerup(ov5693); ++ if (ret) ++ goto err_media_entity_cleanup; ++ ++ ret = ov5693_detect(ov5693); ++ if (ret) ++ goto err_powerdown; ++ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_get_noresume(&client->dev); ++ pm_runtime_enable(&client->dev); ++ ++ ret = v4l2_async_register_subdev_sensor(&ov5693->sd); ++ if (ret) { ++ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ++ ret); ++ goto err_pm_runtime; ++ } ++ ++ pm_runtime_set_autosuspend_delay(&client->dev, 1000); ++ pm_runtime_use_autosuspend(&client->dev); ++ pm_runtime_put_autosuspend(&client->dev); ++ ++ return ret; ++ ++err_pm_runtime: ++ pm_runtime_disable(&client->dev); ++ pm_runtime_put_noidle(&client->dev); ++err_powerdown: ++ ov5693_sensor_powerdown(ov5693); ++err_media_entity_cleanup: ++ media_entity_cleanup(&ov5693->sd.entity); ++err_ctrl_handler_free: ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ ++ return ret; ++} ++ ++static int ov5693_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov5693_device *ov5693 = to_ov5693_sensor(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ media_entity_cleanup(&ov5693->sd.entity); ++ v4l2_ctrl_handler_free(&ov5693->ctrls.handler); ++ mutex_destroy(&ov5693->lock); ++ ++ /* ++ * Disable runtime PM. In case runtime PM is disabled in the kernel, ++ * make sure to turn power off manually. ++ */ ++ pm_runtime_disable(&client->dev); ++ if (!pm_runtime_status_suspended(&client->dev)) ++ ov5693_sensor_powerdown(ov5693); ++ pm_runtime_set_suspended(&client->dev); ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ov5693_pm_ops = { ++ SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL) ++}; ++ ++static const struct acpi_device_id ov5693_acpi_match[] = { ++ {"INT33BE"}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match); ++ ++static struct i2c_driver ov5693_driver = { ++ .driver = { ++ .name = "ov5693", ++ .acpi_match_table = ov5693_acpi_match, ++ .pm = &ov5693_pm_ops, ++ }, ++ .probe_new = ov5693_probe, ++ .remove = ov5693_remove, ++}; ++module_i2c_driver(ov5693_driver); ++ ++MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors"); ++MODULE_LICENSE("GPL"); +-- +2.32.0 + +From ecbcc0bc18332f6e3f4dc53bf1877eb166b89b06 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= +Date: Fri, 22 Jan 2021 20:58:13 +0100 +Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The sensor orientation is read from the _PLC ACPI buffer and converted +to a v4l2 format. + +See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf +page 351 for a definition of the Panel property. + +The sensor rotation is read from the SSDB ACPI buffer and converted into +degrees. + +Signed-off-by: Fabian Wüthrich +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++-- + drivers/media/pci/intel/ipu3/cio2-bridge.h | 3 ++ + 2 files changed, 44 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c +index 4657e99df033..15cf65a141b6 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { + static const struct cio2_property_names prop_names = { + .clock_frequency = "clock-frequency", + .rotation = "rotation", ++ .orientation = "orientation", + .bus_type = "bus-type", + .data_lanes = "data-lanes", + .remote_endpoint = "remote-endpoint", +@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + return ret; + } + ++static u32 cio2_bridge_parse_rotation(u8 rotation) ++{ ++ if (rotation == 1) ++ return 180; ++ return 0; ++} ++ ++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) ++{ ++ switch (panel) { ++ case 4: ++ return V4L2_FWNODE_ORIENTATION_FRONT; ++ case 5: ++ return V4L2_FWNODE_ORIENTATION_BACK; ++ default: ++ return V4L2_FWNODE_ORIENTATION_EXTERNAL; ++ } ++} ++ + static void cio2_bridge_create_fwnode_properties( + struct cio2_sensor *sensor, + struct cio2_bridge *bridge, + const struct cio2_sensor_config *cfg) + { ++ u32 rotation; ++ enum v4l2_fwnode_orientation orientation; ++ ++ rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); ++ orientation = cio2_bridge_parse_orientation(sensor->pld->panel); ++ + sensor->prop_names = prop_names; + + sensor->local_ref[0] = SOFTWARE_NODE_REFERENCE(&sensor->swnodes[SWNODE_CIO2_ENDPOINT]); +@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties( + sensor->dev_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.clock_frequency, + sensor->ssdb.mclkspeed); +- sensor->dev_properties[1] = PROPERTY_ENTRY_U8( ++ sensor->dev_properties[1] = PROPERTY_ENTRY_U32( + sensor->prop_names.rotation, +- sensor->ssdb.degree); ++ rotation); ++ sensor->dev_properties[2] = PROPERTY_ENTRY_U32( ++ sensor->prop_names.orientation, ++ orientation); + + sensor->ep_properties[0] = PROPERTY_ENTRY_U32( + sensor->prop_names.bus_type, +@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge) + for (i = 0; i < bridge->n_sensors; i++) { + sensor = &bridge->sensors[i]; + software_node_unregister_nodes(sensor->swnodes); ++ ACPI_FREE(sensor->pld); + acpi_dev_put(sensor->adev); + } + } +@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + struct fwnode_handle *fwnode; + struct cio2_sensor *sensor; + struct acpi_device *adev; ++ acpi_status status; + int ret; + + for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) { +@@ -194,11 +225,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + if (ret) + goto err_put_adev; + ++ status = acpi_get_physical_device_location(adev->handle, &sensor->pld); ++ if (ACPI_FAILURE(status)) ++ goto err_put_adev; ++ + if (sensor->ssdb.lanes > CIO2_MAX_LANES) { + dev_err(&adev->dev, + "Number of lanes in SSDB is invalid\n"); + ret = -EINVAL; +- goto err_put_adev; ++ goto err_free_pld; + } + + cio2_bridge_create_fwnode_properties(sensor, bridge, cfg); +@@ -206,7 +241,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + + ret = software_node_register_nodes(sensor->swnodes); + if (ret) +- goto err_put_adev; ++ goto err_free_pld; + + fwnode = software_node_fwnode(&sensor->swnodes[ + SWNODE_SENSOR_HID]); +@@ -227,6 +262,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg, + + err_free_swnodes: + software_node_unregister_nodes(sensor->swnodes); ++err_free_pld: ++ ACPI_FREE(sensor->pld); + err_put_adev: + acpi_dev_put(sensor->adev); + return ret; +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h +index dd0ffcafa489..924d99d20328 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h +@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb { + struct cio2_property_names { + char clock_frequency[16]; + char rotation[9]; ++ char orientation[12]; + char bus_type[9]; + char data_lanes[11]; + char remote_endpoint[16]; +@@ -106,6 +107,8 @@ struct cio2_sensor { + struct cio2_node_names node_names; + + struct cio2_sensor_ssdb ssdb; ++ struct acpi_pld_info *pld; ++ + struct cio2_property_names prop_names; + struct property_entry ep_properties[5]; + struct property_entry dev_properties[3]; +-- +2.32.0 + +From 58d0fadc1f6db32fd55d0764482829cd3e905454 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= +Date: Sun, 24 Jan 2021 11:07:42 +0100 +Subject: [PATCH] cio2-bridge: Use macros and add warnings +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit +a warning if we see an unknown value. + +Signed-off-by: Fabian Wüthrich +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------ + drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++ + 2 files changed, 37 insertions(+), 9 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c +index 15cf65a141b6..f757b64eb483 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id, + return ret; + } + +-static u32 cio2_bridge_parse_rotation(u8 rotation) ++static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor) + { +- if (rotation == 1) ++ switch (sensor->ssdb.degree) { ++ case CIO2_SENSOR_ROTATION_NORMAL: ++ return 0; ++ case CIO2_SENSOR_ROTATION_INVERTED: + return 180; +- return 0; ++ default: ++ dev_warn(&sensor->adev->dev, ++ "Unknown rotation %d. Assume 0 degree rotation\n", ++ sensor->ssdb.degree); ++ return 0; ++ } + } + +-static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel) ++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor) + { +- switch (panel) { +- case 4: ++ switch (sensor->pld->panel) { ++ case CIO2_PLD_PANEL_FRONT: + return V4L2_FWNODE_ORIENTATION_FRONT; +- case 5: ++ case CIO2_PLD_PANEL_BACK: + return V4L2_FWNODE_ORIENTATION_BACK; ++ case CIO2_PLD_PANEL_TOP: ++ case CIO2_PLD_PANEL_LEFT: ++ case CIO2_PLD_PANEL_RIGHT: ++ case CIO2_PLD_PANEL_UNKNOWN: ++ return V4L2_FWNODE_ORIENTATION_EXTERNAL; + default: ++ dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n", ++ sensor->pld->panel); + return V4L2_FWNODE_ORIENTATION_EXTERNAL; + } + } +@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties( + u32 rotation; + enum v4l2_fwnode_orientation orientation; + +- rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree); +- orientation = cio2_bridge_parse_orientation(sensor->pld->panel); ++ rotation = cio2_bridge_parse_rotation(sensor); ++ orientation = cio2_bridge_parse_orientation(sensor); + + sensor->prop_names = prop_names; + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h +index 924d99d20328..e1e388cc9f45 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h +@@ -12,6 +12,19 @@ + #define CIO2_MAX_LANES 4 + #define MAX_NUM_LINK_FREQS 3 + ++/* Values are estimated guesses as we don't have a spec */ ++#define CIO2_SENSOR_ROTATION_NORMAL 0 ++#define CIO2_SENSOR_ROTATION_INVERTED 1 ++ ++/* Panel position defined in _PLD section of ACPI Specification 6.3 */ ++#define CIO2_PLD_PANEL_TOP 0 ++#define CIO2_PLD_PANEL_BOTTOM 1 ++#define CIO2_PLD_PANEL_LEFT 2 ++#define CIO2_PLD_PANEL_RIGHT 3 ++#define CIO2_PLD_PANEL_FRONT 4 ++#define CIO2_PLD_PANEL_BACK 5 ++#define CIO2_PLD_PANEL_UNKNOWN 6 ++ + #define CIO2_SENSOR_CONFIG(_HID, _NR, ...) \ + (const struct cio2_sensor_config) { \ + .hid = _HID, \ +-- +2.32.0 + +From 6090df42da546a24055e894184dc25f679eefb89 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= +Date: Thu, 6 May 2021 07:52:44 +0200 +Subject: [PATCH] cio2-bridge: Use correct dev_properties size + +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h +index e1e388cc9f45..deaf5804f70d 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h +@@ -124,7 +124,7 @@ struct cio2_sensor { + + struct cio2_property_names prop_names; + struct property_entry ep_properties[5]; +- struct property_entry dev_properties[3]; ++ struct property_entry dev_properties[4]; + struct property_entry cio2_properties[3]; + struct software_node_ref_args local_ref[1]; + struct software_node_ref_args remote_ref[1]; +-- +2.32.0 + +From 23c9920bb64c8beea4fe2caf175656217cde1a91 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 20 May 2021 23:31:04 +0100 +Subject: [PATCH] media: i2c: Fix vertical flip in ov5693 + +The pinkness experienced by users with rotated sensors in their laptops +was due to an incorrect setting for the vertical flip function; the +datasheet for the sensor gives the settings as bits 1&2 in one place and +bits 1&6 in another. + +Switch to flipping bit 6 instead of bit 2 for 0x3820 in the vertical +flip function to fix the pink hue. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov5693.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c +index 276f625d4d23..1653fb49f6e0 100644 +--- a/drivers/media/i2c/ov5693.c ++++ b/drivers/media/i2c/ov5693.c +@@ -133,7 +133,7 @@ + #define OV5693_SUB_INC_Y_REG 0x3815 + + #define OV5693_FORMAT1_REG 0x3820 +-#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(2) ++#define OV5693_FORMAT1_FLIP_VERT_ISP_EN BIT(6) + #define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN BIT(1) + #define OV5693_FORMAT1_VBIN_EN BIT(0) + #define OV5693_FORMAT2_REG 0x3821 +-- +2.32.0 + +From 8a64066aafb879c46d7410ca1556cc8f7feec086 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Fri, 9 Jul 2021 16:39:18 +0100 +Subject: [PATCH] media: i2c: Add ACPI support to ov8865 + +The ov8865 sensor is sometimes found on x86 platforms enumerated via ACPI. +Add an ACPI match table to the driver so that it's probed on those +platforms. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 9ecf180635ee..a28adf45b1b1 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -5,6 +5,7 @@ + * Author: Paul Kocialkowski + */ + ++#include + #include + #include + #include +@@ -2948,6 +2949,12 @@ static const struct dev_pm_ops ov8865_pm_ops = { + SET_RUNTIME_PM_OPS(ov8865_suspend, ov8865_resume, NULL) + }; + ++static const struct acpi_device_id ov8865_acpi_match[] = { ++ {"INT347A"}, ++ {}, ++}; ++MODULE_DEVICE_TABLE(acpi, ov8865_acpi_match); ++ + static const struct of_device_id ov8865_of_match[] = { + { .compatible = "ovti,ov8865" }, + { } +@@ -2958,6 +2965,7 @@ static struct i2c_driver ov8865_driver = { + .driver = { + .name = "ov8865", + .of_match_table = ov8865_of_match, ++ .acpi_match_table = ov8865_acpi_match, + .pm = &ov8865_pm_ops, + }, + .probe_new = ov8865_probe, +-- +2.32.0 + +From 7ec1396b8cd04dfb7170bc78e7e222d526e4c3a5 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 21:20:17 +0100 +Subject: [PATCH] media: i2c: Fix incorrect value in comment + +The PLL configuration defined here sets 72MHz (which is correct), not +80MHz. Correct the comment. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index a28adf45b1b1..7d716b0d47c1 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -713,7 +713,7 @@ static const struct ov8865_pll2_config ov8865_pll2_config_native = { + /* + * EXTCLK = 24 MHz + * DAC_CLK = 360 MHz +- * SCLK = 80 MHz ++ * SCLK = 72 MHz + */ + + static const struct ov8865_pll2_config ov8865_pll2_config_binning = { +-- +2.32.0 + +From 1bb9de0400d99a53300f126de1bd2fa7208d63e9 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 22:21:52 +0100 +Subject: [PATCH] media: i2c: Check fwnode->secondary for endpoint + +The ov8865 driver is one of those that can be connected to a CIO2 +device by the cio2-bridge code. This means that the absence of an +endpoint for this device is not necessarily fatal, as one might be +built by the cio2-bridge when it probes. Check fwnode->secondary for +an endpoint, and defer probing if one isn't found rather than fail. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 7d716b0d47c1..5fb290a6fc6a 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2781,6 +2781,7 @@ static int ov8865_resume(struct device *dev) + static int ov8865_probe(struct i2c_client *client) + { + struct device *dev = &client->dev; ++ struct fwnode_handle *fwnode = dev_fwnode(dev); + struct fwnode_handle *handle; + struct ov8865_sensor *sensor; + struct v4l2_subdev *subdev; +@@ -2797,11 +2798,11 @@ static int ov8865_probe(struct i2c_client *client) + + /* Graph Endpoint */ + +- handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); +- if (!handle) { +- dev_err(dev, "unable to find endpoint node\n"); +- return -EINVAL; +- } ++ handle = fwnode_graph_get_next_endpoint(fwnode, NULL); ++ if (!handle && !IS_ERR_OR_NULL(fwnode->secondary)) ++ handle = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL); ++ if (!handle) ++ return -EPROBE_DEFER; + + sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; + +-- +2.32.0 + +From 149a83ac30db3dbc6c3aea9b8c18d47a0da8f9c3 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 22:00:25 +0100 +Subject: [PATCH] media: i2c: Support 19.2MHz input clock in ov8865 + +The ov8865 driver as written expects a 24MHz input clock, but the sensor +is sometimes found on x86 platforms with a 19.2MHz input clock supplied. +Add a set of PLL configurations to the driver to support that rate too. +As ACPI doesn't auto-configure the clock rate, check for a clock-frequency +during probe and set that rate if one is found. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 157 +++++++++++++++++++++++++++---------- + 1 file changed, 114 insertions(+), 43 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 5fb290a6fc6a..cae7dc9da49d 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -21,10 +21,6 @@ + #include + #include + +-/* Clock rate */ +- +-#define OV8865_EXTCLK_RATE 24000000 +- + /* Register definitions */ + + /* System */ +@@ -665,6 +661,9 @@ struct ov8865_sensor { + struct regulator *avdd; + struct regulator *dvdd; + struct regulator *dovdd; ++ ++ unsigned long extclk_rate; ++ unsigned int extclk_rate_idx; + struct clk *extclk; + + struct v4l2_fwnode_endpoint endpoint; +@@ -680,49 +679,83 @@ struct ov8865_sensor { + /* Static definitions */ + + /* +- * EXTCLK = 24 MHz + * PHY_SCLK = 720 MHz + * MIPI_PCLK = 90 MHz + */ +-static const struct ov8865_pll1_config ov8865_pll1_config_native = { +- .pll_pre_div_half = 1, +- .pll_pre_div = 0, +- .pll_mul = 30, +- .m_div = 1, +- .mipi_div = 3, +- .pclk_div = 1, +- .sys_pre_div = 1, +- .sys_div = 2, ++ ++static const struct ov8865_pll1_config ov8865_pll1_configs_native[] = { ++ { /* 19.2 MHz input clock */ ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 2, ++ .pll_mul = 75, ++ .m_div = 1, ++ .mipi_div = 3, ++ .pclk_div = 1, ++ .sys_pre_div = 1, ++ .sys_div = 2, ++ }, ++ { /* 24MHz input clock */ ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 0, ++ .pll_mul = 30, ++ .m_div = 1, ++ .mipi_div = 3, ++ .pclk_div = 1, ++ .sys_pre_div = 1, ++ .sys_div = 2, ++ }, + }; + + /* +- * EXTCLK = 24 MHz + * DAC_CLK = 360 MHz + * SCLK = 144 MHz + */ + +-static const struct ov8865_pll2_config ov8865_pll2_config_native = { +- .pll_pre_div_half = 1, +- .pll_pre_div = 0, +- .pll_mul = 30, +- .dac_div = 2, +- .sys_pre_div = 5, +- .sys_div = 0, ++static const struct ov8865_pll2_config ov8865_pll2_configs_native[] = { ++ /* 19.2MHz input clock */ ++ { ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 5, ++ .pll_mul = 75, ++ .dac_div = 1, ++ .sys_pre_div = 1, ++ .sys_div = 3, ++ }, ++ /* 24MHz input clock */ ++ { ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 0, ++ .pll_mul = 30, ++ .dac_div = 2, ++ .sys_pre_div = 5, ++ .sys_div = 0, ++ } + }; + + /* +- * EXTCLK = 24 MHz + * DAC_CLK = 360 MHz + * SCLK = 72 MHz + */ + +-static const struct ov8865_pll2_config ov8865_pll2_config_binning = { ++static const struct ov8865_pll2_config ov8865_pll2_configs_binning[] = { ++ /* 19.2MHz input clock */ ++ { ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 2, ++ .pll_mul = 75, ++ .dac_div = 2, ++ .sys_pre_div = 10, ++ .sys_div = 0, ++ }, ++ /* 24MHz input clock */ ++ { + .pll_pre_div_half = 1, + .pll_pre_div = 0, + .pll_mul = 30, + .dac_div = 2, + .sys_pre_div = 10, + .sys_div = 0, ++ } + }; + + static const struct ov8865_sclk_config ov8865_sclk_config_native = { +@@ -934,8 +967,8 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_native, ++ .pll1_config = ov8865_pll1_configs_native, ++ .pll2_config = ov8865_pll2_configs_native, + .sclk_config = &ov8865_sclk_config_native, + + /* Registers */ +@@ -990,8 +1023,8 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_native, ++ .pll1_config = ov8865_pll1_configs_native, ++ .pll2_config = ov8865_pll2_configs_native, + .sclk_config = &ov8865_sclk_config_native, + + /* Registers */ +@@ -1050,8 +1083,8 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_binning, ++ .pll1_config = ov8865_pll1_configs_native, ++ .pll2_config = ov8865_pll2_configs_binning, + .sclk_config = &ov8865_sclk_config_native, + + /* Registers */ +@@ -1116,8 +1149,8 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 90 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_binning, ++ .pll1_config = ov8865_pll1_configs_native, ++ .pll2_config = ov8865_pll2_configs_binning, + .sclk_config = &ov8865_sclk_config_native, + + /* Registers */ +@@ -1266,6 +1299,13 @@ static const struct ov8865_register_value ov8865_init_sequence[] = { + { 0x4503, 0x10 }, + }; + ++/* Clock rate */ ++ ++static const unsigned long supported_extclk_rates[] = { ++ 19200000, ++ 24000000, ++}; ++ + static const s64 ov8865_link_freq_menu[] = { + 360000000, + }; +@@ -1513,12 +1553,11 @@ static int ov8865_isp_configure(struct ov8865_sensor *sensor) + static unsigned long ov8865_mode_pll1_rate(struct ov8865_sensor *sensor, + const struct ov8865_mode *mode) + { +- const struct ov8865_pll1_config *config = mode->pll1_config; +- unsigned long extclk_rate; ++ const struct ov8865_pll1_config *config; + unsigned long pll1_rate; + +- extclk_rate = clk_get_rate(sensor->extclk); +- pll1_rate = extclk_rate * config->pll_mul / config->pll_pre_div_half; ++ config = &mode->pll1_config[sensor->extclk_rate_idx]; ++ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; + + switch (config->pll_pre_div) { + case 0: +@@ -1552,10 +1591,12 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, + const struct ov8865_mode *mode, + u32 mbus_code) + { +- const struct ov8865_pll1_config *config = mode->pll1_config; ++ const struct ov8865_pll1_config *config; + u8 value; + int ret; + ++ config = &mode->pll1_config[sensor->extclk_rate_idx]; ++ + switch (mbus_code) { + case MEDIA_BUS_FMT_SBGGR10_1X10: + value = OV8865_MIPI_BIT_SEL(10); +@@ -1622,9 +1663,11 @@ static int ov8865_mode_pll1_configure(struct ov8865_sensor *sensor, + static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, + const struct ov8865_mode *mode) + { +- const struct ov8865_pll2_config *config = mode->pll2_config; ++ const struct ov8865_pll2_config *config; + int ret; + ++ config = &mode->pll2_config[sensor->extclk_rate_idx]; ++ + ret = ov8865_write(sensor, OV8865_PLL_CTRL12_REG, + OV8865_PLL_CTRL12_PRE_DIV_HALF(config->pll_pre_div_half) | + OV8865_PLL_CTRL12_DAC_DIV(config->dac_div)); +@@ -2053,9 +2096,11 @@ static int ov8865_mode_configure(struct ov8865_sensor *sensor, + static unsigned long ov8865_mode_mipi_clk_rate(struct ov8865_sensor *sensor, + const struct ov8865_mode *mode) + { +- const struct ov8865_pll1_config *config = mode->pll1_config; ++ const struct ov8865_pll1_config *config; + unsigned long pll1_rate; + ++ config = &mode->pll1_config[sensor->extclk_rate_idx]; ++ + pll1_rate = ov8865_mode_pll1_rate(sensor, mode); + + return pll1_rate / config->m_div / 2; +@@ -2786,7 +2831,8 @@ static int ov8865_probe(struct i2c_client *client) + struct ov8865_sensor *sensor; + struct v4l2_subdev *subdev; + struct media_pad *pad; +- unsigned long rate; ++ unsigned int rate; ++ unsigned int i; + int ret; + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); +@@ -2863,13 +2909,38 @@ static int ov8865_probe(struct i2c_client *client) + goto error_endpoint; + } + +- rate = clk_get_rate(sensor->extclk); +- if (rate != OV8865_EXTCLK_RATE) { +- dev_err(dev, "clock rate %lu Hz is unsupported\n", rate); ++ /* ++ * We could have either a 24MHz or 19.2MHz clock rate. Check for a ++ * clock-frequency property and if found, set that rate. This should ++ * cover ACPI case. If the system uses devicetree then the configured ++ * rate should already be set, so we'll have to check it. ++ */ ++ ++ ret = fwnode_property_read_u32(fwnode, "clock-frequency", &rate); ++ if (!ret) { ++ ret = clk_set_rate(sensor->extclk, rate); ++ if (ret) { ++ dev_err(dev, "failed to set clock rate\n"); ++ return ret; ++ } ++ } ++ ++ sensor->extclk_rate = clk_get_rate(sensor->extclk); ++ ++ for (i = 0; i < ARRAY_SIZE(supported_extclk_rates); i++) { ++ if (sensor->extclk_rate == supported_extclk_rates[i]) ++ break; ++ } ++ ++ if (i == ARRAY_SIZE(supported_extclk_rates)) { ++ dev_err(dev, "clock rate %lu Hz is unsupported\n", ++ sensor->extclk_rate); + ret = -EINVAL; + goto error_endpoint; + } + ++ sensor->extclk_rate_idx = i; ++ + /* Subdev, entity and pad */ + + subdev = &sensor->subdev; +-- +2.32.0 + +From d0f61b9ae31e8301a7876d233ef72fd57dad8a8e Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 22:19:10 +0100 +Subject: [PATCH] media: i2c: Add .get_selection() support to ov8865 + +The ov8865 drivers media pad ops currently does not include +.get_selection() - add support for that callback. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 61 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 61 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index cae7dc9da49d..3ce0af7e0054 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -450,6 +450,15 @@ + #define OV8865_PRE_CTRL0_PATTERN_COLOR_SQUARES 2 + #define OV8865_PRE_CTRL0_PATTERN_BLACK 3 + ++/* Pixel Array */ ++ ++#define OV8865_NATIVE_WIDTH 3296 ++#define OV8865_NATIVE_HEIGHT 2528 ++#define OV8865_ACTIVE_START_TOP 32 ++#define OV8865_ACTIVE_START_LEFT 80 ++#define OV8865_ACTIVE_WIDTH 3264 ++#define OV8865_ACTIVE_HEIGHT 2448 ++ + /* Macros */ + + #define ov8865_subdev_sensor(s) \ +@@ -2745,12 +2754,64 @@ static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, + return 0; + } + ++static void ++__ov8865_get_pad_crop(struct ov8865_sensor *sensor, ++ struct v4l2_subdev_pad_config *cfg, unsigned int pad, ++ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, cfg, pad); ++ break; ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ r->height = sensor->state.mode->output_size_y; ++ r->width = sensor->state.mode->output_size_x; ++ r->top = (OV8865_NATIVE_HEIGHT - sensor->state.mode->output_size_y) / 2; ++ r->left = (OV8865_NATIVE_WIDTH - sensor->state.mode->output_size_x) / 2; ++ break; ++ } ++} ++ ++static int ov8865_get_selection(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_pad_config *cfg, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP: ++ mutex_lock(&sensor->mutex); ++ __ov8865_get_pad_crop(sensor, cfg, sel->pad, ++ sel->which, &sel->r); ++ mutex_unlock(&sensor->mutex); ++ break; ++ case V4L2_SEL_TGT_NATIVE_SIZE: ++ sel->r.top = 0; ++ sel->r.left = 0; ++ sel->r.width = OV8865_NATIVE_WIDTH; ++ sel->r.height = OV8865_NATIVE_HEIGHT; ++ break; ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ sel->r.top = OV8865_ACTIVE_START_TOP; ++ sel->r.left = OV8865_ACTIVE_START_LEFT; ++ sel->r.width = OV8865_ACTIVE_WIDTH; ++ sel->r.height = OV8865_ACTIVE_HEIGHT; ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { + .enum_mbus_code = ov8865_enum_mbus_code, + .get_fmt = ov8865_get_fmt, + .set_fmt = ov8865_set_fmt, + .enum_frame_size = ov8865_enum_frame_size, + .enum_frame_interval = ov8865_enum_frame_interval, ++ .get_selection = ov8865_get_selection, + }; + + static const struct v4l2_subdev_ops ov8865_subdev_ops = { +-- +2.32.0 + +From 6611cde0f25e294b61024693a8d4eba01242e8ac Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 22:34:43 +0100 +Subject: [PATCH] media: i2c: Switch control to V4L2_CID_ANALOGUE_GAIN + +The V4L2_CID_GAIN control for this driver configures registers that +the datasheet specifies as analogue gain. Switch the control's ID +to V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 3ce0af7e0054..c0c6b1d7e1ed 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2137,7 +2137,7 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) + + /* Gain */ + +-static int ov8865_gain_configure(struct ov8865_sensor *sensor, u32 gain) ++static int ov8865_analog_gain_configure(struct ov8865_sensor *sensor, u32 gain) + { + int ret; + +@@ -2447,8 +2447,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) + if (ret) + return ret; + break; +- case V4L2_CID_GAIN: +- ret = ov8865_gain_configure(sensor, ctrl->val); ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov8865_analog_gain_configure(sensor, ctrl->val); + if (ret) + return ret; + break; +@@ -2493,7 +2493,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + + /* Gain */ + +- v4l2_ctrl_new_std(handler, ops, V4L2_CID_GAIN, 128, 8191, 128, 128); ++ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, 128); + + /* White Balance */ + +-- +2.32.0 + +From 1d96e1322cf63d0bdeb65c68b232edce509b99c6 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Mon, 12 Jul 2021 22:54:56 +0100 +Subject: [PATCH] media: i2c: Add vblank control to ov8865 + +Add a V4L2_CID_VBLANK control to the ov8865 driver. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 34 ++++++++++++++++++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index c0c6b1d7e1ed..5f67d85e33bc 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -183,6 +183,8 @@ + #define OV8865_VTS_H(v) (((v) & GENMASK(11, 8)) >> 8) + #define OV8865_VTS_L_REG 0x380f + #define OV8865_VTS_L(v) ((v) & GENMASK(7, 0)) ++#define OV8865_TIMING_MAX_VTS 0xffff ++#define OV8865_TIMING_MIN_VTS 0x04 + #define OV8865_OFFSET_X_H_REG 0x3810 + #define OV8865_OFFSET_X_H(v) (((v) & GENMASK(15, 8)) >> 8) + #define OV8865_OFFSET_X_L_REG 0x3811 +@@ -658,6 +660,7 @@ struct ov8865_state { + struct ov8865_ctrls { + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *vblank; + + struct v4l2_ctrl_handler handler; + }; +@@ -2212,6 +2215,20 @@ static int ov8865_test_pattern_configure(struct ov8865_sensor *sensor, + ov8865_test_pattern_bits[index]); + } + ++/* Blanking */ ++ ++static int ov8865_vts_configure(struct ov8865_sensor *sensor, u32 vblank) ++{ ++ u16 vts = sensor->state.mode->output_size_y + vblank; ++ int ret; ++ ++ ret = ov8865_write(sensor, OV8865_VTS_H_REG, OV8865_VTS_H(vts)); ++ if (ret) ++ return ret; ++ ++ return ov8865_write(sensor, OV8865_VTS_L_REG, OV8865_VTS_L(vts)); ++} ++ + /* State */ + + static int ov8865_state_mipi_configure(struct ov8865_sensor *sensor, +@@ -2463,6 +2480,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_TEST_PATTERN: + index = (unsigned int)ctrl->val; + return ov8865_test_pattern_configure(sensor, index); ++ case V4L2_CID_VBLANK: ++ return ov8865_vts_configure(sensor, ctrl->val); + default: + return -EINVAL; + } +@@ -2479,6 +2498,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + struct ov8865_ctrls *ctrls = &sensor->ctrls; + struct v4l2_ctrl_handler *handler = &ctrls->handler; + const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; ++ const struct ov8865_mode *mode = sensor->state.mode; ++ unsigned int vblank_max, vblank_def; + int ret; + + v4l2_ctrl_handler_init(handler, 32); +@@ -2514,6 +2535,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + ARRAY_SIZE(ov8865_test_pattern_menu) - 1, + 0, 0, ov8865_test_pattern_menu); + ++ /* Blanking */ ++ vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; ++ vblank_def = mode->vts - mode->output_size_y; ++ ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, ++ OV8865_TIMING_MIN_VTS, vblank_max, 1, ++ vblank_def); ++ + /* MIPI CSI-2 */ + + ctrls->link_freq = +@@ -2696,6 +2724,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, + sensor->state.mbus_code != mbus_code) + ret = ov8865_state_configure(sensor, mode, mbus_code); + ++ __v4l2_ctrl_modify_range(sensor->ctrls.vblank, OV8865_TIMING_MIN_VTS, ++ OV8865_TIMING_MAX_VTS - mode->output_size_y, ++ 1, mode->vts - mode->output_size_y); ++ + complete: + mutex_unlock(&sensor->mutex); + +@@ -3023,6 +3055,8 @@ static int ov8865_probe(struct i2c_client *client) + + /* Sensor */ + ++ sensor->state.mode = &ov8865_modes[0]; ++ + ret = ov8865_ctrls_init(sensor); + if (ret) + goto error_mutex; +-- +2.32.0 + +From e432936cfa2d86c486b6287b8b88a7844782b8ac Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 13 Jul 2021 23:40:33 +0100 +Subject: [PATCH] media: i2c: Add hblank control to ov8865 + +Add a V4L2_CID_HBLANK control to the ov8865 driver. This is read only +with timing control intended to be done via vblanking alone. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 5f67d85e33bc..66754ff62a22 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -660,6 +660,7 @@ struct ov8865_state { + struct ov8865_ctrls { + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; + + struct v4l2_ctrl_handler handler; +@@ -2500,6 +2501,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; + const struct ov8865_mode *mode = sensor->state.mode; + unsigned int vblank_max, vblank_def; ++ unsigned int hblank; + int ret; + + v4l2_ctrl_handler_init(handler, 32); +@@ -2536,6 +2538,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + 0, 0, ov8865_test_pattern_menu); + + /* Blanking */ ++ hblank = mode->hts < mode->output_size_x ? 0 : mode->hts - mode->output_size_x; ++ ctrls->hblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HBLANK, hblank, ++ hblank, 1, hblank); ++ ++ if (ctrls->hblank) ++ ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ + vblank_max = OV8865_TIMING_MAX_VTS - mode->output_size_y; + vblank_def = mode->vts - mode->output_size_y; + ctrls->vblank = v4l2_ctrl_new_std(handler, ops, V4L2_CID_VBLANK, +@@ -2684,6 +2693,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_mbus_framefmt *mbus_format = &format->format; + const struct ov8865_mode *mode; + u32 mbus_code = 0; ++ unsigned int hblank; + unsigned int index; + int ret = 0; + +@@ -2728,6 +2738,10 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, + OV8865_TIMING_MAX_VTS - mode->output_size_y, + 1, mode->vts - mode->output_size_y); + ++ hblank = mode->hts < mode->output_size_x ? 0 : mode->hts - mode->output_size_x; ++ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, ++ hblank); ++ + complete: + mutex_unlock(&sensor->mutex); + +-- +2.32.0 + +From f62f72885babb51aed1c5303984c158930c5ba77 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 13 Jul 2021 23:43:17 +0100 +Subject: [PATCH] media: i2c: cap exposure at height + vblank in ov8865 + +Exposure limits depend on the total height; when vblank is altered (and +thus the total height is altered), change the exposure limits to reflect +the new cap. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 24 ++++++++++++++++++++++-- + 1 file changed, 22 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 66754ff62a22..93e741952050 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -662,6 +662,7 @@ struct ov8865_ctrls { + struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *exposure; + + struct v4l2_ctrl_handler handler; + }; +@@ -2455,6 +2456,18 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) + unsigned int index; + int ret; + ++ /* If VBLANK is altered we need to update exposure to compensate */ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ int exposure_max; ++ ++ exposure_max = sensor->state.mode->output_size_y + ctrl->val; ++ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, ++ sensor->ctrls.exposure->minimum, ++ exposure_max, ++ sensor->ctrls.exposure->step, ++ min(sensor->ctrls.exposure->val, exposure_max)); ++ } ++ + /* Wait for the sensor to be on before setting controls. */ + if (pm_runtime_suspended(sensor->dev)) + return 0; +@@ -2511,8 +2524,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + + /* Exposure */ + +- v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, 1048575, 16, +- 512); ++ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, ++ 1048575, 16, 512); + + /* Gain */ + +@@ -2695,6 +2708,7 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, + u32 mbus_code = 0; + unsigned int hblank; + unsigned int index; ++ int exposure_max; + int ret = 0; + + mutex_lock(&sensor->mutex); +@@ -2742,6 +2756,12 @@ static int ov8865_set_fmt(struct v4l2_subdev *subdev, + __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, + hblank); + ++ exposure_max = mode->vts; ++ __v4l2_ctrl_modify_range(sensor->ctrls.exposure, ++ sensor->ctrls.exposure->minimum, exposure_max, ++ sensor->ctrls.exposure->step, ++ min(sensor->ctrls.exposure->val, exposure_max)); ++ + complete: + mutex_unlock(&sensor->mutex); + +-- +2.32.0 + +From fa9f76e6045f25d12bf986424535e5c25956a403 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 14 Jul 2021 18:05:44 +0100 +Subject: [PATCH] media: i2c: Remove unused macros from ov8865 + +There are a number of macros defined in this driver that aren't actually +used within it. There's a lot of macros defined in total, so removing the +unused ones helps make it a bit less busy. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 137 +------------------------------------ + 1 file changed, 1 insertion(+), 136 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 93e741952050..fd4de2ea1fa9 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -46,8 +46,6 @@ + #define OV8865_PLL_CTRL6_REG 0x306 + #define OV8865_PLL_CTRL6_SYS_DIV(v) (((v) - 1) & BIT(0)) + +-#define OV8865_PLL_CTRL8_REG 0x308 +-#define OV8865_PLL_CTRL9_REG 0x309 + #define OV8865_PLL_CTRLA_REG 0x30a + #define OV8865_PLL_CTRLA_PRE_DIV_HALF(v) (((v) - 1) & BIT(0)) + #define OV8865_PLL_CTRLB_REG 0x30b +@@ -60,41 +58,21 @@ + #define OV8865_PLL_CTRLE_SYS_DIV(v) ((v) & GENMASK(2, 0)) + #define OV8865_PLL_CTRLF_REG 0x30f + #define OV8865_PLL_CTRLF_SYS_PRE_DIV(v) (((v) - 1) & GENMASK(3, 0)) +-#define OV8865_PLL_CTRL10_REG 0x310 +-#define OV8865_PLL_CTRL11_REG 0x311 + #define OV8865_PLL_CTRL12_REG 0x312 + #define OV8865_PLL_CTRL12_PRE_DIV_HALF(v) ((((v) - 1) << 4) & BIT(4)) + #define OV8865_PLL_CTRL12_DAC_DIV(v) (((v) - 1) & GENMASK(3, 0)) + +-#define OV8865_PLL_CTRL1B_REG 0x31b +-#define OV8865_PLL_CTRL1C_REG 0x31c +- + #define OV8865_PLL_CTRL1E_REG 0x31e + #define OV8865_PLL_CTRL1E_PLL1_NO_LAT BIT(3) + +-#define OV8865_PAD_OEN0_REG 0x3000 +- +-#define OV8865_PAD_OEN2_REG 0x3002 +- +-#define OV8865_CLK_RST5_REG 0x3005 +- + #define OV8865_CHIP_ID_HH_REG 0x300a + #define OV8865_CHIP_ID_HH_VALUE 0x00 + #define OV8865_CHIP_ID_H_REG 0x300b + #define OV8865_CHIP_ID_H_VALUE 0x88 + #define OV8865_CHIP_ID_L_REG 0x300c + #define OV8865_CHIP_ID_L_VALUE 0x65 +-#define OV8865_PAD_OUT2_REG 0x300d +- +-#define OV8865_PAD_SEL2_REG 0x3010 +-#define OV8865_PAD_PK_REG 0x3011 +-#define OV8865_PAD_PK_DRIVE_STRENGTH_1X (0 << 5) +-#define OV8865_PAD_PK_DRIVE_STRENGTH_2X (1 << 5) +-#define OV8865_PAD_PK_DRIVE_STRENGTH_3X (2 << 5) +-#define OV8865_PAD_PK_DRIVE_STRENGTH_4X (3 << 5) + + #define OV8865_PUMP_CLK_DIV_REG 0x3015 +-#define OV8865_PUMP_CLK_DIV_PUMP_N(v) (((v) << 4) & GENMASK(6, 4)) + #define OV8865_PUMP_CLK_DIV_PUMP_P(v) ((v) & GENMASK(2, 0)) + + #define OV8865_MIPI_SC_CTRL0_REG 0x3018 +@@ -102,21 +80,12 @@ + GENMASK(7, 5)) + #define OV8865_MIPI_SC_CTRL0_MIPI_EN BIT(4) + #define OV8865_MIPI_SC_CTRL0_UNKNOWN BIT(1) +-#define OV8865_MIPI_SC_CTRL0_LANES_PD_MIPI BIT(0) +-#define OV8865_MIPI_SC_CTRL1_REG 0x3019 +-#define OV8865_CLK_RST0_REG 0x301a +-#define OV8865_CLK_RST1_REG 0x301b +-#define OV8865_CLK_RST2_REG 0x301c +-#define OV8865_CLK_RST3_REG 0x301d +-#define OV8865_CLK_RST4_REG 0x301e + + #define OV8865_PCLK_SEL_REG 0x3020 + #define OV8865_PCLK_SEL_PCLK_DIV_MASK BIT(3) + #define OV8865_PCLK_SEL_PCLK_DIV(v) ((((v) - 1) << 3) & BIT(3)) + +-#define OV8865_MISC_CTRL_REG 0x3021 + #define OV8865_MIPI_SC_CTRL2_REG 0x3022 +-#define OV8865_MIPI_SC_CTRL2_CLK_LANES_PD_MIPI BIT(1) + #define OV8865_MIPI_SC_CTRL2_PD_MIPI_RST_SYNC BIT(0) + + #define OV8865_MIPI_BIT_SEL_REG 0x3031 +@@ -125,7 +94,6 @@ + #define OV8865_CLK_SEL0_PLL1_SYS_SEL(v) (((v) << 7) & BIT(7)) + #define OV8865_CLK_SEL1_REG 0x3033 + #define OV8865_CLK_SEL1_MIPI_EOF BIT(5) +-#define OV8865_CLK_SEL1_UNKNOWN BIT(2) + #define OV8865_CLK_SEL1_PLL_SCLK_SEL_MASK BIT(1) + #define OV8865_CLK_SEL1_PLL_SCLK_SEL(v) (((v) << 1) & BIT(1)) + +@@ -142,7 +110,6 @@ + #define OV8865_EXPOSURE_CTRL_H(v) (((v) & GENMASK(15, 8)) >> 8) + #define OV8865_EXPOSURE_CTRL_L_REG 0x3502 + #define OV8865_EXPOSURE_CTRL_L(v) ((v) & GENMASK(7, 0)) +-#define OV8865_EXPOSURE_GAIN_MANUAL_REG 0x3503 + + #define OV8865_GAIN_CTRL_H_REG 0x3508 + #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) +@@ -197,18 +164,6 @@ + #define OV8865_INC_X_ODD(v) ((v) & GENMASK(4, 0)) + #define OV8865_INC_X_EVEN_REG 0x3815 + #define OV8865_INC_X_EVEN(v) ((v) & GENMASK(4, 0)) +-#define OV8865_VSYNC_START_H_REG 0x3816 +-#define OV8865_VSYNC_START_H(v) (((v) & GENMASK(15, 8)) >> 8) +-#define OV8865_VSYNC_START_L_REG 0x3817 +-#define OV8865_VSYNC_START_L(v) ((v) & GENMASK(7, 0)) +-#define OV8865_VSYNC_END_H_REG 0x3818 +-#define OV8865_VSYNC_END_H(v) (((v) & GENMASK(15, 8)) >> 8) +-#define OV8865_VSYNC_END_L_REG 0x3819 +-#define OV8865_VSYNC_END_L(v) ((v) & GENMASK(7, 0)) +-#define OV8865_HSYNC_FIRST_H_REG 0x381a +-#define OV8865_HSYNC_FIRST_H(v) (((v) & GENMASK(15, 8)) >> 8) +-#define OV8865_HSYNC_FIRST_L_REG 0x381b +-#define OV8865_HSYNC_FIRST_L(v) ((v) & GENMASK(7, 0)) + + #define OV8865_FORMAT1_REG 0x3820 + #define OV8865_FORMAT1_FLIP_VERT_ISP_EN BIT(2) +@@ -240,10 +195,6 @@ + #define OV8865_AUTO_SIZE_CTRL_CROP_END_X_REG BIT(2) + #define OV8865_AUTO_SIZE_CTRL_CROP_START_Y_REG BIT(1) + #define OV8865_AUTO_SIZE_CTRL_CROP_START_X_REG BIT(0) +-#define OV8865_AUTO_SIZE_X_OFFSET_H_REG 0x3842 +-#define OV8865_AUTO_SIZE_X_OFFSET_L_REG 0x3843 +-#define OV8865_AUTO_SIZE_Y_OFFSET_H_REG 0x3844 +-#define OV8865_AUTO_SIZE_Y_OFFSET_L_REG 0x3845 + #define OV8865_AUTO_SIZE_BOUNDARIES_REG 0x3846 + #define OV8865_AUTO_SIZE_BOUNDARIES_Y(v) (((v) << 4) & GENMASK(7, 4)) + #define OV8865_AUTO_SIZE_BOUNDARIES_X(v) ((v) & GENMASK(3, 0)) +@@ -259,30 +210,10 @@ + #define OV8865_BLC_CTRL0_TRIG_FORMAT_EN BIT(6) + #define OV8865_BLC_CTRL0_TRIG_GAIN_EN BIT(5) + #define OV8865_BLC_CTRL0_TRIG_EXPOSURE_EN BIT(4) +-#define OV8865_BLC_CTRL0_TRIG_MANUAL_EN BIT(3) +-#define OV8865_BLC_CTRL0_FREEZE_EN BIT(2) +-#define OV8865_BLC_CTRL0_ALWAYS_EN BIT(1) + #define OV8865_BLC_CTRL0_FILTER_EN BIT(0) + #define OV8865_BLC_CTRL1_REG 0x4001 +-#define OV8865_BLC_CTRL1_DITHER_EN BIT(7) +-#define OV8865_BLC_CTRL1_ZERO_LINE_DIFF_EN BIT(6) +-#define OV8865_BLC_CTRL1_COL_SHIFT_256 (0 << 4) + #define OV8865_BLC_CTRL1_COL_SHIFT_128 (1 << 4) +-#define OV8865_BLC_CTRL1_COL_SHIFT_64 (2 << 4) +-#define OV8865_BLC_CTRL1_COL_SHIFT_32 (3 << 4) + #define OV8865_BLC_CTRL1_OFFSET_LIMIT_EN BIT(2) +-#define OV8865_BLC_CTRL1_COLUMN_CANCEL_EN BIT(1) +-#define OV8865_BLC_CTRL2_REG 0x4002 +-#define OV8865_BLC_CTRL3_REG 0x4003 +-#define OV8865_BLC_CTRL4_REG 0x4004 +-#define OV8865_BLC_CTRL5_REG 0x4005 +-#define OV8865_BLC_CTRL6_REG 0x4006 +-#define OV8865_BLC_CTRL7_REG 0x4007 +-#define OV8865_BLC_CTRL8_REG 0x4008 +-#define OV8865_BLC_CTRL9_REG 0x4009 +-#define OV8865_BLC_CTRLA_REG 0x400a +-#define OV8865_BLC_CTRLB_REG 0x400b +-#define OV8865_BLC_CTRLC_REG 0x400c + #define OV8865_BLC_CTRLD_REG 0x400d + #define OV8865_BLC_CTRLD_OFFSET_TRIGGER(v) ((v) & GENMASK(7, 0)) + +@@ -337,66 +268,8 @@ + + /* MIPI */ + +-#define OV8865_MIPI_CTRL0_REG 0x4800 +-#define OV8865_MIPI_CTRL1_REG 0x4801 +-#define OV8865_MIPI_CTRL2_REG 0x4802 +-#define OV8865_MIPI_CTRL3_REG 0x4803 +-#define OV8865_MIPI_CTRL4_REG 0x4804 +-#define OV8865_MIPI_CTRL5_REG 0x4805 +-#define OV8865_MIPI_CTRL6_REG 0x4806 +-#define OV8865_MIPI_CTRL7_REG 0x4807 +-#define OV8865_MIPI_CTRL8_REG 0x4808 +- +-#define OV8865_MIPI_FCNT_MAX_H_REG 0x4810 +-#define OV8865_MIPI_FCNT_MAX_L_REG 0x4811 +- +-#define OV8865_MIPI_CTRL13_REG 0x4813 +-#define OV8865_MIPI_CTRL14_REG 0x4814 +-#define OV8865_MIPI_CTRL15_REG 0x4815 +-#define OV8865_MIPI_EMBEDDED_DT_REG 0x4816 +- +-#define OV8865_MIPI_HS_ZERO_MIN_H_REG 0x4818 +-#define OV8865_MIPI_HS_ZERO_MIN_L_REG 0x4819 +-#define OV8865_MIPI_HS_TRAIL_MIN_H_REG 0x481a +-#define OV8865_MIPI_HS_TRAIL_MIN_L_REG 0x481b +-#define OV8865_MIPI_CLK_ZERO_MIN_H_REG 0x481c +-#define OV8865_MIPI_CLK_ZERO_MIN_L_REG 0x481d +-#define OV8865_MIPI_CLK_PREPARE_MAX_REG 0x481e +-#define OV8865_MIPI_CLK_PREPARE_MIN_REG 0x481f +-#define OV8865_MIPI_CLK_POST_MIN_H_REG 0x4820 +-#define OV8865_MIPI_CLK_POST_MIN_L_REG 0x4821 +-#define OV8865_MIPI_CLK_TRAIL_MIN_H_REG 0x4822 +-#define OV8865_MIPI_CLK_TRAIL_MIN_L_REG 0x4823 +-#define OV8865_MIPI_LPX_P_MIN_H_REG 0x4824 +-#define OV8865_MIPI_LPX_P_MIN_L_REG 0x4825 +-#define OV8865_MIPI_HS_PREPARE_MIN_REG 0x4826 +-#define OV8865_MIPI_HS_PREPARE_MAX_REG 0x4827 +-#define OV8865_MIPI_HS_EXIT_MIN_H_REG 0x4828 +-#define OV8865_MIPI_HS_EXIT_MIN_L_REG 0x4829 +-#define OV8865_MIPI_UI_HS_ZERO_MIN_REG 0x482a +-#define OV8865_MIPI_UI_HS_TRAIL_MIN_REG 0x482b +-#define OV8865_MIPI_UI_CLK_ZERO_MIN_REG 0x482c +-#define OV8865_MIPI_UI_CLK_PREPARE_REG 0x482d +-#define OV8865_MIPI_UI_CLK_POST_MIN_REG 0x482e +-#define OV8865_MIPI_UI_CLK_TRAIL_MIN_REG 0x482f +-#define OV8865_MIPI_UI_LPX_P_MIN_REG 0x4830 +-#define OV8865_MIPI_UI_HS_PREPARE_REG 0x4831 +-#define OV8865_MIPI_UI_HS_EXIT_MIN_REG 0x4832 +-#define OV8865_MIPI_PKT_START_SIZE_REG 0x4833 +- + #define OV8865_MIPI_PCLK_PERIOD_REG 0x4837 +-#define OV8865_MIPI_LP_GPIO0_REG 0x4838 +-#define OV8865_MIPI_LP_GPIO1_REG 0x4839 +- +-#define OV8865_MIPI_CTRL3C_REG 0x483c +-#define OV8865_MIPI_LP_GPIO4_REG 0x483d +- +-#define OV8865_MIPI_CTRL4A_REG 0x484a +-#define OV8865_MIPI_CTRL4B_REG 0x484b +-#define OV8865_MIPI_CTRL4C_REG 0x484c +-#define OV8865_MIPI_LANE_TEST_PATTERN_REG 0x484d +-#define OV8865_MIPI_FRAME_END_DELAY_REG 0x484e +-#define OV8865_MIPI_CLOCK_TEST_PATTERN_REG 0x484f ++ + #define OV8865_MIPI_LANE_SEL01_REG 0x4850 + #define OV8865_MIPI_LANE_SEL01_LANE0(v) (((v) << 0) & GENMASK(2, 0)) + #define OV8865_MIPI_LANE_SEL01_LANE1(v) (((v) << 4) & GENMASK(6, 4)) +@@ -407,7 +280,6 @@ + /* ISP */ + + #define OV8865_ISP_CTRL0_REG 0x5000 +-#define OV8865_ISP_CTRL0_LENC_EN BIT(7) + #define OV8865_ISP_CTRL0_WHITE_BALANCE_EN BIT(4) + #define OV8865_ISP_CTRL0_DPC_BLACK_EN BIT(2) + #define OV8865_ISP_CTRL0_DPC_WHITE_EN BIT(1) +@@ -416,17 +288,11 @@ + #define OV8865_ISP_CTRL2_REG 0x5002 + #define OV8865_ISP_CTRL2_DEBUG BIT(3) + #define OV8865_ISP_CTRL2_VARIOPIXEL_EN BIT(2) +-#define OV8865_ISP_CTRL2_VSYNC_LATCH_EN BIT(0) +-#define OV8865_ISP_CTRL3_REG 0x5003 + + #define OV8865_ISP_GAIN_RED_H_REG 0x5018 + #define OV8865_ISP_GAIN_RED_H(v) (((v) & GENMASK(13, 6)) >> 6) + #define OV8865_ISP_GAIN_RED_L_REG 0x5019 + #define OV8865_ISP_GAIN_RED_L(v) ((v) & GENMASK(5, 0)) +-#define OV8865_ISP_GAIN_GREEN_H_REG 0x501a +-#define OV8865_ISP_GAIN_GREEN_H(v) (((v) & GENMASK(13, 6)) >> 6) +-#define OV8865_ISP_GAIN_GREEN_L_REG 0x501b +-#define OV8865_ISP_GAIN_GREEN_L(v) ((v) & GENMASK(5, 0)) + #define OV8865_ISP_GAIN_BLUE_H_REG 0x501c + #define OV8865_ISP_GAIN_BLUE_H(v) (((v) & GENMASK(13, 6)) >> 6) + #define OV8865_ISP_GAIN_BLUE_L_REG 0x501d +@@ -434,7 +300,6 @@ + + /* VarioPixel */ + +-#define OV8865_VAP_CTRL0_REG 0x5900 + #define OV8865_VAP_CTRL1_REG 0x5901 + #define OV8865_VAP_CTRL1_HSUB_COEF(v) ((((v) - 1) << 2) & \ + GENMASK(3, 2)) +-- +2.32.0 + +From 31c84c46eddc130e9ccfc0bee5021cc8fed6ef9e Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Fri, 16 Jul 2021 00:00:54 +0100 +Subject: [PATCH] media: i2c: Switch exposure control unit to lines + +The ov8865 driver currently has the unit of the V4L2_CID_EXPOSURE control +as 1/16th of a line. This is what the sensor expects, but isn't very +intuitive. Switch the control to be in units of a line and simply do the +16x multiplication before passing the value to the sensor. + +The datasheet for this sensor gives minimum exposure as 2 lines, so take +the opportunity to correct the lower bounds of the control. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index fd4de2ea1fa9..1905028742d5 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -1991,6 +1991,9 @@ static int ov8865_exposure_configure(struct ov8865_sensor *sensor, u32 exposure) + { + int ret; + ++ /* The sensor stores exposure in units of 1/16th of a line */ ++ exposure *= 16; ++ + ret = ov8865_write(sensor, OV8865_EXPOSURE_CTRL_HH_REG, + OV8865_EXPOSURE_CTRL_HH(exposure)); + if (ret) +@@ -2389,8 +2392,8 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + + /* Exposure */ + +- ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 16, +- 1048575, 16, 512); ++ ctrls->exposure = v4l2_ctrl_new_std(handler, ops, V4L2_CID_EXPOSURE, 2, ++ 65535, 1, 32); + + /* Gain */ + +-- +2.32.0 + +From 4bdfc85c28431fdb7e6fa40101b4b9646d5aea1e Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Fri, 16 Jul 2021 22:56:15 +0100 +Subject: [PATCH] media: i2c: Add controls from fwnode to ov8865 + +Add V4L2_CID_ORIENTATION and V4L2_CID_ROTATION controls to the ov8865 +driver by attempting to parse them from firmware. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 1905028742d5..e88825ea76aa 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2381,6 +2381,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + struct v4l2_ctrl_handler *handler = &ctrls->handler; + const struct v4l2_ctrl_ops *ops = &ov8865_ctrl_ops; + const struct ov8865_mode *mode = sensor->state.mode; ++ struct v4l2_fwnode_device_properties props; + unsigned int vblank_max, vblank_def; + unsigned int hblank; + int ret; +@@ -2443,6 +2444,15 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + v4l2_ctrl_new_std(handler, NULL, V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, 1); + ++ /* set properties from fwnode (e.g. rotation, orientation) */ ++ ret = v4l2_fwnode_device_parse(sensor->dev, &props); ++ if (ret) ++ goto error_ctrls; ++ ++ ret = v4l2_ctrl_new_fwnode_properties(handler, ops, &props); ++ if (ret) ++ goto error_ctrls; ++ + if (handler->error) { + ret = handler->error; + goto error_ctrls; +-- +2.32.0 + +From 9647b126e5c3604c09175ffa9f1e8f0bc113d5ba Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 14 Jul 2021 00:05:04 +0100 +Subject: [PATCH] media: ipu3-cio2: Add INT347A to cio2-bridge + +ACPI _HID INT347A represents the OV8865 sensor, the driver for which can +support the platforms that the cio2-bridge serves. Add it to the array +of supported sensors so the bridge will connect the sensor to the CIO2 +device. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/cio2-bridge.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c +index f757b64eb483..05d607cf5962 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -24,6 +24,8 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = { + CIO2_SENSOR_CONFIG("INT33BE", 0), + /* Omnivision OV2680 */ + CIO2_SENSOR_CONFIG("OVTI2680", 0), ++ /* Omnivision OV8865 */ ++ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), + }; + + static const struct cio2_property_names prop_names = { +-- +2.32.0 + diff --git a/patches/5.13/0010-amd-gpio.patch b/patches/5.13/0010-amd-gpio.patch new file mode 100644 index 000000000..f7d2bcd9c --- /dev/null +++ b/patches/5.13/0010-amd-gpio.patch @@ -0,0 +1,109 @@ +From 797219369e6dcc5d2bed353a12cbda2e9e3caf56 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index e90310cbe73a..d89aebf69ce1 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -21,6 +21,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1155,6 +1156,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1212,6 +1224,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.32.0 + +From 17ab906ad892d6f2798724a8c12b6d80f5cae9e8 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index d89aebf69ce1..b18a1dc69b9b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1158,12 +1158,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.32.0 + diff --git a/patches/5.13/0011-amd-s0ix.patch b/patches/5.13/0011-amd-s0ix.patch new file mode 100644 index 000000000..ef9da1334 --- /dev/null +++ b/patches/5.13/0011-amd-s0ix.patch @@ -0,0 +1,1025 @@ +From cbc2f5f6667fddb72517156c071609069252848a Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 22:27:25 +1000 +Subject: [PATCH] platform/x86: amd-pmc: Add device HID for AMD PMC + +The Surface Laptop 4 appears to have used AMD0005 for the PMC instead of +the AMDI0005 which would match the ACPI ID Registry. + +AMD appears to have previously used "AMD" in a number of IDs in the past, +and AMD is not allocated to any other entity as an ID, so adding this ID +should not cause any harm. + +Signed-off-by: Sachi King +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index b9da58ee9b1e..0b5578a8a449 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -275,6 +275,7 @@ static int amd_pmc_remove(struct platform_device *pdev) + static const struct acpi_device_id amd_pmc_acpi_ids[] = { + {"AMDI0005", 0}, + {"AMD0004", 0}, ++ {"AMD0005", 0}, + { } + }; + MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids); +-- +2.32.0 + +From 03247c72b0f1471c51d6680950bcff545f20251c Mon Sep 17 00:00:00 2001 +From: Marcin Bachry +Date: Tue, 16 Mar 2021 15:28:51 -0400 +Subject: [PATCH] PCI: quirks: Quirk PCI d3hot delay for AMD xhci + +Renoir needs a similar delay. + +Signed-off-by: Marcin Bachry +Signed-off-by: Alex Deucher +Patchset: amd-s0ix +--- + drivers/pci/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index 6d74386eadc2..446f8f7b0ee4 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -1900,6 +1900,9 @@ static void quirk_ryzen_xhci_d3hot(struct pci_dev *dev) + } + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15e0, quirk_ryzen_xhci_d3hot); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x15e1, quirk_ryzen_xhci_d3hot); ++/* Renoir XHCI requires longer delay when transitioning from D0 to ++ * D3hot */ ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_AMD, 0x1639, quirk_ryzen_xhci_d3hot); + + #ifdef CONFIG_X86_IO_APIC + static int dmi_disable_ioapicreroute(const struct dmi_system_id *d) +-- +2.32.0 + +From e47b481ce800b5ea83120fce8350eb8051f3fe8e Mon Sep 17 00:00:00 2001 +From: Mario Limonciello +Date: Wed, 9 Jun 2021 13:40:17 -0500 +Subject: [PATCH] ACPI: Check StorageD3Enable _DSD property in ACPI code + +Although first implemented for NVME, this check may be usable by +other drivers as well. Microsoft's specification explicitly mentions +that is may be usable by SATA and AHCI devices. Google also indicates +that they have used this with SDHCI in a downstream kernel tree that +a user can plug a storage device into. + +Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro +Suggested-by: Keith Busch +CC: Shyam-sundar S-k +CC: Alexander Deucher +CC: Rafael J. Wysocki +CC: Prike Liang +Signed-off-by: Mario Limonciello +Reviewed-by: Rafael J. Wysocki +Signed-off-by: Christoph Hellwig +Patchset: amd-s0ix +--- + drivers/acpi/device_pm.c | 29 +++++++++++++++++++++++++++++ + drivers/nvme/host/pci.c | 28 +--------------------------- + include/linux/acpi.h | 5 +++++ + 3 files changed, 35 insertions(+), 27 deletions(-) + +diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c +index 9d2d3b9bb8b5..b20ae93415f0 100644 +--- a/drivers/acpi/device_pm.c ++++ b/drivers/acpi/device_pm.c +@@ -1338,4 +1338,33 @@ int acpi_dev_pm_attach(struct device *dev, bool power_on) + return 1; + } + EXPORT_SYMBOL_GPL(acpi_dev_pm_attach); ++ ++/** ++ * acpi_storage_d3 - Check if D3 should be used in the suspend path ++ * @dev: Device to check ++ * ++ * Return %true if the platform firmware wants @dev to be programmed ++ * into D3hot or D3cold (if supported) in the suspend path, or %false ++ * when there is no specific preference. On some platforms, if this ++ * hint is ignored, @dev may remain unresponsive after suspending the ++ * platform as a whole. ++ * ++ * Although the property has storage in the name it actually is ++ * applied to the PCIe slot and plugging in a non-storage device the ++ * same platform restrictions will likely apply. ++ */ ++bool acpi_storage_d3(struct device *dev) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(dev); ++ u8 val; ++ ++ if (!adev) ++ return false; ++ if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", ++ &val)) ++ return false; ++ return val == 1; ++} ++EXPORT_SYMBOL_GPL(acpi_storage_d3); ++ + #endif /* CONFIG_PM */ +diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c +index 42ad75ff1348..5a72bdf5ad03 100644 +--- a/drivers/nvme/host/pci.c ++++ b/drivers/nvme/host/pci.c +@@ -2828,32 +2828,6 @@ static unsigned long check_vendor_combination_bug(struct pci_dev *pdev) + return 0; + } + +-#ifdef CONFIG_ACPI +-static bool nvme_acpi_storage_d3(struct pci_dev *dev) +-{ +- struct acpi_device *adev = ACPI_COMPANION(&dev->dev); +- u8 val; +- +- /* +- * Look for _DSD property specifying that the storage device on the port +- * must use D3 to support deep platform power savings during +- * suspend-to-idle. +- */ +- +- if (!adev) +- return false; +- if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", +- &val)) +- return false; +- return val == 1; +-} +-#else +-static inline bool nvme_acpi_storage_d3(struct pci_dev *dev) +-{ +- return false; +-} +-#endif /* CONFIG_ACPI */ +- + static void nvme_async_probe(void *data, async_cookie_t cookie) + { + struct nvme_dev *dev = data; +@@ -2903,7 +2877,7 @@ static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id) + + quirks |= check_vendor_combination_bug(pdev); + +- if (!noacpi && nvme_acpi_storage_d3(pdev)) { ++ if (!noacpi && acpi_storage_d3(&pdev->dev)) { + /* + * Some systems use a bios work around to ask for D3 on + * platforms that support kernel managed suspend. +diff --git a/include/linux/acpi.h b/include/linux/acpi.h +index e8ba7063c000..66c43abef4a4 100644 +--- a/include/linux/acpi.h ++++ b/include/linux/acpi.h +@@ -1006,6 +1006,7 @@ int acpi_dev_resume(struct device *dev); + int acpi_subsys_runtime_suspend(struct device *dev); + int acpi_subsys_runtime_resume(struct device *dev); + int acpi_dev_pm_attach(struct device *dev, bool power_on); ++bool acpi_storage_d3(struct device *dev); + #else + static inline int acpi_subsys_runtime_suspend(struct device *dev) { return 0; } + static inline int acpi_subsys_runtime_resume(struct device *dev) { return 0; } +@@ -1013,6 +1014,10 @@ static inline int acpi_dev_pm_attach(struct device *dev, bool power_on) + { + return 0; + } ++static inline bool acpi_storage_d3(struct device *dev) ++{ ++ return false; ++} + #endif + + #if defined(CONFIG_ACPI) && defined(CONFIG_PM_SLEEP) +-- +2.32.0 + +From cbece4688721474e9f061d68532f1fee59dd705f Mon Sep 17 00:00:00 2001 +From: Mario Limonciello +Date: Wed, 9 Jun 2021 13:40:18 -0500 +Subject: [PATCH] ACPI: Add quirks for AMD Renoir/Lucienne CPUs to force the D3 + hint + +AMD systems from Renoir and Lucienne require that the NVME controller +is put into D3 over a Modern Standby / suspend-to-idle +cycle. This is "typically" accomplished using the `StorageD3Enable` +property in the _DSD, but this property was introduced after many +of these systems launched and most OEM systems don't have it in +their BIOS. + +On AMD Renoir without these drives going into D3 over suspend-to-idle +the resume will fail with the NVME controller being reset and a trace +like this in the kernel logs: +``` +[ 83.556118] nvme nvme0: I/O 161 QID 2 timeout, aborting +[ 83.556178] nvme nvme0: I/O 162 QID 2 timeout, aborting +[ 83.556187] nvme nvme0: I/O 163 QID 2 timeout, aborting +[ 83.556196] nvme nvme0: I/O 164 QID 2 timeout, aborting +[ 95.332114] nvme nvme0: I/O 25 QID 0 timeout, reset controller +[ 95.332843] nvme nvme0: Abort status: 0x371 +[ 95.332852] nvme nvme0: Abort status: 0x371 +[ 95.332856] nvme nvme0: Abort status: 0x371 +[ 95.332859] nvme nvme0: Abort status: 0x371 +[ 95.332909] PM: dpm_run_callback(): pci_pm_resume+0x0/0xe0 returns -16 +[ 95.332936] nvme 0000:03:00.0: PM: failed to resume async: error -16 +``` + +The Microsoft documentation for StorageD3Enable mentioned that Windows has +a hardcoded allowlist for D3 support, which was used for these platforms. +Introduce quirks to hardcode them for Linux as well. + +As this property is now "standardized", OEM systems using AMD Cezanne and +newer APU's have adopted this property, and quirks like this should not be +necessary. + +CC: Shyam-sundar S-k +CC: Alexander Deucher +CC: Prike Liang +Link: https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/power-management-for-storage-hardware-devices-intro +Signed-off-by: Mario Limonciello +Acked-by: Rafael J. Wysocki +Tested-by: Julian Sikorski +Signed-off-by: Christoph Hellwig +Patchset: amd-s0ix +--- + drivers/acpi/device_pm.c | 3 +++ + drivers/acpi/internal.h | 9 +++++++++ + drivers/acpi/x86/utils.c | 25 +++++++++++++++++++++++++ + 3 files changed, 37 insertions(+) + +diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c +index b20ae93415f0..0cfdef2fc3ad 100644 +--- a/drivers/acpi/device_pm.c ++++ b/drivers/acpi/device_pm.c +@@ -1358,6 +1358,9 @@ bool acpi_storage_d3(struct device *dev) + struct acpi_device *adev = ACPI_COMPANION(dev); + u8 val; + ++ if (force_storage_d3()) ++ return true; ++ + if (!adev) + return false; + if (fwnode_property_read_u8(acpi_fwnode_handle(adev), "StorageD3Enable", +diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h +index e21611c9a170..7ac01b03ba67 100644 +--- a/drivers/acpi/internal.h ++++ b/drivers/acpi/internal.h +@@ -236,6 +236,15 @@ static inline int suspend_nvs_save(void) { return 0; } + static inline void suspend_nvs_restore(void) {} + #endif + ++#ifdef CONFIG_X86 ++bool force_storage_d3(void); ++#else ++static inline bool force_storage_d3(void) ++{ ++ return false; ++} ++#endif ++ + /*-------------------------------------------------------------------------- + Device properties + -------------------------------------------------------------------------- */ +diff --git a/drivers/acpi/x86/utils.c b/drivers/acpi/x86/utils.c +index bdc1ba00aee9..5298bb4d81fe 100644 +--- a/drivers/acpi/x86/utils.c ++++ b/drivers/acpi/x86/utils.c +@@ -135,3 +135,28 @@ bool acpi_device_always_present(struct acpi_device *adev) + + return ret; + } ++ ++/* ++ * AMD systems from Renoir and Lucienne *require* that the NVME controller ++ * is put into D3 over a Modern Standby / suspend-to-idle cycle. ++ * ++ * This is "typically" accomplished using the `StorageD3Enable` ++ * property in the _DSD that is checked via the `acpi_storage_d3` function ++ * but this property was introduced after many of these systems launched ++ * and most OEM systems don't have it in their BIOS. ++ * ++ * The Microsoft documentation for StorageD3Enable mentioned that Windows has ++ * a hardcoded allowlist for D3 support, which was used for these platforms. ++ * ++ * This allows quirking on Linux in a similar fashion. ++ */ ++const struct x86_cpu_id storage_d3_cpu_ids[] = { ++ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 96, NULL), /* Renoir */ ++ X86_MATCH_VENDOR_FAM_MODEL(AMD, 23, 104, NULL), /* Lucienne */ ++ {} ++}; ++ ++bool force_storage_d3(void) ++{ ++ return x86_match_cpu(storage_d3_cpu_ids); ++} +-- +2.32.0 + +From 538e90326aba5ad0322797144b4fc601115e6359 Mon Sep 17 00:00:00 2001 +From: Alex Deucher +Date: Wed, 17 Mar 2021 10:38:42 -0400 +Subject: [PATCH] platform/x86: force LPS0 functions for AMD + +ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD are supposedly not +required for AMD platforms, and on some platforms they are +not even listed in the function mask but at least some HP +laptops seem to require it to properly support s0ix. + +Based on a patch from Marcin Bachry . + +Bug: https://gitlab.freedesktop.org/drm/amd/-/issues/1230 +Signed-off-by: Alex Deucher +Cc: Marcin Bachry +Reviewed-by: Hans de Goede +Patchset: amd-s0ix +--- + drivers/acpi/x86/s2idle.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c +index 2d7ddb8a8cb6..482e6b23b21a 100644 +--- a/drivers/acpi/x86/s2idle.c ++++ b/drivers/acpi/x86/s2idle.c +@@ -368,6 +368,13 @@ static int lps0_device_attach(struct acpi_device *adev, + + ACPI_FREE(out_obj); + ++ /* ++ * Some HP laptops require ACPI_LPS0_ENTRY_AMD/ACPI_LPS0_EXIT_AMD for proper ++ * S0ix, but don't set the function mask correctly. Fix that up here. ++ */ ++ if (acpi_s2idle_vendor_amd()) ++ lps0_dsm_func_mask |= (1 << ACPI_LPS0_ENTRY_AMD) | (1 << ACPI_LPS0_EXIT_AMD); ++ + acpi_handle_debug(adev->handle, "_DSM function mask: 0x%x\n", + lps0_dsm_func_mask); + +-- +2.32.0 + +From a91022f9251f063ca3f7f1a73af614b5b646a43e Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:35 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Fix command completion code + +The protocol to submit a job request to SMU is to wait for +AMD_PMC_REGISTER_RESPONSE to return 1,meaning SMU is ready to take +requests. PMC driver has to make sure that the response code is always +AMD_PMC_RESULT_OK before making any command submissions. + +Also, when we submit a message to SMU, we have to wait until it processes +the request. Adding a read_poll_timeout() check as this was missing in +the existing code. + +Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") +Signed-off-by: Shyam Sundar S K +Reviewed-by: Hans de Goede +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index 0b5578a8a449..535e431f98a8 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -140,7 +140,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) + + /* Wait until we get a valid response */ + rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, +- val, val > 0, PMC_MSG_DELAY_MIN_US, ++ val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, + PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); + if (rc) { + dev_err(dev->dev, "failed to talk to SMU\n"); +@@ -156,6 +156,14 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) + /* Write message ID to message ID register */ + msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); ++ /* Wait until we get a valid response */ ++ rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, ++ val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, ++ PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); ++ if (rc) { ++ dev_err(dev->dev, "SMU response timed out\n"); ++ return rc; ++ } + return 0; + } + +-- +2.32.0 + +From 77f498db905e555187750157bd8db2430f7d1f74 Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:36 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Fix SMU firmware reporting mechanism + +It was lately understood that the current mechanism available in the +driver to get SMU firmware info works only on internal SMU builds and +there is a separate way to get all the SMU logging counters (addressed +in the next patch). Hence remove all the smu info shown via debugfs as it +is no more useful. + +Also, use dump registers routine only at one place i.e. after the command +submission to SMU is done. + +Fixes: 156ec4731cb2 ("platform/x86: amd-pmc: Add AMD platform support for S2Idle") +Signed-off-by: Shyam Sundar S K +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 15 +-------------- + 1 file changed, 1 insertion(+), 14 deletions(-) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index 535e431f98a8..d32f0a0eeb9f 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -52,7 +52,6 @@ + #define AMD_CPU_ID_PCO AMD_CPU_ID_RV + #define AMD_CPU_ID_CZN AMD_CPU_ID_RN + +-#define AMD_SMU_FW_VERSION 0x0 + #define PMC_MSG_DELAY_MIN_US 100 + #define RESPONSE_REGISTER_LOOP_MAX 200 + +@@ -88,11 +87,6 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 + #ifdef CONFIG_DEBUG_FS + static int smu_fw_info_show(struct seq_file *s, void *unused) + { +- struct amd_pmc_dev *dev = s->private; +- u32 value; +- +- value = ioread32(dev->smu_base + AMD_SMU_FW_VERSION); +- seq_printf(s, "SMU FW Info: %x\n", value); + return 0; + } + DEFINE_SHOW_ATTRIBUTE(smu_fw_info); +@@ -164,6 +158,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) + dev_err(dev->dev, "SMU response timed out\n"); + return rc; + } ++ amd_pmc_dump_registers(dev); + return 0; + } + +@@ -176,7 +171,6 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) + if (rc) + dev_err(pdev->dev, "suspend failed\n"); + +- amd_pmc_dump_registers(pdev); + return 0; + } + +@@ -189,7 +183,6 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) + if (rc) + dev_err(pdev->dev, "resume failed\n"); + +- amd_pmc_dump_registers(pdev); + return 0; + } + +@@ -256,17 +249,11 @@ static int amd_pmc_probe(struct platform_device *pdev) + pci_dev_put(rdev); + base_addr = ((u64)base_addr_hi << 32 | base_addr_lo); + +- dev->smu_base = devm_ioremap(dev->dev, base_addr, AMD_PMC_MAPPING_SIZE); +- if (!dev->smu_base) +- return -ENOMEM; +- + dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMC_BASE_ADDR_OFFSET, + AMD_PMC_MAPPING_SIZE); + if (!dev->regbase) + return -ENOMEM; + +- amd_pmc_dump_registers(dev); +- + platform_set_drvdata(pdev, dev); + amd_pmc_dbgfs_register(dev); + return 0; +-- +2.32.0 + +From e711eb6e913357e1184ad8c7e23e1c3f2ede7494 Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:37 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Add support for logging SMU metrics + +SMU provides a way to dump the s0ix debug statistics in the form of a +metrics table via a of set special mailbox commands. + +Add support to the driver which can send these commands to SMU and expose +the information received via debugfs. The information contains the s0ix +entry/exit, active time of each IP block etc. + +As a side note, SMU subsystem logging is not supported on Picasso based +SoC's. + +Signed-off-by: Shyam Sundar S K +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 148 +++++++++++++++++++++++++++++++-- + 1 file changed, 140 insertions(+), 8 deletions(-) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index d32f0a0eeb9f..b5249fdeb95f 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -46,6 +46,14 @@ + #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE + #define AMD_PMC_RESULT_FAILED 0xFF + ++/* SMU Message Definations */ ++#define SMU_MSG_GETSMUVERSION 0x02 ++#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 ++#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05 ++#define SMU_MSG_LOG_START 0x06 ++#define SMU_MSG_LOG_RESET 0x07 ++#define SMU_MSG_LOG_DUMP_DATA 0x08 ++#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09 + /* List of supported CPU ids */ + #define AMD_CPU_ID_RV 0x15D0 + #define AMD_CPU_ID_RN 0x1630 +@@ -55,17 +63,42 @@ + #define PMC_MSG_DELAY_MIN_US 100 + #define RESPONSE_REGISTER_LOOP_MAX 200 + ++#define SOC_SUBSYSTEM_IP_MAX 12 ++#define DELAY_MIN_US 2000 ++#define DELAY_MAX_US 3000 + enum amd_pmc_def { + MSG_TEST = 0x01, + MSG_OS_HINT_PCO, + MSG_OS_HINT_RN, + }; + ++struct amd_pmc_bit_map { ++ const char *name; ++ u32 bit_mask; ++}; ++ ++static const struct amd_pmc_bit_map soc15_ip_blk[] = { ++ {"DISPLAY", BIT(0)}, ++ {"CPU", BIT(1)}, ++ {"GFX", BIT(2)}, ++ {"VDD", BIT(3)}, ++ {"ACP", BIT(4)}, ++ {"VCN", BIT(5)}, ++ {"ISP", BIT(6)}, ++ {"NBIO", BIT(7)}, ++ {"DF", BIT(8)}, ++ {"USB0", BIT(9)}, ++ {"USB1", BIT(10)}, ++ {"LAPIC", BIT(11)}, ++ {} ++}; ++ + struct amd_pmc_dev { + void __iomem *regbase; +- void __iomem *smu_base; ++ void __iomem *smu_virt_addr; + u32 base_addr; + u32 cpu_id; ++ u32 active_ips; + struct device *dev; + #if IS_ENABLED(CONFIG_DEBUG_FS) + struct dentry *dbgfs_dir; +@@ -73,6 +106,7 @@ struct amd_pmc_dev { + }; + + static struct amd_pmc_dev pmc; ++static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret); + + static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset) + { +@@ -84,9 +118,50 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3 + iowrite32(val, dev->regbase + reg_offset); + } + ++struct smu_metrics { ++ u32 table_version; ++ u32 hint_count; ++ u32 s0i3_cyclecount; ++ u32 timein_s0i2; ++ u64 timeentering_s0i3_lastcapture; ++ u64 timeentering_s0i3_totaltime; ++ u64 timeto_resume_to_os_lastcapture; ++ u64 timeto_resume_to_os_totaltime; ++ u64 timein_s0i3_lastcapture; ++ u64 timein_s0i3_totaltime; ++ u64 timein_swdrips_lastcapture; ++ u64 timein_swdrips_totaltime; ++ u64 timecondition_notmet_lastcapture[SOC_SUBSYSTEM_IP_MAX]; ++ u64 timecondition_notmet_totaltime[SOC_SUBSYSTEM_IP_MAX]; ++} __packed; ++ + #ifdef CONFIG_DEBUG_FS + static int smu_fw_info_show(struct seq_file *s, void *unused) + { ++ struct amd_pmc_dev *dev = s->private; ++ struct smu_metrics table; ++ u32 value; ++ int idx; ++ ++ if (dev->cpu_id == AMD_CPU_ID_PCO) ++ return -EINVAL; ++ ++ memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); ++ ++ seq_puts(s, "\n=== SMU Statistics ===\n"); ++ seq_printf(s, "Table Version: %d\n", table.table_version); ++ seq_printf(s, "Hint Count: %d\n", table.hint_count); ++ seq_printf(s, "S0i3 Cycle Count: %d\n", table.s0i3_cyclecount); ++ seq_printf(s, "Time (in us) to S0i3: %lld\n", table.timeentering_s0i3_lastcapture); ++ seq_printf(s, "Time (in us) in S0i3: %lld\n", table.timein_s0i3_lastcapture); ++ ++ seq_puts(s, "\n=== Active time (in us) ===\n"); ++ for (idx = 0 ; idx < SOC_SUBSYSTEM_IP_MAX ; idx++) { ++ if (soc15_ip_blk[idx].bit_mask & dev->active_ips) ++ seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name, ++ table.timecondition_notmet_lastcapture[idx]); ++ } ++ + return 0; + } + DEFINE_SHOW_ATTRIBUTE(smu_fw_info); +@@ -112,6 +187,32 @@ static inline void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) + } + #endif /* CONFIG_DEBUG_FS */ + ++static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) ++{ ++ u32 phys_addr_low, phys_addr_hi; ++ u64 smu_phys_addr; ++ ++ if (dev->cpu_id == AMD_CPU_ID_PCO) ++ return -EINVAL; ++ ++ /* Get Active devices list from SMU */ ++ amd_pmc_send_cmd(dev, 0, &dev->active_ips, SMU_MSG_GET_SUP_CONSTRAINTS, 1); ++ ++ /* Get dram address */ ++ amd_pmc_send_cmd(dev, 0, &phys_addr_low, SMU_MSG_LOG_GETDRAM_ADDR_LO, 1); ++ amd_pmc_send_cmd(dev, 0, &phys_addr_hi, SMU_MSG_LOG_GETDRAM_ADDR_HI, 1); ++ smu_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); ++ ++ dev->smu_virt_addr = devm_ioremap(dev->dev, smu_phys_addr, sizeof(struct smu_metrics)); ++ if (!dev->smu_virt_addr) ++ return -ENOMEM; ++ ++ /* Start the logging */ ++ amd_pmc_send_cmd(dev, 0, NULL, SMU_MSG_LOG_START, 0); ++ ++ return 0; ++} ++ + static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) + { + u32 value; +@@ -126,10 +227,9 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) + dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); + } + +-static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) ++static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set, u32 *data, u8 msg, bool ret) + { + int rc; +- u8 msg; + u32 val; + + /* Wait until we get a valid response */ +@@ -148,8 +248,8 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, set); + + /* Write message ID to message ID register */ +- msg = (dev->cpu_id == AMD_CPU_ID_RN) ? MSG_OS_HINT_RN : MSG_OS_HINT_PCO; + amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); ++ + /* Wait until we get a valid response */ + rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + val, val == AMD_PMC_RESULT_OK, PMC_MSG_DELAY_MIN_US, +@@ -158,16 +258,40 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, bool set) + dev_err(dev->dev, "SMU response timed out\n"); + return rc; + } ++ ++ if (ret) { ++ /* PMFW may take longer time to return back the data */ ++ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); ++ *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); ++ } ++ + amd_pmc_dump_registers(dev); + return 0; + } + ++static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) ++{ ++ switch (dev->cpu_id) { ++ case AMD_CPU_ID_PCO: ++ return MSG_OS_HINT_PCO; ++ case AMD_CPU_ID_RN: ++ return MSG_OS_HINT_RN; ++ } ++ return -EINVAL; ++} ++ + static int __maybe_unused amd_pmc_suspend(struct device *dev) + { + struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + int rc; ++ u8 msg; ++ ++ /* Reset and Start SMU logging - to monitor the s0i3 stats */ ++ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_RESET, 0); ++ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_START, 0); + +- rc = amd_pmc_send_cmd(pdev, 1); ++ msg = amd_pmc_get_os_hint(pdev); ++ rc = amd_pmc_send_cmd(pdev, 1, NULL, msg, 0); + if (rc) + dev_err(pdev->dev, "suspend failed\n"); + +@@ -178,8 +302,13 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) + { + struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + int rc; ++ u8 msg; ++ ++ /* Let SMU know that we are looking for stats */ ++ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); + +- rc = amd_pmc_send_cmd(pdev, 0); ++ msg = amd_pmc_get_os_hint(pdev); ++ rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); + if (rc) + dev_err(pdev->dev, "resume failed\n"); + +@@ -202,8 +331,7 @@ static int amd_pmc_probe(struct platform_device *pdev) + { + struct amd_pmc_dev *dev = &pmc; + struct pci_dev *rdev; +- u32 base_addr_lo; +- u32 base_addr_hi; ++ u32 base_addr_lo, base_addr_hi; + u64 base_addr; + int err; + u32 val; +@@ -254,6 +382,10 @@ static int amd_pmc_probe(struct platform_device *pdev) + if (!dev->regbase) + return -ENOMEM; + ++ /* Use SMU to get the s0i3 debug stats */ ++ err = amd_pmc_setup_smu_logging(dev); ++ if (err) ++ dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); + platform_set_drvdata(pdev, dev); + amd_pmc_dbgfs_register(dev); + return 0; +-- +2.32.0 + +From 88a2ccda674ec50c98a946f0801fdaa93628c280 Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:38 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Add support for logging s0ix counters + +Even the FCH SSC registers provides certain level of information +about the s0ix entry and exit times which comes handy when the SMU +fails to report the statistics via the mailbox communication. + +This information is captured via a new debugfs file "s0ix_stats". +A non-zero entry in this counters would mean that the system entered +the s0ix state. + +If s0ix entry time and exit time don't change during suspend to idle, +the silicon has not entered the deepest state. + +Signed-off-by: Shyam Sundar S K +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 46 ++++++++++++++++++++++++++++++++-- + 1 file changed, 44 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index b5249fdeb95f..b6ad290c9a86 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -46,6 +46,15 @@ + #define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE + #define AMD_PMC_RESULT_FAILED 0xFF + ++/* FCH SSC Registers */ ++#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30 ++#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34 ++#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38 ++#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C ++#define FCH_SSC_MAPPING_SIZE 0x800 ++#define FCH_BASE_PHY_ADDR_LOW 0xFED81100 ++#define FCH_BASE_PHY_ADDR_HIGH 0x00000000 ++ + /* SMU Message Definations */ + #define SMU_MSG_GETSMUVERSION 0x02 + #define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04 +@@ -96,6 +105,7 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { + struct amd_pmc_dev { + void __iomem *regbase; + void __iomem *smu_virt_addr; ++ void __iomem *fch_virt_addr; + u32 base_addr; + u32 cpu_id; + u32 active_ips; +@@ -140,7 +150,6 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) + { + struct amd_pmc_dev *dev = s->private; + struct smu_metrics table; +- u32 value; + int idx; + + if (dev->cpu_id == AMD_CPU_ID_PCO) +@@ -166,6 +175,29 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) + } + DEFINE_SHOW_ATTRIBUTE(smu_fw_info); + ++static int s0ix_stats_show(struct seq_file *s, void *unused) ++{ ++ struct amd_pmc_dev *dev = s->private; ++ u64 entry_time, exit_time, residency; ++ ++ entry_time = ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_H_OFFSET); ++ entry_time = entry_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_ENTRY_TIME_L_OFFSET); ++ ++ exit_time = ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_H_OFFSET); ++ exit_time = exit_time << 32 | ioread32(dev->fch_virt_addr + FCH_S0I3_EXIT_TIME_L_OFFSET); ++ ++ /* It's in 48MHz. We need to convert it to unit of 100ns */ ++ residency = (exit_time - entry_time) * 10 / 48; ++ ++ seq_puts(s, "=== S0ix statistics ===\n"); ++ seq_printf(s, "S0ix Entry Time: %lld\n", entry_time); ++ seq_printf(s, "S0ix Exit Time: %lld\n", exit_time); ++ seq_printf(s, "Residency Time: %lld\n", residency); ++ ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(s0ix_stats); ++ + static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev) + { + debugfs_remove_recursive(dev->dbgfs_dir); +@@ -176,6 +208,8 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) + dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL); + debugfs_create_file("smu_fw_info", 0644, dev->dbgfs_dir, dev, + &smu_fw_info_fops); ++ debugfs_create_file("s0ix_stats", 0644, dev->dbgfs_dir, dev, ++ &s0ix_stats_fops); + } + #else + static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) +@@ -332,7 +366,7 @@ static int amd_pmc_probe(struct platform_device *pdev) + struct amd_pmc_dev *dev = &pmc; + struct pci_dev *rdev; + u32 base_addr_lo, base_addr_hi; +- u64 base_addr; ++ u64 base_addr, fch_phys_addr; + int err; + u32 val; + +@@ -382,6 +416,14 @@ static int amd_pmc_probe(struct platform_device *pdev) + if (!dev->regbase) + return -ENOMEM; + ++ /* Use FCH registers to get the S0ix stats */ ++ base_addr_lo = FCH_BASE_PHY_ADDR_LOW; ++ base_addr_hi = FCH_BASE_PHY_ADDR_HIGH; ++ fch_phys_addr = ((u64)base_addr_hi << 32 | base_addr_lo); ++ dev->fch_virt_addr = devm_ioremap(dev->dev, fch_phys_addr, FCH_SSC_MAPPING_SIZE); ++ if (!dev->fch_virt_addr) ++ return -ENOMEM; ++ + /* Use SMU to get the s0i3 debug stats */ + err = amd_pmc_setup_smu_logging(dev); + if (err) +-- +2.32.0 + +From 58dde84a288df2fc20ff2827a6b16b10e7eaff08 Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:39 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Add support for ACPI ID AMDI0006 + +Some newer BIOSes have added another ACPI ID for the uPEP device. +SMU statistics behave identically on this device. + +Signed-off-by: Shyam Sundar S K +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index b6ad290c9a86..2a73fe0deaf3 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -443,6 +443,7 @@ static int amd_pmc_remove(struct platform_device *pdev) + + static const struct acpi_device_id amd_pmc_acpi_ids[] = { + {"AMDI0005", 0}, ++ {"AMDI0006", 0}, + {"AMD0004", 0}, + {"AMD0005", 0}, + { } +-- +2.32.0 + +From 0e20c11aebedea83412eedc37da665f096d9c12d Mon Sep 17 00:00:00 2001 +From: Shyam Sundar S K +Date: Thu, 17 Jun 2021 17:00:40 +0530 +Subject: [PATCH] platform/x86: amd-pmc: Add new acpi id for future PMC + controllers + +The upcoming PMC controller would have a newer acpi id, add that to +the supported acpid device list. + +Signed-off-by: Shyam Sundar S K +Patchset: amd-s0ix +--- + drivers/platform/x86/amd-pmc.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c +index 2a73fe0deaf3..5a2be598fc2e 100644 +--- a/drivers/platform/x86/amd-pmc.c ++++ b/drivers/platform/x86/amd-pmc.c +@@ -68,6 +68,7 @@ + #define AMD_CPU_ID_RN 0x1630 + #define AMD_CPU_ID_PCO AMD_CPU_ID_RV + #define AMD_CPU_ID_CZN AMD_CPU_ID_RN ++#define AMD_CPU_ID_YC 0x14B5 + + #define PMC_MSG_DELAY_MIN_US 100 + #define RESPONSE_REGISTER_LOOP_MAX 200 +@@ -309,6 +310,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev) + case AMD_CPU_ID_PCO: + return MSG_OS_HINT_PCO; + case AMD_CPU_ID_RN: ++ case AMD_CPU_ID_YC: + return MSG_OS_HINT_RN; + } + return -EINVAL; +@@ -354,6 +356,7 @@ static const struct dev_pm_ops amd_pmc_pm_ops = { + }; + + static const struct pci_device_id pmc_pci_ids[] = { ++ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_YC) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_CZN) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RN) }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) }, +@@ -444,6 +447,7 @@ static int amd_pmc_remove(struct platform_device *pdev) + static const struct acpi_device_id amd_pmc_acpi_ids[] = { + {"AMDI0005", 0}, + {"AMDI0006", 0}, ++ {"AMDI0007", 0}, + {"AMD0004", 0}, + {"AMD0005", 0}, + { } +-- +2.32.0 + +From 3020f6c4637a7fecb94866eb633719898308e07b Mon Sep 17 00:00:00 2001 +From: Pratik Vishwakarma +Date: Thu, 17 Jun 2021 11:42:08 -0500 +Subject: [PATCH] ACPI: PM: s2idle: Use correct revision id + +AMD spec mentions only revision 0. With this change, +device constraint list is populated properly. + +Signed-off-by: Pratik Vishwakarma +Patchset: amd-s0ix +--- + drivers/acpi/x86/s2idle.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c +index 482e6b23b21a..4339e6da0dd6 100644 +--- a/drivers/acpi/x86/s2idle.c ++++ b/drivers/acpi/x86/s2idle.c +@@ -96,7 +96,7 @@ static void lpi_device_get_constraints_amd(void) + int i, j, k; + + out_obj = acpi_evaluate_dsm_typed(lps0_device_handle, &lps0_dsm_guid, +- 1, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, ++ rev_id, ACPI_LPS0_GET_DEVICE_CONSTRAINTS, + NULL, ACPI_TYPE_PACKAGE); + + if (!out_obj) +-- +2.32.0 +