diff --git a/configs/surface-5.16.config b/configs/surface-5.16.config new file mode 100644 index 000000000..cdf71cce4 --- /dev/null +++ b/configs/surface-5.16.config @@ -0,0 +1,63 @@ +# +# 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_KIP_TABLET_SWITCH=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 +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=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.16/0001-surface3-oemb.patch b/patches/5.16/0001-surface3-oemb.patch new file mode 100644 index 000000000..a9c856150 --- /dev/null +++ b/patches/5.16/0001-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 18f0bf6aa948f6a832accac643946849fb50dd61 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 09ac9cfc40d8..c626109cf445 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 197c56047947..9893e9c3cdf7 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 c60a5e8e7bc9..e947133a2c36 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.34.1 + diff --git a/patches/5.16/0002-mwifiex.patch b/patches/5.16/0002-mwifiex.patch new file mode 100644 index 000000000..00cc508cf --- /dev/null +++ b/patches/5.16/0002-mwifiex.patch @@ -0,0 +1,845 @@ +From 5737bb77fc194faa58ed104991baebe2f664635a 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 | 83 +++++++++++++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ + 3 files changed, 99 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index c3f5583ea70d..3f5138008594 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -2993,6 +2993,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 0234cf3c2974..563dd0d5ac79 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -15,10 +15,21 @@ + * this warranty disclaimer. + */ + ++#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,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + }, + .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), ++ }, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, ++ }, + {} + }; + +@@ -103,6 +122,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) +@@ -159,3 +181,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 8ec4176d698f..25370c5a4f59 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -19,5 +19,11 @@ + + #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.34.1 + +From 21894fcf806536a32a55f28bb6a92ccc17baf09e 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 563dd0d5ac79..32e2f000e57b 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -106,6 +106,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, ++ }, + {} + }; + +-- +2.34.1 + +From bc69d4ea4a67ff0daa3e28c5831b6457a13f8772 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 3f5138008594..372dde99725c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -380,6 +380,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", +@@ -421,6 +422,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 32e2f000e57b..356401bab59c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -38,7 +38,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", +@@ -47,7 +48,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)", +@@ -56,7 +58,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", +@@ -64,7 +67,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", +@@ -72,7 +76,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", +@@ -80,7 +85,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", +@@ -88,7 +94,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", +@@ -96,7 +103,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", +@@ -134,6 +142,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 25370c5a4f59..a1de111ad1db 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -23,6 +23,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.34.1 + +From 7482f937fa88afb9582341d1f35d37ff126e1bcf 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 372dde99725c..586c79dc0a98 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1781,9 +1781,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 356401bab59c..6437f067d07a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -39,7 +39,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", +@@ -49,7 +50,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)", +@@ -59,7 +61,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", +@@ -68,7 +71,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", +@@ -77,7 +81,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", +@@ -86,7 +91,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", +@@ -95,7 +101,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", +@@ -104,7 +111,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", +@@ -145,6 +153,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 a1de111ad1db..0e429779bb04 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -24,6 +24,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.34.1 + +From af7fccf6c49213ae7ce343f7b2c574918fcb0f1a 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 75c83768c257..bb863c3b51f6 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -60,6 +60,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_VALID_LE_STATES 0x800000 + #define BTUSB_QCA_WCN6855 0x1000000 + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD 0x4000000 ++#define BTUSB_LOWER_LESCAN_INTERVAL 0x8000000 + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -356,6 +357,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_COMBINED }, +@@ -3862,6 +3864,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.34.1 + +From bb6c2ece94a6eab366f7962bf2acad77f25db002 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 586c79dc0a98..f87bc9bdfba7 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -238,6 +238,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.34.1 + +From b2ccceed632550f18c346b77bee5312aebdd54f0 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 19b996c6a260..5ab2ad4c7006 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.34.1 + +From f33cf6817c7b0b130a47e664f560cd8ae8f5dcdb 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.34.1 + diff --git a/patches/5.16/0003-ath10k.patch b/patches/5.16/0003-ath10k.patch new file mode 100644 index 000000000..0bf9bafeb --- /dev/null +++ b/patches/5.16/0003-ath10k.patch @@ -0,0 +1,121 @@ +From 40c0fa8c2d8dbfea2673293256789033516822e7 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 5935e0973d14..9cbb7b245e85 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.34.1 + diff --git a/patches/5.16/0004-ipts.patch b/patches/5.16/0004-ipts.patch new file mode 100644 index 000000000..92a807c36 --- /dev/null +++ b/patches/5.16/0004-ipts.patch @@ -0,0 +1,1503 @@ +From 4cc56a3e2ebda9e7f2abb89b489c5b9497e4b371 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 67bb6a25fd0a..d1cb94d3452e 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_ICP_N 0x38E0 /* Ice Lake Point N */ + + #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 3a45aaf002ac..55b8ee30a03c 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_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.34.1 + +From f4e86071db79899b854afd5cb2647c5343d270e8 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 0f5a49fc7c9e..12b081bc875a 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -487,4 +487,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 a086197af544..972cae33ba36 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -59,3 +59,4 @@ obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o + obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.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.34.1 + diff --git a/patches/5.16/0005-surface-sam.patch b/patches/5.16/0005-surface-sam.patch new file mode 100644 index 000000000..4083813a9 --- /dev/null +++ b/patches/5.16/0005-surface-sam.patch @@ -0,0 +1,1756 @@ +From 77b2631555a42afe9faaad79e47342defd10a38e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 2 Jun 2021 03:34:06 +0200 +Subject: [PATCH] platform/surface: aggregator: Make client device removal more + generic + +Currently, there are similar functions defined in the Aggregator +Registry and the controller core. + +Make client device removal more generic and export it. We can then use +this function later on to remove client devices from device hubs as well +as the controller and avoid re-defining similar things. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/bus.c | 24 ++++++++-------------- + drivers/platform/surface/aggregator/bus.h | 3 --- + drivers/platform/surface/aggregator/core.c | 3 ++- + include/linux/surface_aggregator/device.h | 9 ++++++++ + 4 files changed, 19 insertions(+), 20 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c +index 0a40dd9c94ed..abbbb5b08b07 100644 +--- a/drivers/platform/surface/aggregator/bus.c ++++ b/drivers/platform/surface/aggregator/bus.c +@@ -374,27 +374,19 @@ static int ssam_remove_device(struct device *dev, void *_data) + } + + /** +- * ssam_controller_remove_clients() - Remove SSAM client devices registered as +- * direct children under the given controller. +- * @ctrl: The controller to remove all direct clients for. ++ * ssam_remove_clients() - Remove SSAM client devices registered as direct ++ * children under the given parent device. ++ * @dev: The (parent) device to remove all direct clients for. + * +- * Remove all SSAM client devices registered as direct children under the +- * given controller. Note that this only accounts for direct children of the +- * controller device. This does not take care of any client devices where the +- * parent device has been manually set before calling ssam_device_add. Refer +- * to ssam_device_add()/ssam_device_remove() for more details on those cases. +- * +- * To avoid new devices being added in parallel to this call, the main +- * controller lock (not statelock) must be held during this (and if +- * necessary, any subsequent deinitialization) call. ++ * Remove all SSAM client devices registered as direct children under the given ++ * device. Note that this only accounts for direct children of the device. ++ * Refer to ssam_device_add()/ssam_device_remove() for more details. + */ +-void ssam_controller_remove_clients(struct ssam_controller *ctrl) ++void ssam_remove_clients(struct device *dev) + { +- struct device *dev; +- +- dev = ssam_controller_device(ctrl); + device_for_each_child_reverse(dev, NULL, ssam_remove_device); + } ++EXPORT_SYMBOL_GPL(ssam_remove_clients); + + /** + * ssam_bus_register() - Register and set-up the SSAM client device bus. +diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h +index ed032c2cbdb2..6964ee84e79c 100644 +--- a/drivers/platform/surface/aggregator/bus.h ++++ b/drivers/platform/surface/aggregator/bus.h +@@ -12,14 +12,11 @@ + + #ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +-void ssam_controller_remove_clients(struct ssam_controller *ctrl); +- + int ssam_bus_register(void); + void ssam_bus_unregister(void); + + #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ + +-static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} + static inline int ssam_bus_register(void) { return 0; } + static inline void ssam_bus_unregister(void) {} + +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index c61bbeeec2df..d384d36098c2 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -22,6 +22,7 @@ + #include + + #include ++#include + + #include "bus.h" + #include "controller.h" +@@ -735,7 +736,7 @@ static void ssam_serial_hub_remove(struct serdev_device *serdev) + ssam_controller_lock(ctrl); + + /* Remove all client devices. */ +- ssam_controller_remove_clients(ctrl); ++ ssam_remove_clients(&serdev->dev); + + /* Act as if suspending to silence events. */ + status = ssam_ctrl_notif_display_off(ctrl); +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index f636c5310321..cc257097eb05 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -319,6 +319,15 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + ssam_device_driver_unregister) + + ++/* -- Helpers for controller and hub devices. ------------------------------- */ ++ ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++void ssam_remove_clients(struct device *dev); ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++static inline void ssam_remove_clients(struct device *dev) {} ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++ + /* -- Helpers for client-device requests. ----------------------------------- */ + + /** +-- +2.34.1 + +From fa822231a4cd0f316dd00952d39d09f4ce1ed215 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 27 Oct 2021 02:06:38 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Use generic client + removal function + +Use generic client removal function introduced in the previous commit +instead of defining our own one. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 24 ++++--------------- + 1 file changed, 5 insertions(+), 19 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index e70f4c63554e..f6c639342b9d 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -258,20 +258,6 @@ static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid) + return 0; + } + +-static int ssam_hub_remove_devices_fn(struct device *dev, void *data) +-{ +- if (!is_ssam_device(dev)) +- return 0; +- +- ssam_device_remove(to_ssam_device(dev)); +- return 0; +-} +- +-static void ssam_hub_remove_devices(struct device *parent) +-{ +- device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn); +-} +- + static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl, + struct fwnode_handle *node) + { +@@ -317,7 +303,7 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c + + return 0; + err: +- ssam_hub_remove_devices(parent); ++ ssam_remove_clients(parent); + return status; + } + +@@ -414,7 +400,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) + if (hub->state == SSAM_BASE_HUB_CONNECTED) + status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); + else +- ssam_hub_remove_devices(&hub->sdev->dev); ++ ssam_remove_clients(&hub->sdev->dev); + + if (status) + dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); +@@ -496,7 +482,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + err: + ssam_notifier_unregister(sdev->ctrl, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); +- ssam_hub_remove_devices(&sdev->dev); ++ ssam_remove_clients(&sdev->dev); + return status; + } + +@@ -508,7 +494,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) + + ssam_notifier_unregister(sdev->ctrl, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); +- ssam_hub_remove_devices(&sdev->dev); ++ ssam_remove_clients(&sdev->dev); + } + + static const struct ssam_device_id ssam_base_hub_match[] = { +@@ -625,7 +611,7 @@ static int ssam_platform_hub_remove(struct platform_device *pdev) + { + const struct software_node **nodes = platform_get_drvdata(pdev); + +- ssam_hub_remove_devices(&pdev->dev); ++ ssam_remove_clients(&pdev->dev); + set_secondary_fwnode(&pdev->dev, NULL); + software_node_unregister_node_group(nodes); + return 0; +-- +2.34.1 + +From 40bfe303124ccd4e6a99f4415605e19d8970581d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 27 Oct 2021 02:07:33 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Rename device + registration function + +Rename the device registration function to better align names with the +newly introduced device removal function. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index f6c639342b9d..ce2bd88feeaa 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -283,8 +283,8 @@ static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ct + return status; + } + +-static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl, +- struct fwnode_handle *node) ++static int ssam_hub_register_clients(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) + { + struct fwnode_handle *child; + int status; +@@ -398,7 +398,7 @@ static void ssam_base_hub_update_workfn(struct work_struct *work) + hub->state = state; + + if (hub->state == SSAM_BASE_HUB_CONNECTED) +- status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node); ++ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); + else + ssam_remove_clients(&hub->sdev->dev); + +@@ -597,7 +597,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) + + set_secondary_fwnode(&pdev->dev, root); + +- status = ssam_hub_add_devices(&pdev->dev, ctrl, root); ++ status = ssam_hub_register_clients(&pdev->dev, ctrl, root); + if (status) { + set_secondary_fwnode(&pdev->dev, NULL); + software_node_unregister_node_group(nodes); +-- +2.34.1 + +From 1c12fdba2688e863a7285498e08954988799825f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 8 Jun 2021 00:24:47 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as + hot-removed + +Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on +the Surface Pro 8, can be hot-removed. When this occurs, communication +with the device may fail and time out. This timeout can unnecessarily +block and slow down device removal and even cause issues when the +devices are detached and re-attached quickly. Thus, communication should +generally be avoided once hot-removal is detected. + +While we already remove a device as soon as we detect its (hot-)removal, +the corresponding device driver may still attempt to communicate with +the device during teardown. This is especially critical as communication +failure may also extend to disabling of events, which is typically done +at that stage. + +Add a flag to allow marking devices as hot-removed. This can then be +used during client driver teardown to check if any communication +attempts should be avoided. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/bus.c | 3 ++ + include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- + 2 files changed, 48 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c +index abbbb5b08b07..2b003dcbfc4b 100644 +--- a/drivers/platform/surface/aggregator/bus.c ++++ b/drivers/platform/surface/aggregator/bus.c +@@ -388,6 +388,9 @@ void ssam_remove_clients(struct device *dev) + } + EXPORT_SYMBOL_GPL(ssam_remove_clients); + ++ ++/* -- Bus registration. ----------------------------------------------------- */ ++ + /** + * ssam_bus_register() - Register and set-up the SSAM client device bus. + */ +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index cc257097eb05..491aa7e9f4bc 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -148,17 +148,30 @@ struct ssam_device_uid { + #define SSAM_SDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + ++/* ++ * enum ssam_device_flags - Flags for SSAM client devices. ++ * @SSAM_DEVICE_HOT_REMOVED_BIT: ++ * The device has been hot-removed. Further communication with it may time ++ * out and should be avoided. ++ */ ++enum ssam_device_flags { ++ SSAM_DEVICE_HOT_REMOVED_BIT = 0, ++}; ++ + /** + * struct ssam_device - SSAM client device. +- * @dev: Driver model representation of the device. +- * @ctrl: SSAM controller managing this device. +- * @uid: UID identifying the device. ++ * @dev: Driver model representation of the device. ++ * @ctrl: SSAM controller managing this device. ++ * @uid: UID identifying the device. ++ * @flags: Device state flags, see &enum ssam_device_flags. + */ + struct ssam_device { + struct device dev; + struct ssam_controller *ctrl; + + struct ssam_device_uid uid; ++ ++ unsigned long flags; + }; + + /** +@@ -240,6 +253,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + int ssam_device_add(struct ssam_device *sdev); + void ssam_device_remove(struct ssam_device *sdev); + ++/** ++ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. ++ * @sdev: The device to mark as hot-removed. ++ * ++ * Mark the device as having been hot-removed. This signals drivers using the ++ * device that communication with the device should be avoided and may lead to ++ * timeouts. ++ */ ++static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) ++{ ++ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); ++ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); ++} ++ ++/** ++ * ssam_device_is_hot_removed() - Check if the given device has been ++ * hot-removed. ++ * @sdev: The device to check. ++ * ++ * Checks if the given device has been marked as hot-removed. See ++ * ssam_device_mark_hot_removed() for more details. ++ * ++ * Return: Returns ``true`` if the device has been marked as hot-removed. ++ */ ++static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) ++{ ++ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); ++} ++ + /** + * ssam_device_get() - Increment reference count of SSAM client device. + * @sdev: The device to increment the reference count of. +-- +2.34.1 + +From d2a0a4cb9d997b454344fe1c28bf7868e108fbbe Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 8 Jun 2021 00:48:22 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid + communication on unregistering + +When SSAM client devices have been (physically) hot-removed, +communication attempts with those devices may fail and time out. This +can even extend to event notifiers, due to which timeouts may occur +during device removal, slowing down that process. + +Add a flag to the notifier unregister function that allows skipping +communication with the EC to prevent this. Furthermore, add wrappers for +registering and unregistering notifiers belonging to SSAM client devices +that automatically check if the device has been marked as hot-removed +and communication should be avoided. + +Note that non-SSAM client devices can generally not be hot-removed, so +also add a convenience wrapper for those, defaulting to allow +communication. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../driver-api/surface_aggregator/client.rst | 6 +- + .../platform/surface/aggregator/controller.c | 53 ++++++++++------ + include/linux/surface_aggregator/controller.h | 24 +++++++- + include/linux/surface_aggregator/device.h | 60 +++++++++++++++++++ + 4 files changed, 122 insertions(+), 21 deletions(-) + +diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst +index e519d374c378..27f95abdbe99 100644 +--- a/Documentation/driver-api/surface_aggregator/client.rst ++++ b/Documentation/driver-api/surface_aggregator/client.rst +@@ -17,6 +17,8 @@ + .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` + .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` + .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` ++.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` ++.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` + .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` + .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` + +@@ -312,7 +314,9 @@ Handling Events + To receive events from the SAM EC, an event notifier must be registered for + the desired event via |ssam_notifier_register|. The notifier must be + unregistered via |ssam_notifier_unregister| once it is not required any +-more. ++more. For |ssam_device| type clients, the |ssam_device_notifier_register| and ++|ssam_device_notifier_unregister| wrappers should be preferred as they properly ++handle hot-removal of client devices. + + Event notifiers are registered by providing (at minimum) a callback to call + in case an event has been received, the registry specifying how the event +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index b8c377b3f932..6de834b52b63 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, + } + + /** +- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is +- * no longer in use and free the corresponding entry. ++ * 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. ++ * @ec: Flag specifying if the event should actually be disabled 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. ++ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the ++ * event is no longer requested by any client), the specified event will be ++ * disabled on the EC via the corresponding request. ++ * ++ * If ``ec`` equals ``false``, no request will be sent to the EC and the event ++ * can be considered in a detached state (i.e. no longer used but still ++ * enabled). Disabling an event via this method may be required for ++ * hot-removable devices, where event disable requests may time out after the ++ * device has been physically removed. ++ * ++ * In both cases, if the reference count equals zero, the corresponding ++ * reference count entry will be 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 +@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, + * 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) ++ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) + { + const struct ssam_event_registry reg = entry->key.reg; + const struct ssam_event_id id = entry->key.id; +@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + + 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); ++ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", ++ ec ? "disabling" : "detaching", reg.target_category, id.target_category, ++ id.instance, entry->refcount); + + if (entry->flags != flags) { + ssam_warn(ctrl, +@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + id.instance); + } + +- if (entry->refcount == 0) { ++ if (ec && entry->refcount == 0) { + status = ssam_ssh_event_disable(ctrl, reg, id, flags); + kfree(entry); + } +@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif + EXPORT_SYMBOL_GPL(ssam_notifier_register); + + /** +- * ssam_notifier_unregister() - Unregister an event notifier. +- * @ctrl: The controller the notifier has been registered on. +- * @n: The event notifier to unregister. ++ * __ssam_notifier_unregister() - Unregister an event notifier. ++ * @ctrl: The controller the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * @disable: Whether to disable the corresponding event on the EC. + * + * 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. ++ * reaches zero and ``disable`` equals ``true``, the event will be disabled. ++ * ++ * Useful for hot-removable devices, where communication may fail once the ++ * device has been physically removed. In that case, specifying ``disable`` as ++ * ``false`` avoids communication with the EC. + * + * 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, ++ bool disable) + { + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; +@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + goto remove; + } + +- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); ++ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); + } + + remove: +@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + + return status; + } +-EXPORT_SYMBOL_GPL(ssam_notifier_unregister); ++EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); + + /** + * ssam_controller_event_enable() - Enable the specified event. +@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, + return -ENOENT; + } + +- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); ++ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); + + mutex_unlock(&nf->lock); + return status; +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index 74bfdffaf7b0..50a2b4926c06 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -835,8 +835,28 @@ struct ssam_event_notifier { + int ssam_notifier_register(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + +-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, bool disable); ++ ++/** ++ * ssam_notifier_unregister() - Unregister an event notifier. ++ * @ctrl: The controller the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * ++ * 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. ++ */ ++static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, ++ struct ssam_event_notifier *n) ++{ ++ return __ssam_notifier_unregister(ctrl, n, true); ++} + + int ssam_controller_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 491aa7e9f4bc..16816c34da3e 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -472,4 +472,64 @@ static inline void ssam_remove_clients(struct device *dev) {} + sdev->uid.instance, ret); \ + } + ++ ++/* -- Helpers for client-device notifiers. ---------------------------------- */ ++ ++/** ++ * ssam_device_notifier_register() - Register an event notifier for the ++ * specified client device. ++ * @sdev: The device the notifier should be registered on. ++ * @n: The event notifier to register. ++ * ++ * 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 ++ * registered, %-ENOMEM if the corresponding event entry could not be ++ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the ++ * first time that a notifier block is registered for the specific associated ++ * event, returns the status of the event-enable EC-command. ++ */ ++static inline int ssam_device_notifier_register(struct ssam_device *sdev, ++ struct ssam_event_notifier *n) ++{ ++ if (ssam_device_is_hot_removed(sdev)) ++ return -ENODEV; ++ ++ return ssam_notifier_register(sdev->ctrl, n); ++} ++ ++/** ++ * ssam_device_notifier_unregister() - Unregister an event notifier for the ++ * specified client device. ++ * @sdev: The device the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * ++ * 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. ++ * ++ * In case the device has been marked as hot-removed, the event will not be ++ * disabled on the EC, as in those cases any attempt at doing so may time out. ++ * ++ * 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. ++ */ ++static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, ++ struct ssam_event_notifier *n) ++{ ++ return __ssam_notifier_unregister(sdev->ctrl, n, ++ !ssam_device_is_hot_removed(sdev)); ++} ++ + #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ +-- +2.34.1 + +From 1fd981556e0616a6c6303d7fc0ae0f75e7d52eba Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 8 Jun 2021 01:20:49 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Use client device + wrappers for notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index ce2bd88feeaa..9f630e890ff7 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + + ssam_device_set_drvdata(sdev, hub); + +- status = ssam_notifier_register(sdev->ctrl, &hub->notif); ++ status = ssam_device_notifier_register(sdev, &hub->notif); + if (status) + return status; + +@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + return 0; + + err: +- ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); + return status; +@@ -492,7 +492,7 @@ static void ssam_base_hub_remove(struct ssam_device *sdev) + + sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group); + +- ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); + } +-- +2.34.1 + +From 40c79025534b2ea43e7561cc9295be5c1125faa6 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 28 Oct 2021 03:37:06 +0200 +Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for + notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/power/supply/surface_charger.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +index a060c36c7766..59182d55742d 100644 +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) + if (IS_ERR(ac->psy)) + return PTR_ERR(ac->psy); + +- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); ++ return ssam_device_notifier_register(ac->sdev, &ac->notif); + } + + +@@ -251,7 +251,7 @@ static void surface_ac_remove(struct ssam_device *sdev) + { + struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); + +- ssam_notifier_unregister(sdev->ctrl, &ac->notif); ++ ssam_device_notifier_unregister(sdev, &ac->notif); + } + + static const struct spwr_psy_properties spwr_psy_props_adp1 = { +-- +2.34.1 + +From f96ed98c6614de71af7aa490bcf4b6f49034b103 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 28 Oct 2021 03:38:09 +0200 +Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for + notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/power/supply/surface_battery.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c +index 5ec2e6bb2465..540707882bb0 100644 +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) + if (IS_ERR(bat->psy)) + return PTR_ERR(bat->psy); + +- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); ++ return ssam_device_notifier_register(bat->sdev, &bat->notif); + } + + +@@ -837,7 +837,7 @@ static void surface_battery_remove(struct ssam_device *sdev) + { + struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev); + +- ssam_notifier_unregister(sdev->ctrl, &bat->notif); ++ ssam_device_notifier_unregister(sdev, &bat->notif); + cancel_delayed_work_sync(&bat->update_work); + } + +-- +2.34.1 + +From dd80d11c184e44fa0c8f8174fce4faaa38655262 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 8 Jun 2021 01:33:02 +0200 +Subject: [PATCH] HID: surface-hid: Add support for hot-removal + +Add support for hot-removal of SSAM HID client devices. + +Once a device has been hot-removed, further communication with it should +be avoided as it may fail and time out. While the device will be removed +as soon as we detect hot-removal, communication may still occur during +teardown, especially when unregistering notifiers. + +While hot-removal is a surprise event that can happen any time, try to +avoid communication as much as possible once it has been detected to +prevent timeouts that can slow down device removal and cause issues, +e.g. when quickly re-attaching the device. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c +index 5571e74abe91..d2e695e942b6 100644 +--- a/drivers/hid/surface-hid/surface_hid_core.c ++++ b/drivers/hid/surface-hid/surface_hid_core.c +@@ -19,12 +19,30 @@ + #include "surface_hid_core.h" + + ++/* -- Utility functions. ---------------------------------------------------- */ ++ ++static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) ++{ ++ /* ++ * Non-ssam client devices, i.e. platform client devices, cannot be ++ * hot-removed. ++ */ ++ if (!is_ssam_device(shid->dev)) ++ return false; ++ ++ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); ++} ++ ++ + /* -- Device descriptor access. --------------------------------------------- */ + + static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) + { + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, + (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); + if (status) +@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) + { + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, + (u8 *)&shid->attrs, sizeof(shid->attrs)); + if (status) +@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) + static void surface_hid_stop(struct hid_device *hid) + { + struct surface_hid_device *shid = hid->driver_data; ++ bool hot_removed; ++ ++ /* ++ * Communication may fail for devices that have been hot-removed. This ++ * also includes unregistration of HID events, so we need to check this ++ * here. Only if the device has not been marked as hot-removed, we can ++ * safely disable events. ++ */ ++ hot_removed = surface_hid_is_hot_removed(shid); + + /* Note: This call will log errors for us, so ignore them here. */ +- ssam_notifier_unregister(shid->ctrl, &shid->notif); ++ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); + } + + static int surface_hid_open(struct hid_device *hid) +@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) + u8 *buf; + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + buf = kzalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; +@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn + { + struct surface_hid_device *shid = hid->driver_data; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) + return shid->ops.output_report(shid, reportnum, buf, len); + +-- +2.34.1 + +From f17f1c549466acbf3cff0a1f76b45185a57466e4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 31 Oct 2021 12:34:08 +0100 +Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem + category + +The KIP subsystem (full name unknown, abbreviation has been obtained +through reverse engineering) handles detachable peripherals such as the +keyboard cover on the Surface Pro X and Surface Pro 8. + +It is currently not entirely clear what this subsystem entails, but at +the very least it provides event notifications for when the keyboard +cover on the Surface Pro X and Surface Pro 8 have been detached or +re-attached, as well as the state that the keyboard cover is currently +in (e.g. folded-back, folded laptop-like, closed, etc.). + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + include/linux/surface_aggregator/serial_hub.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h +index c3de43edcffa..d1efac85caf1 100644 +--- a/include/linux/surface_aggregator/serial_hub.h ++++ b/include/linux/surface_aggregator/serial_hub.h +@@ -306,7 +306,7 @@ enum ssam_ssh_tc { + SSAM_SSH_TC_LPC = 0x0b, + SSAM_SSH_TC_TCL = 0x0c, + SSAM_SSH_TC_SFL = 0x0d, +- SSAM_SSH_TC_KIP = 0x0e, ++ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ + SSAM_SSH_TC_EXT = 0x0f, + SSAM_SSH_TC_BLD = 0x10, + SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ +-- +2.34.1 + +From 57007b8e0291afd9c728d956e4a3bce047b38ced Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 10 Oct 2021 23:56:23 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub + +Add a Surface System Aggregator Module (SSAM) client device hub for +hot-removable devices managed via the KIP subsystem. + +The KIP subsystem (full name unknown, abbreviation has been obtained +through reverse engineering) is a subsystem that manages hot-removable +SSAM client devices. Specifically, it manages HID input devices +contained in the detachable keyboard cover of the Surface Pro 8 and +Surface Pro X. + +To properly handle detachable devices, we need to remove their kernel +representation when the physical device has been detached and (re-)add +and (re-)initialize said representation when the physical device has +been (re-)attached. Note that we need to hot-remove those devices, as +communication (especially during event notifier unregistration) may time +out when the physical device is no longer present, which would lead to +an unnecessary delay. This delay might become problematic when devices +are detached and re-attached quickly. + +The KIP subsystem handles a single group of devices (e.g. all devices +contained in the keyboard cover) and cannot handle devices individually. +Thus we model it as a client device hub, which removes all devices +contained under it once removal of the hub (e.g. keyboard cover) has +been detected and (re-)adds all devices once the physical hub device has +been (re-)attached. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 247 +++++++++++++++++- + 1 file changed, 245 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 9f630e890ff7..4838ce6519a6 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -514,6 +514,237 @@ static struct ssam_device_driver ssam_base_hub_driver = { + }; + + ++/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ ++ ++/* ++ * Some devices may need a bit of time to be fully usable after being ++ * (re-)connected. This delay has been determined via experimentation. ++ */ ++#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) ++ ++#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c ++ ++enum ssam_kip_hub_state { ++ SSAM_KIP_HUB_UNINITIALIZED, ++ SSAM_KIP_HUB_CONNECTED, ++ SSAM_KIP_HUB_DISCONNECTED, ++}; ++ ++struct ssam_kip_hub { ++ struct ssam_device *sdev; ++ ++ enum ssam_kip_hub_state state; ++ struct delayed_work update_work; ++ ++ struct ssam_event_notifier notif; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { ++ .target_category = SSAM_SSH_TC_KIP, ++ .target_id = 0x01, ++ .command_id = 0x2c, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_kip_get_connection_state(struct ssam_kip_hub *hub, enum ssam_kip_hub_state *state) ++{ ++ int status; ++ u8 connected; ++ ++ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); ++ if (status < 0) { ++ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); ++ return status; ++ } ++ ++ *state = connected ? SSAM_KIP_HUB_CONNECTED : SSAM_KIP_HUB_DISCONNECTED; ++ return 0; ++} ++ ++static ssize_t ssam_kip_hub_state_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ssam_kip_hub *hub = dev_get_drvdata(dev); ++ const char *state; ++ ++ switch (hub->state) { ++ case SSAM_KIP_HUB_UNINITIALIZED: ++ state = "uninitialized"; ++ break; ++ ++ case SSAM_KIP_HUB_CONNECTED: ++ state = "connected"; ++ break; ++ ++ case SSAM_KIP_HUB_DISCONNECTED: ++ state = "disconnected"; ++ break; ++ ++ default: ++ /* ++ * Any value not handled in the above cases is invalid and ++ * should never have been set. Thus this case should be ++ * impossible to reach. ++ */ ++ WARN(1, "invalid KIP hub state: %d\n", hub->state); ++ state = ""; ++ break; ++ } ++ ++ return sysfs_emit(buf, "%s\n", state); ++} ++ ++static struct device_attribute ssam_kip_hub_attr_state = ++ __ATTR(state, 0444, ssam_kip_hub_state_show, NULL); ++ ++static struct attribute *ssam_kip_hub_attrs[] = { ++ &ssam_kip_hub_attr_state.attr, ++ NULL, ++}; ++ ++static const struct attribute_group ssam_kip_hub_group = { ++ .attrs = ssam_kip_hub_attrs, ++}; ++ ++static int ssam_kip_hub_mark_hot_removed(struct device *dev, void *_data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (is_ssam_device(dev)) ++ ssam_device_mark_hot_removed(sdev); ++ ++ return 0; ++} ++ ++static void ssam_kip_hub_update_workfn(struct work_struct *work) ++{ ++ struct ssam_kip_hub *hub = container_of(work, struct ssam_kip_hub, update_work.work); ++ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); ++ enum ssam_kip_hub_state state; ++ int status = 0; ++ ++ status = ssam_kip_get_connection_state(hub, &state); ++ if (status) ++ return; ++ ++ if (hub->state == state) ++ return; ++ hub->state = state; ++ ++ if (hub->state == SSAM_KIP_HUB_CONNECTED) ++ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); ++ else ++ ssam_remove_clients(&hub->sdev->dev); ++ ++ if (status) ++ dev_err(&hub->sdev->dev, "failed to update KIP-hub devices: %d\n", status); ++} ++ ++static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_kip_hub *hub = container_of(nf, struct ssam_kip_hub, notif); ++ unsigned long delay; ++ ++ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length < 1) { ++ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); ++ return 0; ++ } ++ ++ /* Mark devices as hot-removed before we remove any */ ++ if (!event->data[0]) ++ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_kip_hub_mark_hot_removed); ++ ++ /* ++ * Delay update when KIP devices are being connected to give devices/EC ++ * some time to set up. ++ */ ++ delay = event->data[0] ? SSAM_KIP_UPDATE_CONNECT_DELAY : 0; ++ ++ schedule_delayed_work(&hub->update_work, delay); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static int __maybe_unused ssam_kip_hub_resume(struct device *dev) ++{ ++ struct ssam_kip_hub *hub = dev_get_drvdata(dev); ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++} ++static SIMPLE_DEV_PM_OPS(ssam_kip_hub_pm_ops, NULL, ssam_kip_hub_resume); ++ ++static int ssam_kip_hub_probe(struct ssam_device *sdev) ++{ ++ struct ssam_kip_hub *hub; ++ int status; ++ ++ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); ++ if (!hub) ++ return -ENOMEM; ++ ++ hub->sdev = sdev; ++ hub->state = SSAM_KIP_HUB_UNINITIALIZED; ++ ++ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ ++ hub->notif.base.fn = ssam_kip_hub_notif; ++ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, ++ hub->notif.event.id.instance = 0, ++ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; ++ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ INIT_DELAYED_WORK(&hub->update_work, ssam_kip_hub_update_workfn); ++ ++ ssam_device_set_drvdata(sdev, hub); ++ ++ status = ssam_device_notifier_register(sdev, &hub->notif); ++ if (status) ++ return status; ++ ++ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_hub_group); ++ if (status) ++ goto err; ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++ ++err: ++ ssam_device_notifier_unregister(sdev, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); ++ ssam_remove_clients(&sdev->dev); ++ return status; ++} ++ ++static void ssam_kip_hub_remove(struct ssam_device *sdev) ++{ ++ struct ssam_kip_hub *hub = ssam_device_get_drvdata(sdev); ++ ++ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_hub_group); ++ ++ ssam_device_notifier_unregister(sdev, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); ++ ssam_remove_clients(&sdev->dev); ++} ++ ++static const struct ssam_device_id ssam_kip_hub_match[] = { ++ { SSAM_SDEV(KIP, 0x01, 0x00, 0x00) }, ++ { }, ++}; ++ ++static struct ssam_device_driver ssam_kip_hub_driver = { ++ .probe = ssam_kip_hub_probe, ++ .remove = ssam_kip_hub_remove, ++ .match_table = ssam_kip_hub_match, ++ .driver = { ++ .name = "surface_kip_hub", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_kip_hub_pm_ops, ++ }, ++}; ++ ++ + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + + static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -636,18 +867,30 @@ static int __init ssam_device_hub_init(void) + + status = platform_driver_register(&ssam_platform_hub_driver); + if (status) +- return status; ++ goto err_platform; + + status = ssam_device_driver_register(&ssam_base_hub_driver); + if (status) +- platform_driver_unregister(&ssam_platform_hub_driver); ++ goto err_base; ++ ++ status = ssam_device_driver_register(&ssam_kip_hub_driver); ++ if (status) ++ goto err_kip; + ++ return 0; ++ ++err_kip: ++ ssam_device_driver_unregister(&ssam_base_hub_driver); ++err_base: ++ platform_driver_unregister(&ssam_platform_hub_driver); ++err_platform: + return status; + } + module_init(ssam_device_hub_init); + + static void __exit ssam_device_hub_exit(void) + { ++ ssam_device_driver_unregister(&ssam_kip_hub_driver); + ssam_device_driver_unregister(&ssam_base_hub_driver); + platform_driver_unregister(&ssam_platform_hub_driver); + } +-- +2.34.1 + +From 0fb1086de9ea2da46253bc67cb32b11dc1b0506d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 27 Oct 2021 22:33:03 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + keyboard cover on Surface Pro 8 + +Add support for the detachable keyboard cover on the Surface Pro 8. + +The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers +of earlier Surface Pro generations, handled via the Surface System +Aggregator Module (SSAM). The keyboard and touchpad (as well as other +HID input devices) of this cover are standard SSAM HID client devices +(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), +however, some care needs to be taken as they can be physically detached +(similarly to the Surface Book 3). Specifically, the respective SSAM +client devices need to be removed when the keyboard cover has been +detached and (re-)initialized when the keyboard cover has been +(re-)attached. + +On the Surface Pro 8, detachment of the keyboard cover (and by extension +its devices) is managed via the KIP subsystem. Therefore, said devices +need to be registered under the KIP device hub, which in turn will +remove and re-create/re-initialize those devices as needed. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- + 1 file changed, 36 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 4838ce6519a6..c0e29c0514df 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -47,6 +47,12 @@ static const struct software_node ssam_node_hub_base = { + .parent = &ssam_node_root, + }; + ++/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ ++static const struct software_node ssam_node_hub_kip = { ++ .name = "ssam:01:0e:01:00:00", ++ .parent = &ssam_node_root, ++}; ++ + /* AC adapter. */ + static const struct software_node ssam_node_bat_ac = { + .name = "ssam:01:02:01:01:01", +@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { + .parent = &ssam_node_hub_base, + }; + ++/* HID keyboard (KIP hub). */ ++static const struct software_node ssam_node_hid_kip_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID pen stash (KIP hub; pen taken / stashed away evens). */ ++static const struct software_node ssam_node_hid_kip_penstash = { ++ .name = "ssam:01:15:02:02:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID touchpad (KIP hub). */ ++static const struct software_node ssam_node_hid_kip_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID device instance 5 (KIP hub, unknown HID device). */ ++static const struct software_node ssam_node_hid_kip_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ + /* + * Devices for 5th- and 6th-generations models: + * - Surface Book 2, +@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { + + static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_root, ++ &ssam_node_hub_kip, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, +- /* TODO: Add support for keyboard cover. */ ++ &ssam_node_hid_kip_keyboard, ++ &ssam_node_hid_kip_penstash, ++ &ssam_node_hid_kip_touchpad, ++ &ssam_node_hid_kip_iid5, ++ /* TODO: Add support for tablet mode switch. */ + NULL, + }; + +-- +2.34.1 + +From c8375a25c47b2e066db67ef20e780df974f9322a Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 8 Jun 2021 03:19:20 +0200 +Subject: [PATCH] platform/surface: Add KIP tablet-mode switch + +Add a driver providing a tablet-mode switch input device for Surface +models using the KIP subsystem to manage detachable peripherals. + +The Surface Pro 8 has a detachable keyboard cover. Unlike the keyboard +covers of previous generation Surface Pro models, this cover is fully +handled by the Surface System Aggregator Module (SSAM). The SSAM KIP +subsystem (full name unknown, abbreviation found through reverse +engineering) provides notifications for mode changes of the cover. +Specifically, it allows us to know when the cover has been folded back, +detached, or whether it is in laptop mode. + +The driver introduced with this change captures these events and +translates them to standard SW_TABLET_MODE input events. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + MAINTAINERS | 6 + + drivers/platform/surface/Kconfig | 22 ++ + drivers/platform/surface/Makefile | 1 + + .../surface/surface_kip_tablet_switch.c | 245 ++++++++++++++++++ + 4 files changed, 274 insertions(+) + create mode 100644 drivers/platform/surface/surface_kip_tablet_switch.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index dd36acc87ce6..d69fdf5aadec 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -12678,6 +12678,12 @@ L: platform-driver-x86@vger.kernel.org + S: Maintained + F: drivers/platform/surface/surface_hotplug.c + ++MICROSOFT SURFACE KIP TABLET-MODE SWITCH ++M: Maximilian Luz ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/platform/surface/surface_kip_tablet_switch.c ++ + MICROSOFT SURFACE PLATFORM PROFILE DRIVER + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index 3105f651614f..3c0ee0cdaef5 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -152,6 +152,28 @@ config SURFACE_HOTPLUG + Select M or Y here, if you want to (fully) support hot-plugging of + dGPU devices on the Surface Book 2 and/or 3 during D3cold. + ++config SURFACE_KIP_TABLET_SWITCH ++ tristate "Surface KIP Tablet-Mode Switch Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ depends on INPUT ++ help ++ Provides a tablet-mode switch input device on Microsoft Surface models ++ using the KIP subsystem for detachable keyboards (e.g. keyboard ++ covers). ++ ++ The KIP subsystem is used on newer Surface generations to handle ++ detachable input peripherals, specifically the keyboard cover ++ (containing keyboard and touchpad) on the Surface Pro 8. This module ++ provides a driver to let user-space know when the device should be ++ considered in tablet-mode due to the keyboard cover being detached or ++ folded back (essentially signaling when the keyboard is not available ++ for input). It does so by creating a tablet-mode switch input device, ++ sending the standard SW_TABLET_MODE event on mode change. ++ ++ Select M or Y here, if you want to provide tablet-mode switch input ++ events on the Surface Pro 8. ++ + config SURFACE_PLATFORM_PROFILE + tristate "Surface Platform Profile Driver" + depends on SURFACE_AGGREGATOR_REGISTRY +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 32889482de55..6d9291c993c4 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -14,5 +14,6 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o ++obj-$(CONFIG_SURFACE_KIP_TABLET_SWITCH) += surface_kip_tablet_switch.o + obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o + obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o +diff --git a/drivers/platform/surface/surface_kip_tablet_switch.c b/drivers/platform/surface/surface_kip_tablet_switch.c +new file mode 100644 +index 000000000000..458470067579 +--- /dev/null ++++ b/drivers/platform/surface/surface_kip_tablet_switch.c +@@ -0,0 +1,245 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface System Aggregator Module (SSAM) tablet mode switch via KIP ++ * subsystem. ++ * ++ * Copyright (C) 2021 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#define SSAM_EVENT_KIP_CID_LID_STATE 0x1d ++ ++enum ssam_kip_lid_state { ++ SSAM_KIP_LID_STATE_DISCONNECTED = 0x01, ++ SSAM_KIP_LID_STATE_CLOSED = 0x02, ++ SSAM_KIP_LID_STATE_LAPTOP = 0x03, ++ SSAM_KIP_LID_STATE_FOLDED_CANVAS = 0x04, ++ SSAM_KIP_LID_STATE_FOLDED_BACK = 0x05, ++}; ++ ++struct ssam_kip_sw { ++ struct ssam_device *sdev; ++ ++ enum ssam_kip_lid_state state; ++ struct work_struct update_work; ++ struct input_dev *mode_switch; ++ ++ struct ssam_event_notifier notif; ++}; ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_lid_state, u8, { ++ .target_category = SSAM_SSH_TC_KIP, ++ .target_id = 0x01, ++ .command_id = 0x1d, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_kip_get_lid_state(struct ssam_kip_sw *sw, enum ssam_kip_lid_state *state) ++{ ++ int status; ++ u8 raw; ++ ++ status = ssam_retry(__ssam_kip_get_lid_state, sw->sdev->ctrl, &raw); ++ if (status < 0) { ++ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); ++ return status; ++ } ++ ++ *state = raw; ++ return 0; ++} ++ ++static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ssam_kip_sw *sw = dev_get_drvdata(dev); ++ const char *state; ++ ++ switch (sw->state) { ++ case SSAM_KIP_LID_STATE_DISCONNECTED: ++ state = "disconnected"; ++ break; ++ ++ case SSAM_KIP_LID_STATE_CLOSED: ++ state = "closed"; ++ break; ++ ++ case SSAM_KIP_LID_STATE_LAPTOP: ++ state = "laptop"; ++ break; ++ ++ case SSAM_KIP_LID_STATE_FOLDED_CANVAS: ++ state = "folded-canvas"; ++ break; ++ ++ case SSAM_KIP_LID_STATE_FOLDED_BACK: ++ state = "folded-back"; ++ break; ++ ++ default: ++ state = ""; ++ dev_warn(dev, "unknown KIP lid state: %d\n", sw->state); ++ break; ++ } ++ ++ return sysfs_emit(buf, "%s\n", state); ++} ++static DEVICE_ATTR_RO(state); ++ ++static struct attribute *ssam_kip_sw_attrs[] = { ++ &dev_attr_state.attr, ++ NULL, ++}; ++ ++static const struct attribute_group ssam_kip_sw_group = { ++ .attrs = ssam_kip_sw_attrs, ++}; ++ ++static void ssam_kip_sw_update_workfn(struct work_struct *work) ++{ ++ struct ssam_kip_sw *sw = container_of(work, struct ssam_kip_sw, update_work); ++ enum ssam_kip_lid_state state; ++ int tablet, status; ++ ++ status = ssam_kip_get_lid_state(sw, &state); ++ if (status) ++ return; ++ ++ if (sw->state == state) ++ return; ++ sw->state = state; ++ ++ /* Send SW_TABLET_MODE event. */ ++ tablet = state != SSAM_KIP_LID_STATE_LAPTOP; ++ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); ++ input_sync(sw->mode_switch); ++} ++ ++static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_kip_sw *sw = container_of(nf, struct ssam_kip_sw, notif); ++ ++ if (event->command_id != SSAM_EVENT_KIP_CID_LID_STATE) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length < 1) { ++ dev_err(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); ++ return 0; ++ } ++ ++ schedule_work(&sw->update_work); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static int __maybe_unused ssam_kip_sw_resume(struct device *dev) ++{ ++ struct ssam_kip_sw *sw = dev_get_drvdata(dev); ++ ++ schedule_work(&sw->update_work); ++ return 0; ++} ++static SIMPLE_DEV_PM_OPS(ssam_kip_sw_pm_ops, NULL, ssam_kip_sw_resume); ++ ++static int ssam_kip_sw_probe(struct ssam_device *sdev) ++{ ++ struct ssam_kip_sw *sw; ++ int tablet, status; ++ ++ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); ++ if (!sw) ++ return -ENOMEM; ++ ++ sw->sdev = sdev; ++ INIT_WORK(&sw->update_work, ssam_kip_sw_update_workfn); ++ ++ ssam_device_set_drvdata(sdev, sw); ++ ++ /* Get initial state. */ ++ status = ssam_kip_get_lid_state(sw, &sw->state); ++ if (status) ++ return status; ++ ++ /* Set up tablet mode switch. */ ++ sw->mode_switch = devm_input_allocate_device(&sdev->dev); ++ if (!sw->mode_switch) ++ return -ENOMEM; ++ ++ sw->mode_switch->name = "Microsoft Surface KIP Tablet Mode Switch"; ++ sw->mode_switch->phys = "ssam/01:0e:01:00:01/input0"; ++ sw->mode_switch->id.bustype = BUS_HOST; ++ sw->mode_switch->dev.parent = &sdev->dev; ++ ++ tablet = sw->state != SSAM_KIP_LID_STATE_LAPTOP; ++ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); ++ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); ++ ++ status = input_register_device(sw->mode_switch); ++ if (status) ++ return status; ++ ++ /* Set up notifier. */ ++ sw->notif.base.priority = 0; ++ sw->notif.base.fn = ssam_kip_sw_notif; ++ sw->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ sw->notif.event.id.target_category = SSAM_SSH_TC_KIP, ++ sw->notif.event.id.instance = 0, ++ sw->notif.event.mask = SSAM_EVENT_MASK_TARGET; ++ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_device_notifier_register(sdev, &sw->notif); ++ if (status) ++ return status; ++ ++ status = sysfs_create_group(&sdev->dev.kobj, &ssam_kip_sw_group); ++ if (status) ++ goto err; ++ ++ /* We might have missed events during setup, so check again. */ ++ schedule_work(&sw->update_work); ++ return 0; ++ ++err: ++ ssam_device_notifier_unregister(sdev, &sw->notif); ++ cancel_work_sync(&sw->update_work); ++ return status; ++} ++ ++static void ssam_kip_sw_remove(struct ssam_device *sdev) ++{ ++ struct ssam_kip_sw *sw = ssam_device_get_drvdata(sdev); ++ ++ sysfs_remove_group(&sdev->dev.kobj, &ssam_kip_sw_group); ++ ++ ssam_device_notifier_unregister(sdev, &sw->notif); ++ cancel_work_sync(&sw->update_work); ++} ++ ++static const struct ssam_device_id ssam_kip_sw_match[] = { ++ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_kip_sw_match); ++ ++static struct ssam_device_driver ssam_kip_sw_driver = { ++ .probe = ssam_kip_sw_probe, ++ .remove = ssam_kip_sw_remove, ++ .match_table = ssam_kip_sw_match, ++ .driver = { ++ .name = "surface_kip_tablet_mode_switch", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_kip_sw_pm_ops, ++ }, ++}; ++module_ssam_device_driver(ssam_kip_sw_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using KIP subsystem"); ++MODULE_LICENSE("GPL"); +-- +2.34.1 + +From eaea62f0a964f4dab5e3c85600ef29137e96585f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 27 Oct 2021 22:33:03 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet + mode switch on Surface Pro 8 + +Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. +The respective driver for this device provides SW_TABLET_MODE input +events for user-space based on the state of the keyboard cover (e.g. +detached, folded-back, normal/laptop mode). + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index c0e29c0514df..eaf0054627a5 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { + .parent = &ssam_node_root, + }; + ++/* Tablet-mode switch via KIP subsystem. */ ++static const struct software_node ssam_node_kip_tablet_switch = { ++ .name = "ssam:01:0e:01:00:01", ++ .parent = &ssam_node_root, ++}; ++ + /* DTX / detachment-system device (Surface Book 3). */ + static const struct software_node ssam_node_bas_dtx = { + .name = "ssam:01:11:01:00:00", +@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, ++ &ssam_node_kip_tablet_switch, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_iid5, +- /* TODO: Add support for tablet mode switch. */ + NULL, + }; + +-- +2.34.1 + diff --git a/patches/5.16/0006-surface-sam-over-hid.patch b/patches/5.16/0006-surface-sam-over-hid.patch new file mode 100644 index 000000000..7c8dfb5b8 --- /dev/null +++ b/patches/5.16/0006-surface-sam-over-hid.patch @@ -0,0 +1,335 @@ +From 8b4df0e64f2a1e7d16c4f83f4097cb32d03a9ef3 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 92c1cc07ed46..3b688cea8e00 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -609,6 +609,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, +@@ -710,6 +732,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.34.1 + +From 9ad838244ac247e1a9539826526006caf07ca317 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 3c0ee0cdaef5..e5eedb85d471 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 6d9291c993c4..9eb3a7e6382c 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.34.1 + diff --git a/patches/5.16/0007-surface-gpe.patch b/patches/5.16/0007-surface-gpe.patch new file mode 100644 index 000000000..d14c8735f --- /dev/null +++ b/patches/5.16/0007-surface-gpe.patch @@ -0,0 +1,37 @@ +From dfb311f9c608f997ba613d19b0e2f6f4b248322d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 27 Oct 2021 00:56:11 +0200 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 8 + +The new Surface Pro 8 uses GPEs for lid events as well. Add an entry for +that so that the lid can be used to wake the device. Note that this is a +device with a keyboard type cover, where this acts as the "lid". + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index c1775db29efb..ec66fde28e75 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -99,6 +99,14 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4D, + }, ++ { ++ .ident = "Surface Pro 8", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 8"), ++ }, ++ .driver_data = (void *)lid_device_props_l4B, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.34.1 + diff --git a/patches/5.16/0008-surface-button.patch b/patches/5.16/0008-surface-button.patch new file mode 100644 index 000000000..9385d9a0d --- /dev/null +++ b/patches/5.16/0008-surface-button.patch @@ -0,0 +1,149 @@ +From 42397d362da66a2d1475b0d3190364b8ff3b107f Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index cb6ec59a045d..4e8944f59def 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -474,8 +474,8 @@ static const struct soc_device_data soc_device_INT33D3 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -486,31 +486,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.34.1 + +From 0496acc10a2b28d099cf4638479ce2c5cee11fb1 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 242fb690dcaf..30eea54dbb47 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.34.1 + diff --git a/patches/5.16/0009-surface-typecover.patch b/patches/5.16/0009-surface-typecover.patch new file mode 100644 index 000000000..c88bfcee4 --- /dev/null +++ b/patches/5.16/0009-surface-typecover.patch @@ -0,0 +1,233 @@ +From f5a1ba596bd95924590c5cd9eb75bf8918fe6c1d 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 082376a6cb3d..cfc2e684a22c 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, +@@ -211,6 +220,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 +@@ -386,6 +396,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 ++ }, + { } + }; + +@@ -1698,6 +1718,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; +@@ -1721,6 +1804,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); + +@@ -1750,15 +1836,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) +@@ -1810,6 +1900,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); +@@ -2177,6 +2268,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.34.1 + diff --git a/patches/5.16/0010-cameras.patch b/patches/5.16/0010-cameras.patch new file mode 100644 index 000000000..84236fc3d --- /dev/null +++ b/patches/5.16/0010-cameras.patch @@ -0,0 +1,5121 @@ +From 353f3287f282fb0deb8114b0a928f84185891eb8 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 356ea966cf8d..76fd4e6e8e46 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1966,12 +1966,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); + +@@ -2005,8 +2012,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.34.1 + +From 5d51820260e5d0a777fd5111fa5491fe0e4da450 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 d69fdf5aadec..2eeffdd634c2 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -14117,6 +14117,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 d6a5d4ca439a..8761a90a7a86 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -1058,6 +1058,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 4d4fe08d7a6a..b01f6cd05ee8 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..9499ee10f56c +--- /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_state *state, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_format(&ov5693->sd, state, 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_state *state, ++ unsigned int pad, enum v4l2_subdev_format_whence which) ++{ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ return v4l2_subdev_get_try_crop(&ov5693->sd, state, pad); ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ return &ov5693->mode.crop; ++ } ++ ++ return NULL; ++} ++ ++static int ov5693_get_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ 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_state *state, ++ 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, state, 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, state, 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_state *state, ++ 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, state, 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_state *state, ++ 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, state, 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, state, 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_state *state, ++ 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_state *state, ++ 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, state, 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.34.1 + +From e5a5d1e45e7c029ac1fa3c37a0c2cb65137b7b11 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 9499ee10f56c..c558f9b48c83 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.34.1 + +From 0006fe98c53fd00ecd373057822f222c37eebca5 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 ce50f3ea87b8..7626c8608f8f 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -9,6 +9,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -2946,6 +2947,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" }, + { } +@@ -2956,6 +2963,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.34.1 + +From 402b2099a17351fc300d2a0453282df6b76fd3d8 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. + +Reviewed-by: Paul Kocialkowski +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 7626c8608f8f..8e3f8a554452 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.34.1 + +From 0e74b1496706ee4e06dd073bf5cbcf850e0db8b6 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sat, 10 Jul 2021 22:21:52 +0100 +Subject: [PATCH] media: i2c: Defer probe if not endpoint found + +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. Return -EPROBE_DEFER if no +endpoint is found rather than a fatal error. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 8e3f8a554452..9bc8d5d8199b 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2796,10 +2796,8 @@ 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; +- } ++ if (!handle) ++ return -EPROBE_DEFER; + + sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; + +-- +2.34.1 + +From 3283a3bd720e70e376d1c1323240239969096883 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 | 186 +++++++++++++++++++++++++++---------- + 1 file changed, 135 insertions(+), 51 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 9bc8d5d8199b..4ddc1b277cc0 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 */ +@@ -567,6 +563,25 @@ struct ov8865_sclk_config { + unsigned int sclk_div; + }; + ++struct ov8865_pll_configs { ++ const struct ov8865_pll1_config *pll1_config; ++ const struct ov8865_pll2_config *pll2_config_native; ++ const struct ov8865_pll2_config *pll2_config_binning; ++}; ++ ++/* Clock rate */ ++ ++enum extclk_rate { ++ OV8865_19_2_MHZ, ++ OV8865_24_MHZ, ++ OV8865_NUM_SUPPORTED_RATES ++}; ++ ++static const unsigned long supported_extclk_rates[] = { ++ [OV8865_19_2_MHZ] = 19200000, ++ [OV8865_24_MHZ] = 24000000, ++}; ++ + /* + * General formulas for (array-centered) mode calculation: + * - photo_array_width = 3296 +@@ -635,9 +650,7 @@ struct ov8865_mode { + + struct v4l2_fract frame_interval; + +- const struct ov8865_pll1_config *pll1_config; +- const struct ov8865_pll2_config *pll2_config; +- const struct ov8865_sclk_config *sclk_config; ++ bool pll2_binning; + + const struct ov8865_register_value *register_values; + unsigned int register_values_count; +@@ -665,6 +678,9 @@ struct ov8865_sensor { + struct regulator *avdd; + struct regulator *dvdd; + struct regulator *dovdd; ++ ++ unsigned long extclk_rate; ++ const struct ov8865_pll_configs *pll_configs; + struct clk *extclk; + + struct v4l2_fwnode_endpoint endpoint; +@@ -680,43 +696,70 @@ 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_config_native_19_2mhz = { ++ .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, ++}; ++ ++static const struct ov8865_pll1_config ov8865_pll1_config_native_24mhz = { ++ .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_config_native_19_2mhz = { ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 5, ++ .pll_mul = 75, ++ .dac_div = 1, ++ .sys_pre_div = 1, ++ .sys_div = 3, ++}; ++ ++static const struct ov8865_pll2_config ov8865_pll2_config_native_24mhz = { ++ .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_config_binning_19_2mhz = { ++ .pll_pre_div_half = 1, ++ .pll_pre_div = 2, ++ .pll_mul = 75, ++ .dac_div = 2, ++ .sys_pre_div = 10, ++ .sys_div = 0, ++}; ++ ++static const struct ov8865_pll2_config ov8865_pll2_config_binning_24mhz = { + .pll_pre_div_half = 1, + .pll_pre_div = 0, + .pll_mul = 30, +@@ -725,6 +768,23 @@ static const struct ov8865_pll2_config ov8865_pll2_config_binning = { + .sys_div = 0, + }; + ++static struct ov8865_pll_configs ov8865_pll_configs_19_2mhz = { ++ .pll1_config = &ov8865_pll1_config_native_19_2mhz, ++ .pll2_config_native = &ov8865_pll2_config_native_19_2mhz, ++ .pll2_config_binning = &ov8865_pll2_config_binning_19_2mhz, ++}; ++ ++static struct ov8865_pll_configs ov8865_pll_configs_24mhz = { ++ .pll1_config = &ov8865_pll1_config_native_24mhz, ++ .pll2_config_native = &ov8865_pll2_config_native_24mhz, ++ .pll2_config_binning = &ov8865_pll2_config_binning_24mhz, ++}; ++ ++static const struct ov8865_pll_configs *ov8865_pll_configs[] = { ++ &ov8865_pll_configs_19_2mhz, ++ &ov8865_pll_configs_24mhz, ++}; ++ + static const struct ov8865_sclk_config ov8865_sclk_config_native = { + .sys_sel = 1, + .sclk_sel = 0, +@@ -934,9 +994,7 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_native, +- .sclk_config = &ov8865_sclk_config_native, ++ .pll2_binning = false, + + /* Registers */ + .register_values = ov8865_register_values_native, +@@ -990,9 +1048,7 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_native, +- .sclk_config = &ov8865_sclk_config_native, ++ .pll2_binning = false, + + /* Registers */ + .register_values = ov8865_register_values_native, +@@ -1050,9 +1106,7 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 30 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_binning, +- .sclk_config = &ov8865_sclk_config_native, ++ .pll2_binning = true, + + /* Registers */ + .register_values = ov8865_register_values_binning, +@@ -1116,9 +1170,7 @@ static const struct ov8865_mode ov8865_modes[] = { + .frame_interval = { 1, 90 }, + + /* PLL */ +- .pll1_config = &ov8865_pll1_config_native, +- .pll2_config = &ov8865_pll2_config_binning, +- .sclk_config = &ov8865_sclk_config_native, ++ .pll2_binning = true, + + /* Registers */ + .register_values = ov8865_register_values_binning, +@@ -1513,12 +1565,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 = sensor->pll_configs->pll1_config; ++ pll1_rate = sensor->extclk_rate * config->pll_mul / config->pll_pre_div_half; + + switch (config->pll_pre_div) { + case 0: +@@ -1552,10 +1603,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 = sensor->pll_configs->pll1_config; ++ + switch (mbus_code) { + case MEDIA_BUS_FMT_SBGGR10_1X10: + value = OV8865_MIPI_BIT_SEL(10); +@@ -1622,9 +1675,12 @@ 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_binning ? sensor->pll_configs->pll2_config_binning : ++ sensor->pll_configs->pll2_config_native; ++ + 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)); +@@ -1658,7 +1714,7 @@ static int ov8865_mode_pll2_configure(struct ov8865_sensor *sensor, + static int ov8865_mode_sclk_configure(struct ov8865_sensor *sensor, + const struct ov8865_mode *mode) + { +- const struct ov8865_sclk_config *config = mode->sclk_config; ++ const struct ov8865_sclk_config *config = &ov8865_sclk_config_native; + int ret; + + ret = ov8865_write(sensor, OV8865_CLK_SEL0_REG, +@@ -2053,9 +2109,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 = sensor->pll_configs->pll1_config; ++ + pll1_rate = ov8865_mode_pll1_rate(sensor, mode); + + return pll1_rate / config->m_div / 2; +@@ -2783,7 +2841,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); +@@ -2858,13 +2917,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 the 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(dev_fwnode(dev), "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->pll_configs = ov8865_pll_configs[i]; ++ + /* Subdev, entity and pad */ + + subdev = &sensor->subdev; +-- +2.34.1 + +From 2397116765c8feae2aa35ae67cb49f8279cc921a 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 driver's v4l2_subdev_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 | 64 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 64 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 4ddc1b277cc0..0f2776390a8e 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) \ +@@ -2756,12 +2765,67 @@ 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_state *state, unsigned int pad, ++ enum v4l2_subdev_format_whence which, struct v4l2_rect *r) ++{ ++ const struct ov8865_mode *mode = sensor->state.mode; ++ ++ switch (which) { ++ case V4L2_SUBDEV_FORMAT_TRY: ++ *r = *v4l2_subdev_get_try_crop(&sensor->subdev, state, pad); ++ break; ++ case V4L2_SUBDEV_FORMAT_ACTIVE: ++ r->height = mode->output_size_y; ++ r->width = mode->output_size_x; ++ r->top = (OV8865_NATIVE_HEIGHT - mode->output_size_y) / 2; ++ r->left = (OV8865_NATIVE_WIDTH - mode->output_size_x) / 2; ++ break; ++ } ++} ++ ++static int ov8865_get_selection(struct v4l2_subdev *subdev, ++ struct v4l2_subdev_state *state, ++ 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, state, 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, ++ .set_selection = ov8865_get_selection, + }; + + static const struct v4l2_subdev_ops ov8865_subdev_ops = { +-- +2.34.1 + +From 1ba730c1776aadbb35ef3cffcaa5471e7a9578c5 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. + +Reviewed-by: Paul Kocialkowski +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 0f2776390a8e..a832938c33b6 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2150,7 +2150,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; + +@@ -2460,8 +2460,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; +@@ -2506,7 +2506,8 @@ 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.34.1 + +From 62289693d7fab63920cd67449f81d6270babb248 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 a832938c33b6..f741c0713ca4 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 +@@ -675,6 +677,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; + }; +@@ -2225,6 +2228,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, +@@ -2476,6 +2493,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; + } +@@ -2492,6 +2511,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); +@@ -2528,6 +2549,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 = +@@ -2708,6 +2736,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); + +@@ -3035,6 +3067,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.34.1 + +From 476dcf4df3b82737854f7e941deba7e485f67aca 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 f741c0713ca4..4b18cc80f985 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -677,6 +677,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; +@@ -2513,6 +2514,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); +@@ -2550,6 +2552,13 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + 0, 0, ov8865_test_pattern_menu); + + /* Blanking */ ++ hblank = 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, +@@ -2696,6 +2705,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; + +@@ -2740,6 +2750,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; ++ __v4l2_ctrl_modify_range(sensor->ctrls.hblank, hblank, hblank, 1, ++ hblank); ++ + complete: + mutex_unlock(&sensor->mutex); + +-- +2.34.1 + +From bcb51e4d7e546e03ffa8b90abeb60d50815fa392 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 20 Oct 2021 22:43:54 +0100 +Subject: [PATCH] media: i2c: Update HTS values in ov8865 + +The HTS values for some of the modes in the ov8865 driver are a bit +unusual, coming in lower than the output_size_x is set to. It seems +like they might be calculated to fit the desired framerate into a +configuration with just two data lanes. To bring this more in line +with expected behaviour, raise the HTS values above the output_size_x. + +The corollary of that change is that the hardcoded frame intervals +against the modes no longer make sense, so remove those entirely. +Update the .g/s_frame_interval() callbacks to calculate the frame +interval based on the current mode and the vblank and hblank settings +plus the number of data lanes detected from firmware. + +The implementation of the .enum_frame_interval() callback is no longer +suitable since the possible frame rate is now a continuous range depending +on the vblank control setting, so remove that callback entirely. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 65 +++++++------------------------------- + 1 file changed, 11 insertions(+), 54 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 4b18cc80f985..1b8674152750 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -659,8 +659,6 @@ struct ov8865_mode { + unsigned int blc_anchor_right_start; + unsigned int blc_anchor_right_end; + +- struct v4l2_fract frame_interval; +- + bool pll2_binning; + + const struct ov8865_register_value *register_values; +@@ -964,7 +962,7 @@ static const struct ov8865_mode ov8865_modes[] = { + { + /* Horizontal */ + .output_size_x = 3264, +- .hts = 1944, ++ .hts = 3888, + + /* Vertical */ + .output_size_y = 2448, +@@ -1003,9 +1001,6 @@ static const struct ov8865_mode ov8865_modes[] = { + .blc_anchor_right_start = 1984, + .blc_anchor_right_end = 2239, + +- /* Frame Interval */ +- .frame_interval = { 1, 30 }, +- + /* PLL */ + .pll2_binning = false, + +@@ -1018,11 +1013,11 @@ static const struct ov8865_mode ov8865_modes[] = { + { + /* Horizontal */ + .output_size_x = 3264, +- .hts = 2582, ++ .hts = 3888, + + /* Vertical */ + .output_size_y = 1836, +- .vts = 2002, ++ .vts = 2470, + + .size_auto = true, + .size_auto_boundary_x = 8, +@@ -1057,9 +1052,6 @@ static const struct ov8865_mode ov8865_modes[] = { + .blc_anchor_right_start = 1984, + .blc_anchor_right_end = 2239, + +- /* Frame Interval */ +- .frame_interval = { 1, 30 }, +- + /* PLL */ + .pll2_binning = false, + +@@ -1115,9 +1107,6 @@ static const struct ov8865_mode ov8865_modes[] = { + .blc_anchor_right_start = 992, + .blc_anchor_right_end = 1119, + +- /* Frame Interval */ +- .frame_interval = { 1, 30 }, +- + /* PLL */ + .pll2_binning = true, + +@@ -1179,9 +1168,6 @@ static const struct ov8865_mode ov8865_modes[] = { + .blc_anchor_right_start = 992, + .blc_anchor_right_end = 1119, + +- /* Frame Interval */ +- .frame_interval = { 1, 90 }, +- + /* PLL */ + .pll2_binning = true, + +@@ -2628,11 +2614,18 @@ static int ov8865_g_frame_interval(struct v4l2_subdev *subdev, + { + struct ov8865_sensor *sensor = ov8865_subdev_sensor(subdev); + const struct ov8865_mode *mode; ++ unsigned int framesize; ++ unsigned int fps; + + mutex_lock(&sensor->mutex); + + mode = sensor->state.mode; +- interval->interval = mode->frame_interval; ++ framesize = mode->hts * (mode->output_size_y + ++ sensor->ctrls.vblank->val); ++ fps = DIV_ROUND_CLOSEST(sensor->ctrls.pixel_rate->val, framesize); ++ ++ interval->interval.numerator = 1; ++ interval->interval.denominator = fps; + + mutex_unlock(&sensor->mutex); + +@@ -2777,41 +2770,6 @@ static int ov8865_enum_frame_size(struct v4l2_subdev *subdev, + return 0; + } + +-static int ov8865_enum_frame_interval(struct v4l2_subdev *subdev, +- struct v4l2_subdev_state *sd_state, +- struct v4l2_subdev_frame_interval_enum *interval_enum) +-{ +- const struct ov8865_mode *mode = NULL; +- unsigned int mode_index; +- unsigned int interval_index; +- +- if (interval_enum->index > 0) +- return -EINVAL; +- /* +- * Multiple modes with the same dimensions may have different frame +- * intervals, so look up each relevant mode. +- */ +- for (mode_index = 0, interval_index = 0; +- mode_index < ARRAY_SIZE(ov8865_modes); mode_index++) { +- mode = &ov8865_modes[mode_index]; +- +- if (mode->output_size_x == interval_enum->width && +- mode->output_size_y == interval_enum->height) { +- if (interval_index == interval_enum->index) +- break; +- +- interval_index++; +- } +- } +- +- if (mode_index == ARRAY_SIZE(ov8865_modes)) +- return -EINVAL; +- +- interval_enum->interval = mode->frame_interval; +- +- return 0; +-} +- + static void + __ov8865_get_pad_crop(struct ov8865_sensor *sensor, + struct v4l2_subdev_state *state, unsigned int pad, +@@ -2870,7 +2828,6 @@ static const struct v4l2_subdev_pad_ops ov8865_subdev_pad_ops = { + .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, + .set_selection = ov8865_get_selection, + }; +-- +2.34.1 + +From 2f7d7653db59ffd05403a164ac41638da582f3f3 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 | 26 ++++++++++++++++++++++++-- + 1 file changed, 24 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 1b8674152750..99548ad15dcd 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -677,6 +677,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; + }; +@@ -2454,6 +2455,19 @@ 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; +@@ -2510,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 */ + +@@ -2700,6 +2714,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); +@@ -2747,6 +2762,13 @@ 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.34.1 + +From 8bbf3a86534f5fa56196683084d68cb65e087571 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_CAMERA_ORIENTATION and V4L2_CID_CAMERA_SENSOR_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 99548ad15dcd..dfb5095ef16b 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2513,6 +2513,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; +@@ -2576,6 +2577,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.34.1 + +From d5b0072bb2bf01b521bfcf0fd753afdbb9faf773 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 dfb5095ef16b..5f19d82554df 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2125,6 +2125,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) +@@ -2525,8 +2528,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.34.1 + +From 356aa1b9970681877ec41c3070864b458312fb76 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 24 Aug 2021 22:39:02 +0100 +Subject: [PATCH] media: i2c: Re-order runtime pm initialisation + +The kerneldoc for pm_runtime_set_suspended() says: + +"It is not valid to call this function for devices with runtime PM +enabled" + +To satisfy that requirement, re-order the calls so that +pm_runtime_enable() is the last one. + +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 5f19d82554df..18b5f1e8e9a7 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -3085,8 +3085,8 @@ static int ov8865_probe(struct i2c_client *client) + + /* Runtime PM */ + +- pm_runtime_enable(sensor->dev); + pm_runtime_set_suspended(sensor->dev); ++ pm_runtime_enable(sensor->dev); + + /* V4L2 subdev register */ + +-- +2.34.1 + +From 0cf665f87ff80019f95aabe51c74e8c52d88dfbf Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 24 Aug 2021 23:17:39 +0100 +Subject: [PATCH] media: i2c: Use dev_err_probe() in ov8865 + +There is a chance that regulator_get() returns -EPROBE_DEFER, in which +case printing an error message is undesirable. To avoid spurious messages +in dmesg in the event that -EPROBE_DEFER is returned, use dev_err_probe() +on error paths for regulator_get(). + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 46 +++++++++++++++++--------------------- + 1 file changed, 20 insertions(+), 26 deletions(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 18b5f1e8e9a7..19e6bebf340d 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2955,6 +2955,26 @@ static int ov8865_probe(struct i2c_client *client) + sensor->dev = dev; + sensor->i2c_client = client; + ++ /* Regulators */ ++ ++ /* DVDD: digital core */ ++ sensor->dvdd = devm_regulator_get(dev, "dvdd"); ++ if (IS_ERR(sensor->dvdd)) ++ return dev_err_probe(dev, PTR_ERR(sensor->dvdd), ++ "cannot get DVDD regulator\n"); ++ ++ /* DOVDD: digital I/O */ ++ sensor->dovdd = devm_regulator_get(dev, "dovdd"); ++ if (IS_ERR(sensor->dovdd)) ++ return dev_err_probe(dev, PTR_ERR(sensor->dovdd), ++ "cannot get DOVDD regulator\n"); ++ ++ /* AVDD: analog */ ++ sensor->avdd = devm_regulator_get(dev, "avdd"); ++ if (IS_ERR(sensor->avdd)) ++ return dev_err_probe(dev, PTR_ERR(sensor->avdd), ++ "cannot get AVDD regulator\n"); ++ + /* Graph Endpoint */ + + handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); +@@ -2985,32 +3005,6 @@ static int ov8865_probe(struct i2c_client *client) + goto error_endpoint; + } + +- /* Regulators */ +- +- /* DVDD: digital core */ +- sensor->dvdd = devm_regulator_get(dev, "dvdd"); +- if (IS_ERR(sensor->dvdd)) { +- dev_err(dev, "cannot get DVDD (digital core) regulator\n"); +- ret = PTR_ERR(sensor->dvdd); +- goto error_endpoint; +- } +- +- /* DOVDD: digital I/O */ +- sensor->dovdd = devm_regulator_get(dev, "dovdd"); +- if (IS_ERR(sensor->dovdd)) { +- dev_err(dev, "cannot get DOVDD (digital I/O) regulator\n"); +- ret = PTR_ERR(sensor->dovdd); +- goto error_endpoint; +- } +- +- /* AVDD: analog */ +- sensor->avdd = devm_regulator_get(dev, "avdd"); +- if (IS_ERR(sensor->avdd)) { +- dev_err(dev, "cannot get AVDD (analog) regulator\n"); +- ret = PTR_ERR(sensor->avdd); +- goto error_endpoint; +- } +- + /* External Clock */ + + sensor->extclk = devm_clk_get(dev, NULL); +-- +2.34.1 + +From 0fc879df264184696fad72f153d179c55cfa81fe 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. + +Reviewed-by: Andy Shevchenko +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 67c467d3c81f..0c1c5d8d8dfd 100644 +--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c ++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c +@@ -22,6 +22,8 @@ + static const struct cio2_sensor_config cio2_supported_sensors[] = { + /* Omnivision OV5693 */ + CIO2_SENSOR_CONFIG("INT33BE", 0), ++ /* Omnivision OV8865 */ ++ CIO2_SENSOR_CONFIG("INT347A", 1, 360000000), + /* Omnivision OV2680 */ + CIO2_SENSOR_CONFIG("OVTI2680", 0), + }; +-- +2.34.1 + +From 68ba64c731b9df3d9c0d76ab4c8d9c06e003cee3 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Thu, 7 Oct 2021 15:34:52 +0200 +Subject: [PATCH] media: i2c: ov8865: Fix lockdep error + +ov8865_state_init() calls ov8865_state_mipi_configure() which uses +__v4l2_ctrl_s_ctrl[_int64](). This means that sensor->mutex (which +is also sensor->ctrls.handler.lock) must be locked before calling +ov8865_state_init(). + +Note ov8865_state_mipi_configure() is also used in other places where +the lock is already held so it cannot be changed itself. + +This fixes the following lockdep kernel WARN: + +[ 13.233413] ------------[ cut here ]------------ +[ 13.233421] WARNING: CPU: 0 PID: 8 at drivers/media/v4l2-core/v4l2-ctrls-api.c:833 __v4l2_ctrl_s_ctrl+0x4d/0x60 [videodev] +... +[ 13.234063] Call Trace: +[ 13.234074] ov8865_state_configure+0x98b/0xc00 [ov8865] +[ 13.234095] ov8865_probe+0x4b1/0x54c [ov8865] +[ 13.234117] i2c_device_probe+0x13c/0x2d0 + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index 19e6bebf340d..d5af8aedf5e8 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -3073,7 +3073,9 @@ static int ov8865_probe(struct i2c_client *client) + if (ret) + goto error_mutex; + ++ mutex_lock(&sensor->mutex); + ret = ov8865_state_init(sensor); ++ mutex_unlock(&sensor->mutex); + if (ret) + goto error_ctrls; + +-- +2.34.1 + +From ad795bc149f99e991391abcbe26a578f4b4b50dd Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 36 ++++++++++++++++++++++++++++++++++-- + include/acpi/acpi_bus.h | 5 ++++- + 2 files changed, 38 insertions(+), 3 deletions(-) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 2c80765670bc..6385024170ac 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -797,6 +797,12 @@ static const char * const acpi_ignore_dep_ids[] = { + NULL + }; + ++/* List of HIDs for which we honor deps of matching ACPI devs, when checking _DEP lists. */ ++static const char * const acpi_honor_dep_ids[] = { ++ "INT3472", /* Camera sensor PMIC / clk and regulator info */ ++ NULL ++}; ++ + static struct acpi_device *acpi_bus_get_parent(acpi_handle handle) + { + struct acpi_device *device = NULL; +@@ -1762,8 +1768,12 @@ static void acpi_scan_dep_init(struct acpi_device *adev) + struct acpi_dep_data *dep; + + list_for_each_entry(dep, &acpi_dep_list, node) { +- if (dep->consumer == adev->handle) ++ if (dep->consumer == adev->handle) { ++ if (dep->honor_dep) ++ adev->flags.honor_deps = 1; ++ + adev->dep_unmet++; ++ } + } + } + +@@ -1967,7 +1977,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) + for (count = 0, i = 0; i < dep_devices.count; i++) { + struct acpi_device_info *info; + struct acpi_dep_data *dep; +- bool skip; ++ bool skip, honor_dep; + + status = acpi_get_object_info(dep_devices.handles[i], &info); + if (ACPI_FAILURE(status)) { +@@ -1976,6 +1986,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) + } + + skip = acpi_info_matches_ids(info, acpi_ignore_dep_ids); ++ honor_dep = acpi_info_matches_ids(info, acpi_honor_dep_ids); + kfree(info); + + if (skip) +@@ -1989,6 +2000,7 @@ static u32 acpi_scan_check_dep(acpi_handle handle, bool check_dep) + + dep->supplier = dep_devices.handles[i]; + dep->consumer = handle; ++ dep->honor_dep = honor_dep; + + mutex_lock(&acpi_dep_list_lock); + list_add_tail(&dep->node , &acpi_dep_list); +@@ -2076,6 +2088,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +@@ -2318,6 +2333,23 @@ void acpi_dev_clear_dependencies(struct acpi_device *supplier) + } + EXPORT_SYMBOL_GPL(acpi_dev_clear_dependencies); + ++/** ++ * acpi_dev_ready_for_enumeration - Check if the ACPI device is ready for enumeration ++ * @device: Pointer to the &struct acpi_device to check ++ * ++ * Check if the device is present and has no unmet dependencies. ++ * ++ * Return true if the device is ready for enumeratino. Otherwise, return false. ++ */ ++bool acpi_dev_ready_for_enumeration(const struct acpi_device *device) ++{ ++ if (device->flags.honor_deps && device->dep_unmet) ++ return false; ++ ++ return acpi_device_is_present(device); ++} ++EXPORT_SYMBOL_GPL(acpi_dev_ready_for_enumeration); ++ + /** + * acpi_dev_get_first_consumer_dev - Return ACPI device dependent on @supplier + * @supplier: Pointer to the dependee device +diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h +index 480f9207a4c6..2f93ecf05dac 100644 +--- a/include/acpi/acpi_bus.h ++++ b/include/acpi/acpi_bus.h +@@ -202,7 +202,8 @@ struct acpi_device_flags { + u32 coherent_dma:1; + u32 cca_seen:1; + u32 enumeration_by_parent:1; +- u32 reserved:19; ++ u32 honor_deps:1; ++ u32 reserved:18; + }; + + /* File System */ +@@ -285,6 +286,7 @@ struct acpi_dep_data { + struct list_head node; + acpi_handle supplier; + acpi_handle consumer; ++ bool honor_dep; + }; + + /* Performance Management */ +@@ -693,6 +695,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); ++bool acpi_dev_ready_for_enumeration(const struct acpi_device *device); + 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); +-- +2.34.1 + +From 6dbc7e9bbbc1aff5f7e16f2bb5f8e22b72dfc00c Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:58 +0200 +Subject: [PATCH] i2c: acpi: Use acpi_dev_ready_for_enumeration() helper + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +To ensure the correct probe-ordering the ACPI core has code to defer the +enumeration of consumers affected by this until the providers are ready. + +Call the new acpi_dev_ready_for_enumeration() helper to avoid +enumerating / instantiating i2c-clients too early. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/i2c/i2c-core-acpi.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index 3b688cea8e00..0542d8aba902 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -144,9 +144,12 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, + struct list_head resource_list; + int ret; + +- if (acpi_bus_get_status(adev) || !adev->status.present) ++ if (acpi_bus_get_status(adev)) + return -EINVAL; + ++ if (!acpi_dev_ready_for_enumeration(adev)) ++ return -ENODEV; ++ + if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) + return -ENODEV; + +-- +2.34.1 + +From 9b4c9d5d362e1624b8d9f5ecf49486257c70453b Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:59 +0200 +Subject: [PATCH] platform_data: Add linux/platform_data/tps68470.h file + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the provider-device +during probe/registration of the provider device. + +The TI TPS68470 PMIC is used x86/ACPI devices with the consumer-info +missing from the ACPI tables. Thus the tps68470-clk and tps68470-regulator +drivers must provide the consumer-info at probe time. + +Define tps68470_clk_platform_data and tps68470_regulator_platform_data +structs to allow the x86 platform code to pass the necessary consumer info +to these drivers. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + include/linux/platform_data/tps68470.h | 35 ++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + create mode 100644 include/linux/platform_data/tps68470.h + +diff --git a/include/linux/platform_data/tps68470.h b/include/linux/platform_data/tps68470.h +new file mode 100644 +index 000000000000..126d082c3f2e +--- /dev/null ++++ b/include/linux/platform_data/tps68470.h +@@ -0,0 +1,35 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * TI TPS68470 PMIC platform data definition. ++ * ++ * Copyright (c) 2021 Red Hat Inc. ++ * ++ * Red Hat authors: ++ * Hans de Goede ++ */ ++#ifndef __PDATA_TPS68470_H ++#define __PDATA_TPS68470_H ++ ++enum tps68470_regulators { ++ TPS68470_CORE, ++ TPS68470_ANA, ++ TPS68470_VCM, ++ TPS68470_VIO, ++ TPS68470_VSIO, ++ TPS68470_AUX1, ++ TPS68470_AUX2, ++ TPS68470_NUM_REGULATORS ++}; ++ ++struct regulator_init_data; ++ ++struct tps68470_regulator_platform_data { ++ const struct regulator_init_data *reg_init_data[TPS68470_NUM_REGULATORS]; ++}; ++ ++struct tps68470_clk_platform_data { ++ const char *consumer_dev_name; ++ const char *consumer_con_id; ++}; ++ ++#endif +-- +2.34.1 + +From 190e1d22b43f4e5300ad28739c4aa0709f33860c Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:00 +0200 +Subject: [PATCH] regulator: Introduce tps68470-regulator driver + +The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in +the kernel the Regulators and Clocks are controlled by an OpRegion +driver designed to work with power control methods defined in ACPI, but +some platforms lack those methods, meaning drivers need to be able to +consume the resources of these chips through the usual frameworks. + +This commit adds a driver for the regulators provided by the tps68470, +and is designed to bind to the platform_device registered by the +intel_skl_int3472 module. + +This is based on this out of tree driver written by Intel: +https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/regulator/tps68470-regulator.c +with various cleanups added. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/regulator/Kconfig | 9 ++ + drivers/regulator/Makefile | 1 + + drivers/regulator/tps68470-regulator.c | 193 +++++++++++++++++++++++++ + 3 files changed, 203 insertions(+) + create mode 100644 drivers/regulator/tps68470-regulator.c + +diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig +index 6be9b1c8a615..25e3acb378e3 100644 +--- a/drivers/regulator/Kconfig ++++ b/drivers/regulator/Kconfig +@@ -1339,6 +1339,15 @@ config REGULATOR_TPS65912 + help + This driver supports TPS65912 voltage regulator chip. + ++config REGULATOR_TPS68470 ++ tristate "TI TPS68370 PMIC Regulators Driver" ++ depends on INTEL_SKL_INT3472 ++ help ++ This driver adds support for the TPS68470 PMIC to register ++ regulators against the usual framework. ++ ++ The module will be called "tps68470-regulator" ++ + config REGULATOR_TWL4030 + tristate "TI TWL4030/TWL5030/TWL6030/TPS659x0 PMIC" + depends on TWL4030_CORE +diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile +index b07d2a22df0b..0ea7e6adc267 100644 +--- a/drivers/regulator/Makefile ++++ b/drivers/regulator/Makefile +@@ -158,6 +158,7 @@ obj-$(CONFIG_REGULATOR_TPS6524X) += tps6524x-regulator.o + obj-$(CONFIG_REGULATOR_TPS6586X) += tps6586x-regulator.o + obj-$(CONFIG_REGULATOR_TPS65910) += tps65910-regulator.o + obj-$(CONFIG_REGULATOR_TPS65912) += tps65912-regulator.o ++obj-$(CONFIG_REGULATOR_TPS68470) += tps68470-regulator.o + obj-$(CONFIG_REGULATOR_TPS65132) += tps65132-regulator.o + obj-$(CONFIG_REGULATOR_TWL4030) += twl-regulator.o twl6030-regulator.o + obj-$(CONFIG_REGULATOR_UNIPHIER) += uniphier-regulator.o +diff --git a/drivers/regulator/tps68470-regulator.c b/drivers/regulator/tps68470-regulator.c +new file mode 100644 +index 000000000000..3129fa13a122 +--- /dev/null ++++ b/drivers/regulator/tps68470-regulator.c +@@ -0,0 +1,193 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Regulator driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2018 Intel Corporation ++ * ++ * Authors: ++ * Zaikuo Wang ++ * Tianshu Qiu ++ * Jian Xu Zheng ++ * Yuning Pu ++ * Rajmohan Mani ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TPS68470_REGULATOR(_name, _id, _ops, _n, _vr, \ ++ _vm, _er, _em, _t, _lr, _nlr) \ ++ [TPS68470_ ## _name] = { \ ++ .name = # _name, \ ++ .id = _id, \ ++ .ops = &_ops, \ ++ .n_voltages = _n, \ ++ .type = REGULATOR_VOLTAGE, \ ++ .owner = THIS_MODULE, \ ++ .vsel_reg = _vr, \ ++ .vsel_mask = _vm, \ ++ .enable_reg = _er, \ ++ .enable_mask = _em, \ ++ .volt_table = _t, \ ++ .linear_ranges = _lr, \ ++ .n_linear_ranges = _nlr, \ ++ } ++ ++static const struct linear_range tps68470_ldo_ranges[] = { ++ REGULATOR_LINEAR_RANGE(875000, 0, 125, 17800), ++}; ++ ++static const struct linear_range tps68470_core_ranges[] = { ++ REGULATOR_LINEAR_RANGE(900000, 0, 42, 25000), ++}; ++ ++/* Operations permitted on DCDCx, LDO2, LDO3 and LDO4 */ ++static const struct regulator_ops tps68470_regulator_ops = { ++ .is_enabled = regulator_is_enabled_regmap, ++ .enable = regulator_enable_regmap, ++ .disable = regulator_disable_regmap, ++ .get_voltage_sel = regulator_get_voltage_sel_regmap, ++ .set_voltage_sel = regulator_set_voltage_sel_regmap, ++ .list_voltage = regulator_list_voltage_linear_range, ++ .map_voltage = regulator_map_voltage_linear_range, ++}; ++ ++static const struct regulator_desc regulators[] = { ++ TPS68470_REGULATOR(CORE, TPS68470_CORE, ++ tps68470_regulator_ops, 43, TPS68470_REG_VDVAL, ++ TPS68470_VDVAL_DVOLT_MASK, TPS68470_REG_VDCTL, ++ TPS68470_VDCTL_EN_MASK, ++ NULL, tps68470_core_ranges, ++ ARRAY_SIZE(tps68470_core_ranges)), ++ TPS68470_REGULATOR(ANA, TPS68470_ANA, ++ tps68470_regulator_ops, 126, TPS68470_REG_VAVAL, ++ TPS68470_VAVAL_AVOLT_MASK, TPS68470_REG_VACTL, ++ TPS68470_VACTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++ TPS68470_REGULATOR(VCM, TPS68470_VCM, ++ tps68470_regulator_ops, 126, TPS68470_REG_VCMVAL, ++ TPS68470_VCMVAL_VCVOLT_MASK, TPS68470_REG_VCMCTL, ++ TPS68470_VCMCTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++ TPS68470_REGULATOR(VIO, TPS68470_VIO, ++ tps68470_regulator_ops, 126, TPS68470_REG_VIOVAL, ++ TPS68470_VIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, ++ TPS68470_S_I2C_CTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++ ++/* ++ * (1) This register must have same setting as VIOVAL if S_IO LDO is used to ++ * power daisy chained IOs in the receive side. ++ * (2) If there is no I2C daisy chain it can be set freely. ++ * ++ */ ++ TPS68470_REGULATOR(VSIO, TPS68470_VSIO, ++ tps68470_regulator_ops, 126, TPS68470_REG_VSIOVAL, ++ TPS68470_VSIOVAL_IOVOLT_MASK, TPS68470_REG_S_I2C_CTL, ++ TPS68470_S_I2C_CTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++ TPS68470_REGULATOR(AUX1, TPS68470_AUX1, ++ tps68470_regulator_ops, 126, TPS68470_REG_VAUX1VAL, ++ TPS68470_VAUX1VAL_AUX1VOLT_MASK, ++ TPS68470_REG_VAUX1CTL, ++ TPS68470_VAUX1CTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++ TPS68470_REGULATOR(AUX2, TPS68470_AUX2, ++ tps68470_regulator_ops, 126, TPS68470_REG_VAUX2VAL, ++ TPS68470_VAUX2VAL_AUX2VOLT_MASK, ++ TPS68470_REG_VAUX2CTL, ++ TPS68470_VAUX2CTL_EN_MASK, ++ NULL, tps68470_ldo_ranges, ++ ARRAY_SIZE(tps68470_ldo_ranges)), ++}; ++ ++#define TPS68470_REG_INIT_DATA(_name, _min_uV, _max_uV) \ ++ [TPS68470_ ## _name] = { \ ++ .constraints = { \ ++ .name = # _name, \ ++ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE | \ ++ REGULATOR_CHANGE_STATUS, \ ++ .min_uV = _min_uV, \ ++ .max_uV = _max_uV, \ ++ }, \ ++ } ++ ++struct regulator_init_data tps68470_init[] = { ++ TPS68470_REG_INIT_DATA(CORE, 900000, 1950000), ++ TPS68470_REG_INIT_DATA(ANA, 875000, 3100000), ++ TPS68470_REG_INIT_DATA(VCM, 875000, 3100000), ++ TPS68470_REG_INIT_DATA(VIO, 875000, 3100000), ++ TPS68470_REG_INIT_DATA(VSIO, 875000, 3100000), ++ TPS68470_REG_INIT_DATA(AUX1, 875000, 3100000), ++ TPS68470_REG_INIT_DATA(AUX2, 875000, 3100000), ++}; ++ ++static int tps68470_regulator_probe(struct platform_device *pdev) ++{ ++ struct tps68470_regulator_platform_data *pdata = pdev->dev.platform_data; ++ struct regulator_config config = { }; ++ struct regmap *tps68470_regmap; ++ struct regulator_dev *rdev; ++ int i; ++ ++ tps68470_regmap = dev_get_drvdata(pdev->dev.parent); ++ ++ for (i = 0; i < TPS68470_NUM_REGULATORS; i++) { ++ config.dev = pdev->dev.parent; ++ config.regmap = tps68470_regmap; ++ if (pdata && pdata->reg_init_data[i]) ++ config.init_data = pdata->reg_init_data[i]; ++ else ++ config.init_data = &tps68470_init[i]; ++ ++ rdev = devm_regulator_register(&pdev->dev, ®ulators[i], &config); ++ if (IS_ERR(rdev)) { ++ dev_err(&pdev->dev, "failed to register %s regulator\n", ++ regulators[i].name); ++ return PTR_ERR(rdev); ++ } ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver tps68470_regulator_driver = { ++ .driver = { ++ .name = "tps68470-regulator", ++ }, ++ .probe = tps68470_regulator_probe, ++}; ++ ++/* ++ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers ++ * registering before the drivers for the camera-sensors which use them bind. ++ * subsys_initcall() ensures this when the drivers are builtin. ++ */ ++static int __init tps68470_regulator_init(void) ++{ ++ return platform_driver_register(&tps68470_regulator_driver); ++} ++subsys_initcall(tps68470_regulator_init); ++ ++static void __exit tps68470_regulator_exit(void) ++{ ++ platform_driver_unregister(&tps68470_regulator_driver); ++} ++module_exit(tps68470_regulator_exit); ++ ++MODULE_ALIAS("platform:tps68470-regulator"); ++MODULE_DESCRIPTION("TPS68470 voltage regulator driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.34.1 + +From 5a056cdfd2509a10378053861dbaafc6288098de Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:01 +0200 +Subject: [PATCH] clk: Introduce clk-tps68470 driver + +The TPS68470 PMIC provides Clocks, GPIOs and Regulators. At present in +the kernel the Regulators and Clocks are controlled by an OpRegion +driver designed to work with power control methods defined in ACPI, but +some platforms lack those methods, meaning drivers need to be able to +consume the resources of these chips through the usual frameworks. + +This commit adds a driver for the clocks provided by the tps68470, +and is designed to bind to the platform_device registered by the +intel_skl_int3472 module. + +This is based on this out of tree driver written by Intel: +https://github.com/intel/linux-intel-lts/blob/4.14/base/drivers/clk/clk-tps68470.c +with various cleanups added. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/clk/Kconfig | 6 + + drivers/clk/Makefile | 1 + + drivers/clk/clk-tps68470.c | 256 +++++++++++++++++++++++++++++++++++ + include/linux/mfd/tps68470.h | 11 ++ + 4 files changed, 274 insertions(+) + create mode 100644 drivers/clk/clk-tps68470.c + +diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig +index c5b3dc97396a..7dffecac83d1 100644 +--- a/drivers/clk/Kconfig ++++ b/drivers/clk/Kconfig +@@ -169,6 +169,12 @@ config COMMON_CLK_CDCE706 + help + This driver supports TI CDCE706 programmable 3-PLL clock synthesizer. + ++config COMMON_CLK_TPS68470 ++ tristate "Clock Driver for TI TPS68470 PMIC" ++ depends on I2C && REGMAP_I2C && INTEL_SKL_INT3472 ++ help ++ This driver supports the clocks provided by TPS68470 ++ + config COMMON_CLK_CDCE925 + tristate "Clock driver for TI CDCE913/925/937/949 devices" + depends on I2C +diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile +index e42312121e51..6b6a88ae1425 100644 +--- a/drivers/clk/Makefile ++++ b/drivers/clk/Makefile +@@ -63,6 +63,7 @@ obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o + obj-$(CONFIG_COMMON_CLK_STM32F) += clk-stm32f4.o + obj-$(CONFIG_COMMON_CLK_STM32H7) += clk-stm32h7.o + obj-$(CONFIG_COMMON_CLK_STM32MP157) += clk-stm32mp1.o ++obj-$(CONFIG_COMMON_CLK_TPS68470) += clk-tps68470.o + obj-$(CONFIG_CLK_TWL6040) += clk-twl6040.o + obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o + obj-$(CONFIG_COMMON_CLK_VC5) += clk-versaclock5.o +diff --git a/drivers/clk/clk-tps68470.c b/drivers/clk/clk-tps68470.c +new file mode 100644 +index 000000000000..27e8cbd0f60e +--- /dev/null ++++ b/drivers/clk/clk-tps68470.c +@@ -0,0 +1,256 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Clock driver for TPS68470 PMIC ++ * ++ * Copyright (C) 2018 Intel Corporation ++ * ++ * Authors: ++ * Zaikuo Wang ++ * Tianshu Qiu ++ * Jian Xu Zheng ++ * Yuning Pu ++ * Antti Laakso ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define TPS68470_CLK_NAME "tps68470-clk" ++ ++#define to_tps68470_clkdata(clkd) \ ++ container_of(clkd, struct tps68470_clkdata, clkout_hw) ++ ++struct tps68470_clkout_freqs { ++ unsigned long freq; ++ unsigned int xtaldiv; ++ unsigned int plldiv; ++ unsigned int postdiv; ++ unsigned int buckdiv; ++ unsigned int boostdiv; ++} clk_freqs[] = { ++/* ++ * The PLL is used to multiply the crystal oscillator ++ * frequency range of 3 MHz to 27 MHz by a programmable ++ * factor of F = (M/N)*(1/P) such that the output ++ * available at the HCLK_A or HCLK_B pins are in the range ++ * of 4 MHz to 64 MHz in increments of 0.1 MHz ++ * ++ * hclk_# = osc_in * (((plldiv*2)+320) / (xtaldiv+30)) * (1 / 2^postdiv) ++ * ++ * PLL_REF_CLK should be as close as possible to 100kHz ++ * PLL_REF_CLK = input clk / XTALDIV[7:0] + 30) ++ * ++ * PLL_VCO_CLK = (PLL_REF_CLK * (plldiv*2 + 320)) ++ * ++ * BOOST should be as close as possible to 2Mhz ++ * BOOST = PLL_VCO_CLK / (BOOSTDIV[4:0] + 16) * ++ * ++ * BUCK should be as close as possible to 5.2Mhz ++ * BUCK = PLL_VCO_CLK / (BUCKDIV[3:0] + 5) ++ * ++ * osc_in xtaldiv plldiv postdiv hclk_# ++ * 20Mhz 170 32 1 19.2Mhz ++ * 20Mhz 170 40 1 20Mhz ++ * 20Mhz 170 80 1 24Mhz ++ * ++ */ ++ { 19200000, 170, 32, 1, 2, 3 }, ++ { 20000000, 170, 40, 1, 3, 4 }, ++ { 24000000, 170, 80, 1, 4, 8 }, ++}; ++ ++struct tps68470_clkdata { ++ struct clk_hw clkout_hw; ++ struct regmap *regmap; ++ struct clk *clk; ++ int clk_cfg_idx; ++}; ++ ++static int tps68470_clk_is_prepared(struct clk_hw *hw) ++{ ++ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); ++ int val; ++ ++ if (regmap_read(clkdata->regmap, TPS68470_REG_PLLCTL, &val)) ++ return 0; ++ ++ return val & TPS68470_PLL_EN_MASK; ++} ++ ++static int tps68470_clk_prepare(struct clk_hw *hw) ++{ ++ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); ++ int idx = clkdata->clk_cfg_idx; ++ ++ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, clk_freqs[idx].boostdiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, clk_freqs[idx].buckdiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, TPS68470_PLLSWR_DEFAULT); ++ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, clk_freqs[idx].xtaldiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, clk_freqs[idx].plldiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, clk_freqs[idx].postdiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV2, clk_freqs[idx].postdiv); ++ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, TPS68470_CLKCFG2_DRV_STR_2MA); ++ ++ regmap_write(clkdata->regmap, TPS68470_REG_PLLCTL, ++ TPS68470_OSC_EXT_CAP_DEFAULT << TPS68470_OSC_EXT_CAP_SHIFT | ++ TPS68470_CLK_SRC_XTAL << TPS68470_CLK_SRC_SHIFT); ++ ++ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, ++ (TPS68470_PLL_OUTPUT_ENABLE << ++ TPS68470_OUTPUT_A_SHIFT) | ++ (TPS68470_PLL_OUTPUT_ENABLE << ++ TPS68470_OUTPUT_B_SHIFT)); ++ ++ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, ++ TPS68470_PLL_EN_MASK, TPS68470_PLL_EN_MASK); ++ ++ return 0; ++} ++ ++static void tps68470_clk_unprepare(struct clk_hw *hw) ++{ ++ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); ++ ++ /* disable clock first*/ ++ regmap_update_bits(clkdata->regmap, TPS68470_REG_PLLCTL, TPS68470_PLL_EN_MASK, 0); ++ ++ /* write hw defaults */ ++ regmap_write(clkdata->regmap, TPS68470_REG_BOOSTDIV, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_BUCKDIV, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_PLLSWR, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_XTALDIV, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_PLLDIV, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_POSTDIV, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG2, 0); ++ regmap_write(clkdata->regmap, TPS68470_REG_CLKCFG1, 0); ++} ++ ++static unsigned long tps68470_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ++{ ++ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); ++ ++ return clk_freqs[clkdata->clk_cfg_idx].freq; ++} ++ ++static int tps68470_clk_cfg_lookup(unsigned long rate) ++{ ++ long diff, best_diff = LONG_MAX; ++ int i, best_idx = 0; ++ ++ for (i = 0; i < ARRAY_SIZE(clk_freqs); i++) { ++ diff = clk_freqs[i].freq - rate; ++ if (diff == 0) ++ return i; ++ ++ diff = abs(diff); ++ if (diff < best_diff) { ++ best_diff = diff; ++ best_idx = i; ++ } ++ } ++ ++ return best_idx; ++} ++ ++static long tps68470_clk_round_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long *parent_rate) ++{ ++ int idx = tps68470_clk_cfg_lookup(rate); ++ ++ return clk_freqs[idx].freq; ++} ++ ++static int tps68470_clk_set_rate(struct clk_hw *hw, unsigned long rate, ++ unsigned long parent_rate) ++{ ++ struct tps68470_clkdata *clkdata = to_tps68470_clkdata(hw); ++ int idx = tps68470_clk_cfg_lookup(rate); ++ ++ if (rate != clk_freqs[idx].freq) ++ return -EINVAL; ++ ++ clkdata->clk_cfg_idx = idx; ++ return 0; ++} ++ ++static const struct clk_ops tps68470_clk_ops = { ++ .is_prepared = tps68470_clk_is_prepared, ++ .prepare = tps68470_clk_prepare, ++ .unprepare = tps68470_clk_unprepare, ++ .recalc_rate = tps68470_clk_recalc_rate, ++ .round_rate = tps68470_clk_round_rate, ++ .set_rate = tps68470_clk_set_rate, ++}; ++ ++static struct clk_init_data tps68470_clk_initdata = { ++ .name = TPS68470_CLK_NAME, ++ .ops = &tps68470_clk_ops, ++}; ++ ++static int tps68470_clk_probe(struct platform_device *pdev) ++{ ++ struct tps68470_clk_platform_data *pdata = pdev->dev.platform_data; ++ struct tps68470_clkdata *tps68470_clkdata; ++ int ret; ++ ++ tps68470_clkdata = devm_kzalloc(&pdev->dev, sizeof(*tps68470_clkdata), ++ GFP_KERNEL); ++ if (!tps68470_clkdata) ++ return -ENOMEM; ++ ++ tps68470_clkdata->regmap = dev_get_drvdata(pdev->dev.parent); ++ tps68470_clkdata->clkout_hw.init = &tps68470_clk_initdata; ++ tps68470_clkdata->clk = devm_clk_register(&pdev->dev, &tps68470_clkdata->clkout_hw); ++ if (IS_ERR(tps68470_clkdata->clk)) ++ return PTR_ERR(tps68470_clkdata->clk); ++ ++ ret = devm_clk_hw_register_clkdev(&pdev->dev, &tps68470_clkdata->clkout_hw, ++ TPS68470_CLK_NAME, NULL); ++ if (ret) ++ return ret; ++ ++ if (pdata) { ++ ret = devm_clk_hw_register_clkdev(&pdev->dev, ++ &tps68470_clkdata->clkout_hw, ++ pdata->consumer_con_id, ++ pdata->consumer_dev_name); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static struct platform_driver tps68470_clk_driver = { ++ .driver = { ++ .name = TPS68470_CLK_NAME, ++ }, ++ .probe = tps68470_clk_probe, ++}; ++ ++/* ++ * The ACPI tps68470 probe-ordering depends on the clk/gpio/regulator drivers ++ * registering before the drivers for the camera-sensors which use them bind. ++ * subsys_initcall() ensures this when the drivers are builtin. ++ */ ++static int __init tps68470_clk_init(void) ++{ ++ return platform_driver_register(&tps68470_clk_driver); ++} ++subsys_initcall(tps68470_clk_init); ++ ++static void __exit tps68470_clk_exit(void) ++{ ++ platform_driver_unregister(&tps68470_clk_driver); ++} ++module_exit(tps68470_clk_exit); ++ ++MODULE_ALIAS("platform:tps68470-clk"); ++MODULE_DESCRIPTION("clock driver for TPS68470 pmic"); ++MODULE_LICENSE("GPL"); +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index ffe81127d91c..7807fa329db0 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -75,6 +75,17 @@ + #define TPS68470_CLKCFG1_MODE_A_MASK GENMASK(1, 0) + #define TPS68470_CLKCFG1_MODE_B_MASK GENMASK(3, 2) + ++#define TPS68470_CLKCFG2_DRV_STR_2MA 0x05 ++#define TPS68470_PLL_OUTPUT_ENABLE 0x02 ++#define TPS68470_CLK_SRC_XTAL BIT(0) ++#define TPS68470_PLLSWR_DEFAULT GENMASK(1, 0) ++#define TPS68470_OSC_EXT_CAP_DEFAULT 0x05 ++ ++#define TPS68470_OUTPUT_A_SHIFT 0x00 ++#define TPS68470_OUTPUT_B_SHIFT 0x02 ++#define TPS68470_CLK_SRC_SHIFT GENMASK(2, 0) ++#define TPS68470_OSC_EXT_CAP_SHIFT BIT(2) ++ + #define TPS68470_GPIO_CTL_REG_A(x) (TPS68470_REG_GPCTL0A + (x) * 2) + #define TPS68470_GPIO_CTL_REG_B(x) (TPS68470_REG_GPCTL0B + (x) * 2) + #define TPS68470_GPIO_MODE_MASK GENMASK(1, 0) +-- +2.34.1 + +From 5773134e6376a818cdea8c15447b6c9eb26da355 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + .../x86/intel/int3472/intel_skl_int3472_tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c +index c05b4cf502fe..42e688f4cad4 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c ++++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c +@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.34.1 + +From 85062c3816e98da6370c4b95fd0cf499adda91cb Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:03 +0200 +Subject: [PATCH] platform/x86: int3472: Split into 2 drivers + +The intel_skl_int3472.ko module contains 2 separate drivers, +the int3472_discrete platform driver and the int3472_tps68470 +I2C-driver. + +These 2 drivers contain very little shared code, only +skl_int3472_get_acpi_buffer() and skl_int3472_fill_cldb() are +shared. + +Split the module into 2 drivers, linking the little shared code +directly into both. + +This will allow us to add soft-module dependencies for the +tps68470 clk, gpio and regulator drivers to the new +intel_skl_int3472_tps68470.ko to help with probe ordering issues +without causing these modules to get loaded on boards which only +use the int3472_discrete platform driver. + +While at it also rename the .c and .h files to remove the +cumbersome intel_skl_int3472_ prefix. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/Makefile | 9 ++-- + ...lk_and_regulator.c => clk_and_regulator.c} | 2 +- + drivers/platform/x86/intel/int3472/common.c | 54 +++++++++++++++++++ + .../{intel_skl_int3472_common.h => common.h} | 3 -- + ...ntel_skl_int3472_discrete.c => discrete.c} | 28 ++++++++-- + ...ntel_skl_int3472_tps68470.c => tps68470.c} | 23 +++++++- + 6 files changed, 105 insertions(+), 14 deletions(-) + rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_clk_and_regulator.c => clk_and_regulator.c} (99%) + create mode 100644 drivers/platform/x86/intel/int3472/common.c + rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_common.h => common.h} (94%) + rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_discrete.c => discrete.c} (93%) + rename drivers/platform/x86/intel/int3472/{intel_skl_int3472_tps68470.c => tps68470.c} (85%) + +diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile +index 2362e04db18d..4a4b2518ea16 100644 +--- a/drivers/platform/x86/intel/int3472/Makefile ++++ b/drivers/platform/x86/intel/int3472/Makefile +@@ -1,5 +1,4 @@ +-obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o +-intel_skl_int3472-y := intel_skl_int3472_common.o \ +- intel_skl_int3472_discrete.o \ +- intel_skl_int3472_tps68470.o \ +- intel_skl_int3472_clk_and_regulator.o ++obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ ++ intel_skl_int3472_tps68470.o ++intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o ++intel_skl_int3472_tps68470-y := tps68470.o common.o +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c +similarity index 99% +rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c +rename to drivers/platform/x86/intel/int3472/clk_and_regulator.c +index 1700e7557a82..1cf958983e86 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c ++++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c +@@ -9,7 +9,7 @@ + #include + #include + +-#include "intel_skl_int3472_common.h" ++#include "common.h" + + /* + * The regulators have to have .ops to be valid, but the only ops we actually +diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c +new file mode 100644 +index 000000000000..350655a9515b +--- /dev/null ++++ b/drivers/platform/x86/intel/int3472/common.c +@@ -0,0 +1,54 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* Author: Dan Scally */ ++ ++#include ++#include ++ ++#include "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; ++} +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/common.h +similarity index 94% +rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h +rename to drivers/platform/x86/intel/int3472/common.h +index 714fde73b524..d14944ee8586 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h ++++ b/drivers/platform/x86/intel/int3472/common.h +@@ -105,9 +105,6 @@ struct int3472_discrete_device { + 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); +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +similarity index 93% +rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +rename to drivers/platform/x86/intel/int3472/discrete.c +index e59d79c7e82f..a19a1f5dbdd7 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -14,7 +14,7 @@ + #include + #include + +-#include "intel_skl_int3472_common.h" ++#include "common.h" + + /* + * 79234640-9e10-4fea-a5c1-b5aa8b19756f +@@ -332,7 +332,9 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) + return 0; + } + +-int skl_int3472_discrete_probe(struct platform_device *pdev) ++static int skl_int3472_discrete_remove(struct platform_device *pdev); ++ ++static int skl_int3472_discrete_probe(struct platform_device *pdev) + { + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3472_discrete_device *int3472; +@@ -395,7 +397,7 @@ int skl_int3472_discrete_probe(struct platform_device *pdev) + return ret; + } + +-int skl_int3472_discrete_remove(struct platform_device *pdev) ++static int skl_int3472_discrete_remove(struct platform_device *pdev) + { + struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); + +@@ -411,3 +413,23 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) + + return 0; + } ++ ++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, ++}; ++module_platform_driver(int3472_discrete); ++ ++MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_LICENSE("GPL v2"); +diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +similarity index 85% +rename from drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c +rename to drivers/platform/x86/intel/int3472/tps68470.c +index 42e688f4cad4..b94cf66ab61f 100644 +--- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -7,7 +7,7 @@ + #include + #include + +-#include "intel_skl_int3472_common.h" ++#include "common.h" + + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 +@@ -102,7 +102,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) + return DESIGNED_FOR_WINDOWS; + } + +-int skl_int3472_tps68470_probe(struct i2c_client *client) ++static int skl_int3472_tps68470_probe(struct i2c_client *client) + { + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct regmap *regmap; +@@ -142,3 +142,22 @@ int skl_int3472_tps68470_probe(struct i2c_client *client) + + return ret; + } ++ ++static const struct acpi_device_id int3472_device_id[] = { ++ { "INT3472", 0 }, ++ { } ++}; ++MODULE_DEVICE_TABLE(acpi, int3472_device_id); ++ ++static struct i2c_driver int3472_tps68470 = { ++ .driver = { ++ .name = "int3472-tps68470", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe_new = skl_int3472_tps68470_probe, ++}; ++module_i2c_driver(int3472_tps68470); ++ ++MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_LICENSE("GPL v2"); +-- +2.34.1 + +From ca4571a3af3604abce4f95ae031c87ebf22161cc Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:04 +0200 +Subject: [PATCH] platform/x86: int3472: Add get_sensor_adev_and_name() helper + +The discrete.c code is not the only code which needs to lookup the +acpi_device and device-name for the sensor for which the INT3472 +ACPI-device is a GPIO/clk/regulator provider. + +The tps68470.c code also needs this functionality, so factor this +out into a new get_sensor_adev_and_name() helper. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/common.c | 28 +++++++++++++++++++ + drivers/platform/x86/intel/int3472/common.h | 3 ++ + drivers/platform/x86/intel/int3472/discrete.c | 22 +++------------ + 3 files changed, 35 insertions(+), 18 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c +index 350655a9515b..77cf058e4168 100644 +--- a/drivers/platform/x86/intel/int3472/common.c ++++ b/drivers/platform/x86/intel/int3472/common.c +@@ -52,3 +52,31 @@ int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) + kfree(obj); + return ret; + } ++ ++/* sensor_adev_ret may be NULL, name_ret must not be NULL */ ++int skl_int3472_get_sensor_adev_and_name(struct device *dev, ++ struct acpi_device **sensor_adev_ret, ++ const char **name_ret) ++{ ++ struct acpi_device *adev = ACPI_COMPANION(dev); ++ struct acpi_device *sensor; ++ int ret = 0; ++ ++ sensor = acpi_dev_get_first_consumer_dev(adev); ++ if (!sensor) { ++ dev_err(dev, "INT3472 seems to have no dependents.\n"); ++ return -ENODEV; ++ } ++ ++ *name_ret = devm_kasprintf(dev, GFP_KERNEL, I2C_DEV_NAME_FORMAT, ++ acpi_dev_name(sensor)); ++ if (!*name_ret) ++ ret = -ENOMEM; ++ ++ if (ret == 0 && sensor_adev_ret) ++ *sensor_adev_ret = sensor; ++ else ++ acpi_dev_put(sensor); ++ ++ return ret; ++} +diff --git a/drivers/platform/x86/intel/int3472/common.h b/drivers/platform/x86/intel/int3472/common.h +index d14944ee8586..53270d19c73a 100644 +--- a/drivers/platform/x86/intel/int3472/common.h ++++ b/drivers/platform/x86/intel/int3472/common.h +@@ -108,6 +108,9 @@ struct int3472_discrete_device { + 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_get_sensor_adev_and_name(struct device *dev, ++ struct acpi_device **sensor_adev_ret, ++ const char **name_ret); + + int skl_int3472_register_clock(struct int3472_discrete_device *int3472); + void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index a19a1f5dbdd7..efd31a0c7a88 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -363,19 +363,10 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) + 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; +- } ++ ret = skl_int3472_get_sensor_adev_and_name(&pdev->dev, &int3472->sensor, ++ &int3472->sensor_name); ++ if (ret) ++ return ret; + + /* + * Initialising this list means we can call gpiod_remove_lookup_table() +@@ -390,11 +381,6 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) + } + + return 0; +- +-err_put_sensor: +- acpi_dev_put(int3472->sensor); +- +- return ret; + } + + static int skl_int3472_discrete_remove(struct platform_device *pdev) +-- +2.34.1 + +From b81c726c0d7568ded109d37f1a4c2803466e7b64 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:05 +0200 +Subject: [PATCH] platform/x86: int3472: Pass tps68470_clk_platform_data to the + tps68470-regulator MFD-cell + +Pass tps68470_clk_platform_data to the tps68470-clk MFD-cell, +so that sensors which use the TPS68470 can find their clock. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 33 ++++++++++++++----- + 1 file changed, 25 insertions(+), 8 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index b94cf66ab61f..78e34e7b6969 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include + + #include "common.h" +@@ -17,12 +18,6 @@ static const struct mfd_cell tps68470_cros[] = { + { .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, +@@ -105,10 +100,17 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) + static int skl_int3472_tps68470_probe(struct i2c_client *client) + { + struct acpi_device *adev = ACPI_COMPANION(&client->dev); ++ struct tps68470_clk_platform_data clk_pdata = {}; ++ struct mfd_cell *cells; + struct regmap *regmap; + int device_type; + int ret; + ++ ret = skl_int3472_get_sensor_adev_and_name(&client->dev, NULL, ++ &clk_pdata.consumer_dev_name); ++ if (ret) ++ return 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)); +@@ -126,9 +128,24 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + 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), ++ cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); ++ if (!cells) ++ return -ENOMEM; ++ ++ cells[0].name = "tps68470-clk"; ++ cells[0].platform_data = &clk_pdata; ++ cells[0].pdata_size = sizeof(clk_pdata); ++ cells[1].name = "tps68470-regulator"; ++ /* ++ * The GPIO cell must be last because acpi_gpiochip_add() calls ++ * acpi_dev_clear_dependencies() and the clk + regulators must ++ * be ready when this happens. ++ */ ++ cells[2].name = "tps68470-gpio"; ++ ++ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, + NULL, 0, NULL); ++ kfree(cells); + break; + case DESIGNED_FOR_CHROMEOS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, +-- +2.34.1 + +From abdf52f78104bb61eb43ba5e764773d76914c8c2 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:06 +0200 +Subject: [PATCH] platform/x86: int3472: Pass tps68470_regulator_platform_data + to the tps68470-regulator MFD-cell + +Pass tps68470_regulator_platform_data to the tps68470-regulator +MFD-cell, specifying the voltages of the various regulators and +tying the regulators to the sensor supplies so that sensors which use +the TPS68470 can find their regulators. + +Since the voltages and supply connections are board-specific, this +introduces a DMI matches int3472_tps68470_board_data struct which +contains the necessary per-board info. + +This per-board info also includes GPIO lookup information for the +sensor GPIOs which may be connected to the tps68470 gpios. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/Makefile | 2 +- + drivers/platform/x86/intel/int3472/tps68470.c | 43 +++++-- + drivers/platform/x86/intel/int3472/tps68470.h | 25 ++++ + .../x86/intel/int3472/tps68470_board_data.c | 118 ++++++++++++++++++ + 4 files changed, 180 insertions(+), 8 deletions(-) + create mode 100644 drivers/platform/x86/intel/int3472/tps68470.h + create mode 100644 drivers/platform/x86/intel/int3472/tps68470_board_data.c + +diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile +index 4a4b2518ea16..ca56e7eea781 100644 +--- a/drivers/platform/x86/intel/int3472/Makefile ++++ b/drivers/platform/x86/intel/int3472/Makefile +@@ -1,4 +1,4 @@ + obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472_discrete.o \ + intel_skl_int3472_tps68470.o + intel_skl_int3472_discrete-y := discrete.o clk_and_regulator.o common.o +-intel_skl_int3472_tps68470-y := tps68470.o common.o ++intel_skl_int3472_tps68470-y := tps68470.o tps68470_board_data.o common.o +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 78e34e7b6969..aae24d228770 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -9,6 +9,7 @@ + #include + + #include "common.h" ++#include "tps68470.h" + + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 +@@ -100,6 +101,7 @@ static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) + static int skl_int3472_tps68470_probe(struct i2c_client *client) + { + struct acpi_device *adev = ACPI_COMPANION(&client->dev); ++ const struct int3472_tps68470_board_data *board_data; + struct tps68470_clk_platform_data clk_pdata = {}; + struct mfd_cell *cells; + struct regmap *regmap; +@@ -128,6 +130,12 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + device_type = skl_int3472_tps68470_calc_type(adev); + switch (device_type) { + case DESIGNED_FOR_WINDOWS: ++ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); ++ if (!board_data) { ++ dev_err(&client->dev, "No board-data found for this laptop/tablet model\n"); ++ return -ENODEV; ++ } ++ + cells = kcalloc(3, sizeof(*cells), GFP_KERNEL); + if (!cells) + return -ENOMEM; +@@ -136,6 +144,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[0].platform_data = &clk_pdata; + cells[0].pdata_size = sizeof(clk_pdata); + cells[1].name = "tps68470-regulator"; ++ cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; ++ cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); + /* + * The GPIO cell must be last because acpi_gpiochip_add() calls + * acpi_dev_clear_dependencies() and the clk + regulators must +@@ -143,9 +153,15 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + */ + cells[2].name = "tps68470-gpio"; + ++ gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_table); ++ + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, cells, 3, + NULL, 0, NULL); + kfree(cells); ++ ++ if (ret) ++ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); ++ + break; + case DESIGNED_FOR_CHROMEOS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, +@@ -160,18 +176,31 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + return ret; + } + ++static int skl_int3472_tps68470_remove(struct i2c_client *client) ++{ ++ const struct int3472_tps68470_board_data *board_data; ++ ++ board_data = int3472_tps68470_get_board_data(dev_name(&client->dev)); ++ if (board_data) ++ gpiod_remove_lookup_table(board_data->tps68470_gpio_lookup_table); ++ ++ return 0; ++} ++ ++ + static const struct acpi_device_id int3472_device_id[] = { +- { "INT3472", 0 }, +- { } ++ { "INT3472", 0 }, ++ { } + }; + MODULE_DEVICE_TABLE(acpi, int3472_device_id); + + static struct i2c_driver int3472_tps68470 = { +- .driver = { +- .name = "int3472-tps68470", +- .acpi_match_table = int3472_device_id, +- }, +- .probe_new = skl_int3472_tps68470_probe, ++ .driver = { ++ .name = "int3472-tps68470", ++ .acpi_match_table = int3472_device_id, ++ }, ++ .probe_new = skl_int3472_tps68470_probe, ++ .remove = skl_int3472_tps68470_remove, + }; + module_i2c_driver(int3472_tps68470); + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.h b/drivers/platform/x86/intel/int3472/tps68470.h +new file mode 100644 +index 000000000000..cfd33eb62740 +--- /dev/null ++++ b/drivers/platform/x86/intel/int3472/tps68470.h +@@ -0,0 +1,25 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/* ++ * TI TPS68470 PMIC platform data definition. ++ * ++ * Copyright (c) 2021 Red Hat Inc. ++ * ++ * Red Hat authors: ++ * Hans de Goede ++ */ ++ ++#ifndef _INTEL_SKL_INT3472_TPS68470_H ++#define _INTEL_SKL_INT3472_TPS68470_H ++ ++struct gpiod_lookup_table; ++struct tps68470_regulator_platform_data; ++ ++struct int3472_tps68470_board_data { ++ const char *dev_name; ++ struct gpiod_lookup_table *tps68470_gpio_lookup_table; ++ const struct tps68470_regulator_platform_data *tps68470_regulator_pdata; ++}; ++ ++const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name); ++ ++#endif +diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c +new file mode 100644 +index 000000000000..96954a789bb8 +--- /dev/null ++++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c +@@ -0,0 +1,118 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * TI TPS68470 PMIC platform data definition. ++ * ++ * Copyright (c) 2021 Dan Scally ++ * Copyright (c) 2021 Red Hat Inc. ++ * ++ * Red Hat authors: ++ * Hans de Goede ++ */ ++ ++#include ++#include ++#include ++#include ++#include "tps68470.h" ++ ++static struct regulator_consumer_supply int347a_core_consumer_supplies[] = { ++ REGULATOR_SUPPLY("dvdd", "i2c-INT347A:00"), ++}; ++ ++static struct regulator_consumer_supply int347a_ana_consumer_supplies[] = { ++ REGULATOR_SUPPLY("avdd", "i2c-INT347A:00"), ++}; ++ ++static struct regulator_consumer_supply int347a_vsio_consumer_supplies[] = { ++ REGULATOR_SUPPLY("dovdd", "i2c-INT347A:00"), ++}; ++ ++static const struct regulator_init_data surface_go_tps68470_core_reg_init_data = { ++ .constraints = { ++ .min_uV = 1200000, ++ .max_uV = 1200000, ++ .apply_uV = 1, ++ .valid_ops_mask = REGULATOR_CHANGE_STATUS, ++ }, ++ .num_consumer_supplies = ARRAY_SIZE(int347a_core_consumer_supplies), ++ .consumer_supplies = int347a_core_consumer_supplies, ++}; ++ ++static const struct regulator_init_data surface_go_tps68470_ana_reg_init_data = { ++ .constraints = { ++ .min_uV = 2815200, ++ .max_uV = 2815200, ++ .apply_uV = 1, ++ .valid_ops_mask = REGULATOR_CHANGE_STATUS, ++ }, ++ .num_consumer_supplies = ARRAY_SIZE(int347a_ana_consumer_supplies), ++ .consumer_supplies = int347a_ana_consumer_supplies, ++}; ++ ++static const struct regulator_init_data surface_go_tps68470_vsio_reg_init_data = { ++ .constraints = { ++ .min_uV = 1800600, ++ .max_uV = 1800600, ++ .apply_uV = 1, ++ .valid_ops_mask = REGULATOR_CHANGE_STATUS, ++ }, ++ .num_consumer_supplies = ARRAY_SIZE(int347a_vsio_consumer_supplies), ++ .consumer_supplies = int347a_vsio_consumer_supplies, ++}; ++ ++static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata = { ++ .reg_init_data = { ++ [TPS68470_CORE] = &surface_go_tps68470_core_reg_init_data, ++ [TPS68470_ANA] = &surface_go_tps68470_ana_reg_init_data, ++ [TPS68470_VSIO] = &surface_go_tps68470_vsio_reg_init_data, ++ }, ++}; ++ ++static struct gpiod_lookup_table surface_go_tps68470_gpios = { ++ .dev_id = "i2c-INT347A:00", ++ .table = { ++ GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), ++ GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW) ++ } ++}; ++ ++static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { ++ .dev_name = "i2c-INT3472:05", ++ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, ++ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, ++}; ++ ++static const struct dmi_system_id int3472_tps68470_board_data_table[] = { ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go"), ++ }, ++ .driver_data = (void *)&surface_go_tps68470_board_data, ++ }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 2"), ++ }, ++ .driver_data = (void *)&surface_go_tps68470_board_data, ++ }, ++ { } ++}; ++ ++const struct int3472_tps68470_board_data *int3472_tps68470_get_board_data(const char *dev_name) ++{ ++ const struct int3472_tps68470_board_data *board_data; ++ const struct dmi_system_id *match; ++ ++ match = dmi_first_match(int3472_tps68470_board_data_table); ++ while (match) { ++ board_data = match->driver_data; ++ if (strcmp(board_data->dev_name, dev_name) == 0) ++ return board_data; ++ ++ dmi_first_match(++match); ++ } ++ ++ return NULL; ++} +-- +2.34.1 + +From d4d2b610f59c11809fbb157d9ca4b4441926cc4d Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:57:07 +0200 +Subject: [PATCH] platform/x86: int3472: Deal with probe ordering issues + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around this info missing from the ACPI tables on devices where +the int3472 driver is used, the int3472 MFD-cell drivers attach info about +consumers to the clks/regulators when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +All the sensor ACPI fw-nodes have a _DEP dependency on the INT3472 ACPI +fw-node, so to work around these probe ordering issues the ACPI core / +i2c-code does not instantiate the I2C-clients for any ACPI devices +which have a _DEP dependency on an INT3472 ACPI device until all +_DEP-s are met. + +This relies on acpi_dev_clear_dependencies() getting called by the driver +for the _DEP-s when they are ready, add a acpi_dev_clear_dependencies() +call to the discrete.c probe code. + +In the tps68470 case calling acpi_dev_clear_dependencies() is already done +by the acpi_gpiochip_add() call done by the driver for the GPIO MFD cell +(The GPIO cell is deliberately the last cell created to make sure the +clk + regulator cells are already instantiated when this happens). + +However for proper probe ordering, the clk/regulator cells must not just +be instantiated the must be fully ready (the clks + regulators must be +registered with their subsystems). + +Add MODULE_SOFTDEP dependencies for the clk and regulator drivers for +the instantiated MFD-cells so that these are loaded before us and so +that they bind immediately when the platform-devs are instantiated. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 1 + + drivers/platform/x86/intel/int3472/tps68470.c | 6 ++++++ + 2 files changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index efd31a0c7a88..18e6d51acc96 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -380,6 +380,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) + return ret; + } + ++ acpi_dev_clear_dependencies(adev); + return 0; + } + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index aae24d228770..21c6c1a6edfc 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -173,6 +173,11 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + return device_type; + } + ++ /* ++ * No acpi_dev_clear_dependencies() here, since the acpi_gpiochip_add() ++ * for the GPIO cell already does this. ++ */ ++ + return ret; + } + +@@ -207,3 +212,4 @@ module_i2c_driver(int3472_tps68470); + MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); + MODULE_AUTHOR("Daniel Scally "); + MODULE_LICENSE("GPL v2"); ++MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); +-- +2.34.1 + +From e0f6f33f5b58ad90fa1451d2e59c518e79ed8293 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 4 Nov 2021 21:46:27 +0000 +Subject: [PATCH] media: i2c: Add integration time margin to ov8865 + +Without this integration margin to reduce the max exposure, it seems +that we trip over a limit that results in the image being entirely +black when max exposure is set. Add the margin to prevent this issue. + +With thanks to jhautbois for spotting and reporting. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov8865.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c +index d5af8aedf5e8..966487e32bfe 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -143,6 +143,7 @@ + #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_INTEGRATION_TIME_MARGIN 8 + + #define OV8865_GAIN_CTRL_H_REG 0x3508 + #define OV8865_GAIN_CTRL_H(v) (((v) & GENMASK(12, 8)) >> 8) +@@ -2462,7 +2463,8 @@ static int ov8865_s_ctrl(struct v4l2_ctrl *ctrl) + if (ctrl->id == V4L2_CID_VBLANK) { + int exposure_max; + +- exposure_max = sensor->state.mode->output_size_y + ctrl->val; ++ exposure_max = sensor->state.mode->output_size_y + ctrl->val - ++ OV8865_INTEGRATION_TIME_MARGIN; + __v4l2_ctrl_modify_range(sensor->ctrls.exposure, + sensor->ctrls.exposure->minimum, + exposure_max, +-- +2.34.1 + +From 112e3114442fa5c8b509dd1b040ae2a114d6b294 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 4 Nov 2021 21:48:38 +0000 +Subject: [PATCH] media: i2c: Fix max gain in ov8865 + +The maximum gain figure in the v4l2 ctrl is wrong. The field is 12 bits +wide, which is where the 8191 figure comes from, but the datasheet is +specific that maximum gain is 16x (the low seven bits are fractional, so +16x gain is 2048) + +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 966487e32bfe..6c78edb65d1e 100644 +--- a/drivers/media/i2c/ov8865.c ++++ b/drivers/media/i2c/ov8865.c +@@ -2535,7 +2535,7 @@ static int ov8865_ctrls_init(struct ov8865_sensor *sensor) + + /* Gain */ + +- v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 8191, 128, ++ v4l2_ctrl_new_std(handler, ops, V4L2_CID_ANALOGUE_GAIN, 128, 2048, 128, + 128); + + /* White Balance */ +-- +2.34.1 + +From 4d22e6a654d4f9ab2033c563c49dc07c843922ac Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Fri, 3 Dec 2021 12:51:08 +0100 +Subject: [PATCH] mfd: intel-lpss: Fix I2C4 not being available on the + Microsoft Surface Go & Go 2 + +Many DSDTs for Kaby Lake and Kaby Lake Refresh models contain a +_SB.PCI0.GEXP ACPI Device node describing an I2C attached PCA953x +GPIO expander. + +This seems to be something which is copy and pasted from the DSDT +from some reference design since this ACPI Device is present even on +models where no such GPIO expander is used at all, such as on the +Microsoft Surface Go & Go 2. + +This ACPI Device is a problem because it contains a SystemMemory +OperationRegion which covers the MMIO for the I2C4 I2C controller. This +causes the MFD cell for the I2C4 controller to not be instantiated due +to a resource conflict, requiring the use of acpi_enforce_resources=lax +to work around this. + +I have done an extensive analysis of all the ACPI tables on the +Microsoft Surface Go and the _SB.PCI0.GEXP ACPI Device's methods are +not used by any code in the ACPI tables, neither are any of them +directly called by any Linux kernel code. This is unsurprising since +running i2cdetect on the I2C4 bus shows that there is no GPIO +expander chip present on these devices at all. + +This commit adds a PCI subsystem vendor:device table listing PCI devices +where it is known to be safe to ignore resource conflicts with ACPI +declared SystemMemory regions. + +This makes the I2C4 bus work out of the box on the Microsoft Surface +Go & Go 2, which is necessary for the cameras on these devices to work. + +Cc: Dan Scally +Cc: Kate Hsuan +Cc: Maximilian Luz +Reviewed-by: Laurent Pinchart +Reviewed-by: Andy Shevchenko +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/mfd/intel-lpss-pci.c | 12 ++++++++++++ + drivers/mfd/intel-lpss.c | 1 + + drivers/mfd/intel-lpss.h | 1 + + 3 files changed, 14 insertions(+) + +diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c +index a872b4485eac..81185d17d9c9 100644 +--- a/drivers/mfd/intel-lpss-pci.c ++++ b/drivers/mfd/intel-lpss-pci.c +@@ -17,6 +17,15 @@ + + #include "intel-lpss.h" + ++/* Some DSDTs have an unused GEXP ACPI device conflicting with I2C4 resources */ ++static const struct pci_device_id ignore_resource_conflicts_ids[] = { ++ /* Microsoft Surface Go (version 1) I2C4 */ ++ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1182), }, ++ /* Microsoft Surface Go 2 I2C4 */ ++ { PCI_DEVICE_SUB(PCI_VENDOR_ID_INTEL, 0x9d64, 0x152d, 0x1237), }, ++ { } ++}; ++ + static int intel_lpss_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) + { +@@ -35,6 +44,9 @@ static int intel_lpss_pci_probe(struct pci_dev *pdev, + info->mem = &pdev->resource[0]; + info->irq = pdev->irq; + ++ if (pci_match_id(ignore_resource_conflicts_ids, pdev)) ++ info->ignore_resource_conflicts = true; ++ + pdev->d3cold_delay = 0; + + /* Probably it is enough to set this for iDMA capable devices only */ +diff --git a/drivers/mfd/intel-lpss.c b/drivers/mfd/intel-lpss.c +index 0e15afc39f54..cfbee2cfba6b 100644 +--- a/drivers/mfd/intel-lpss.c ++++ b/drivers/mfd/intel-lpss.c +@@ -401,6 +401,7 @@ int intel_lpss_probe(struct device *dev, + return ret; + + lpss->cell->swnode = info->swnode; ++ lpss->cell->ignore_resource_conflicts = info->ignore_resource_conflicts; + + intel_lpss_init_dev(lpss); + +diff --git a/drivers/mfd/intel-lpss.h b/drivers/mfd/intel-lpss.h +index 22dbc4aed793..062ce95b68b9 100644 +--- a/drivers/mfd/intel-lpss.h ++++ b/drivers/mfd/intel-lpss.h +@@ -19,6 +19,7 @@ struct software_node; + + struct intel_lpss_platform_info { + struct resource *mem; ++ bool ignore_resource_conflicts; + int irq; + unsigned long clk_rate; + const char *clk_con_id; +-- +2.34.1 + +From bade949c01a5cabfa8d652bc2af00db127e42241 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 6 Jan 2022 22:12:38 +0000 +Subject: [PATCH] platform/x86: int3472: Add board data for Surface Go 3 + +The Surface Go 3 needs some board data in order to configure the +TPS68470 PMIC - add entries to the tables in tps68470_board_data.c +that define the configuration that's needed. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + .../x86/intel/int3472/tps68470_board_data.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c +index 96954a789bb8..2dcadfa62196 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c ++++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c +@@ -82,6 +82,12 @@ static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = + .tps68470_regulator_pdata = &surface_go_tps68470_pdata, + }; + ++static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data = { ++ .dev_name = "i2c-INT3472:01", ++ .tps68470_gpio_lookup_table = &surface_go_tps68470_gpios, ++ .tps68470_regulator_pdata = &surface_go_tps68470_pdata, ++}; ++ + static const struct dmi_system_id int3472_tps68470_board_data_table[] = { + { + .matches = { +@@ -97,6 +103,13 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = { + }, + .driver_data = (void *)&surface_go_tps68470_board_data, + }, ++ { ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), ++ }, ++ .driver_data = (void *)&surface_go3_tps68470_board_data, ++ }, + { } + }; + +-- +2.34.1 + diff --git a/patches/5.16/0011-amd-gpio.patch b/patches/5.16/0011-amd-gpio.patch new file mode 100644 index 000000000..10f4801ed --- /dev/null +++ b/patches/5.16/0011-amd-gpio.patch @@ -0,0 +1,109 @@ +From 78dd3fef224a7f8e06e8cf502a60ed901cce50ac 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 5b6d1a95776f..0a05e196419a 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1152,6 +1153,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 +@@ -1207,6 +1219,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.34.1 + +From 67b587ef8f6136f44c51c3d23233c29d64e05a6c 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 0a05e196419a..35de5613980a 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1155,12 +1155,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.34.1 + diff --git a/patches/5.16/0012-misc-fixes.patch b/patches/5.16/0012-misc-fixes.patch new file mode 100644 index 000000000..95c49c0ba --- /dev/null +++ b/patches/5.16/0012-misc-fixes.patch @@ -0,0 +1,53 @@ +From 79c1f0e58b463796983080bb5b6087ff16891163 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 8 Dec 2021 16:22:50 +0100 +Subject: [PATCH] acpi/battery: Add device HID and quirk for Microsoft Surface + Go 3 + +For some reason, the Microsoft Surface Go 3 uses the standard ACPI +interface for battery information, but does not use the standard PNP0C0A +HID. Instead it uses MSHW0146 as identifier. Add that ID to the driver +as this seems to work well. + +Additionally, the power state is not updated immediately after the AC +has been (un-)plugged, so add the respective quirk for that. + +Signed-off-by: Maximilian Luz +Patchset: misc-fixes +--- + drivers/acpi/battery.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c +index 8afa85d6eb6a..65882cb791a5 100644 +--- a/drivers/acpi/battery.c ++++ b/drivers/acpi/battery.c +@@ -59,6 +59,10 @@ MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + + static const struct acpi_device_id battery_device_ids[] = { + {"PNP0C0A", 0}, ++ ++ /* Microsoft Surface Go 3 */ ++ {"MSHW0146", 0}, ++ + {"", 0}, + }; + +@@ -1155,6 +1159,14 @@ static const struct dmi_system_id bat_dmi_table[] __initconst = { + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo MIIX 320-10ICR"), + }, + }, ++ { ++ /* Microsoft Surface Go 3 */ ++ .callback = battery_notification_delay_quirk, ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Go 3"), ++ }, ++ }, + {}, + }; + +-- +2.34.1 + diff --git a/patches/README.md b/patches/README.md index 0bb1f773d..c6bb9ed32 100644 --- a/patches/README.md +++ b/patches/README.md @@ -9,9 +9,8 @@ Please direct any pull-requests for new patches and kernel functionality to this ## Maintained Versions The currently maintained versions are -- [`5.14`](https://github.com/linux-surface/kernel/tree/v5.14-surface) (latest) -- [`5.10`](https://github.com/linux-surface/kernel/tree/v5.10-surface) (latest LTS), and -- [`4.19`](https://github.com/linux-surface/kernel/tree/v4.19-surface) (Surface LTS) +- [`5.16`](https://github.com/linux-surface/kernel/tree/v5.16-surface) (latest Arch Linux) +- [`5.15`](https://github.com/linux-surface/kernel/tree/v5.15-surface) (latest Ubuntu/Fedora) Any other versions are only included for historical purposes. Unmaintained versions will likely (if necessary with a bit of re-basing) still work, but will not have the latest changes (so please don't report bugs for those).