From 48401bba34b62b5dd44b84417dc2292a1909daa5 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 7 Apr 2026 13:33:46 +0200 Subject: [PATCH 01/25] Bluetooth: Add Broadcom channel priority commands Certain Broadcom bluetooth chips (bcm4377/bcm4378/bcm438) need ACL streams carrying audio to be set as "high priority" using a vendor specific command to prevent 10-ish second-long dropouts whenever something does a device scan. This patch sends the command when the socket priority is set to TC_PRIO_INTERACTIVE, as BlueZ does for audio. Signed-off-by: Sasha Finkelstein --- MAINTAINERS | 2 ++ drivers/bluetooth/hci_bcm4377.c | 2 ++ include/net/bluetooth/bluetooth.h | 4 ++++ include/net/bluetooth/hci_core.h | 11 +++++++++++ net/bluetooth/Kconfig | 7 +++++++ net/bluetooth/Makefile | 1 + net/bluetooth/brcm.c | 29 +++++++++++++++++++++++++++++ net/bluetooth/brcm.h | 17 +++++++++++++++++ net/bluetooth/hci_conn.c | 27 +++++++++++++++++++++++++++ net/bluetooth/l2cap_sock.c | 13 +++++++++++++ 10 files changed, 113 insertions(+) create mode 100644 net/bluetooth/brcm.c create mode 100644 net/bluetooth/brcm.h diff --git a/MAINTAINERS b/MAINTAINERS index 485d69745ce7c3..cde843b4f4d966 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2593,6 +2593,8 @@ F: include/dt-bindings/pinctrl/apple.h F: include/linux/mfd/macsmc.h F: include/linux/soc/apple/* F: include/uapi/drm/asahi_drm.h +F: net/bluetooth/brcm.c +F: net/bluetooth/brcm.h ARM/ARTPEC MACHINE SUPPORT M: Jesper Nilsson diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 925d0a6359453e..5f79920c030681 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -2397,6 +2397,8 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (bcm4377->hw->broken_le_ext_adv_report_phy) hci_set_quirk(hdev, HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_PHY); + hci_set_brcm_capable(hdev); + pci_set_drvdata(pdev, bcm4377); hci_set_drvdata(hdev, bcm4377); SET_HCIDEV_DEV(hdev, &pdev->dev); diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 3faea66b19799a..5d82944370e427 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -458,6 +458,7 @@ struct l2cap_ctrl { }; struct hci_dev; +struct hci_conn; typedef void (*hci_req_complete_t)(struct hci_dev *hdev, u8 status, u16 opcode); typedef void (*hci_req_complete_skb_t)(struct hci_dev *hdev, u8 status, @@ -470,6 +471,9 @@ void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status, int hci_ethtool_ts_info(unsigned int index, int sk_proto, struct kernel_ethtool_ts_info *ts_info); +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen); + #define HCI_REQ_START BIT(0) #define HCI_REQ_SKB BIT(1) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index aa600fbf9a5359..4d72d5068b3e63 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -642,6 +642,10 @@ struct hci_dev { bool aosp_quality_report; #endif +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + bool brcm_capable; +#endif + int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev); @@ -1791,6 +1795,13 @@ static inline void hci_set_aosp_capable(struct hci_dev *hdev) #endif } +static inline void hci_set_brcm_capable(struct hci_dev *hdev) +{ +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + hdev->brcm_capable = true; +#endif +} + static inline void hci_devcd_setup(struct hci_dev *hdev) { #ifdef CONFIG_DEV_COREDUMP diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index ee6457d1a5eed3..b611942c7b8ffd 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -107,6 +107,13 @@ config BT_AOSPEXT This options enables support for the Android Open Source Project defined HCI vendor extensions. +config BT_BRCMEXT + bool "Enable Broadcom extensions" + depends on BT + help + This option enables support for the Broadcom defined HCI + vendor extensions. + config BT_DEBUGFS bool "Export Bluetooth internals in debugfs" depends on BT && DEBUG_FS diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 41049b280887db..d402645dfb7da0 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -23,5 +23,6 @@ bluetooth-$(CONFIG_BT_LE) += iso.o bluetooth-$(CONFIG_BT_LEDS) += leds.o bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o +bluetooth-$(CONFIG_BT_BRCMEXT) += brcm.o bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o diff --git a/net/bluetooth/brcm.c b/net/bluetooth/brcm.c new file mode 100644 index 00000000000000..9aa0a265ab3d6b --- /dev/null +++ b/net/bluetooth/brcm.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#include +#include + +#include "brcm.h" + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + struct sk_buff *skb; + u8 cmd[3]; + + if (!hdev->brcm_capable) + return 0; + + cmd[0] = handle; + cmd[1] = handle >> 8; + cmd[2] = !!enable; + + skb = hci_cmd_sync(hdev, 0xfc57, sizeof(cmd), cmd, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + kfree_skb(skb); + return 0; +} diff --git a/net/bluetooth/brcm.h b/net/bluetooth/brcm.h new file mode 100644 index 00000000000000..fdaee63bd1d23c --- /dev/null +++ b/net/bluetooth/brcm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable); + +#else + +static inline int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + return 0; +} + +#endif diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 54eabaa46960b8..ee1d28235ddb79 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -35,6 +35,7 @@ #include #include +#include "brcm.h" #include "smp.h" #include "eir.h" @@ -3156,6 +3157,32 @@ int hci_conn_set_phy(struct hci_conn *conn, u32 phys) } } +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen) { + int val; + bool old_high, new_high, changed; + + if (level != SOL_SOCKET) + return 0; + + if (optname != SO_PRIORITY) + return 0; + + if (optlen < sizeof(int)) + return -EINVAL; + + if (copy_from_sockptr(&val, optval, sizeof(val))) + return -EFAULT; + + old_high = sk->sk_priority >= TC_PRIO_INTERACTIVE; + new_high = val >= TC_PRIO_INTERACTIVE; + changed = old_high != new_high; + if (!changed) + return 0; + + return brcm_set_high_priority(conn->hdev, conn->handle, new_high); +} + static int abort_conn_sync(struct hci_dev *hdev, void *data) { struct hci_conn *conn = data; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index c138aa4ae26690..fb197cafa0eaca 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -896,6 +896,16 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, BT_DBG("sk %p", sk); + if (level == SOL_SOCKET) { + conn = chan->conn; + if (conn) + err = hci_conn_setsockopt(conn->hcon, sock->sk, level, + optname, optval, optlen); + if (err) + return err; + return sock_setsockopt(sock, level, optname, optval, optlen); + } + if (level == SOL_L2CAP) return l2cap_sock_setsockopt_old(sock, optname, optval, optlen); @@ -1979,6 +1989,9 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + if (sock) + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); + chan = l2cap_chan_create(); if (!chan) { sk_free(sk); From 3941122b449b853080da1e16d2d2577f65819312 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 21 May 2026 10:30:50 +0200 Subject: [PATCH 02/25] Fail the build on RUST=y and RUST_IS_AVAILABLE=n The current approach of silently disabling all rust drivers if the toolchain is missing results in users that try to compile their own kernels getting a "successful" build and then being confused about where did their drivers go. In comparison, missing openssl results in a build failure, not a disappearance of everything that depends on it. This also means that allyesconfig will depend on rust, but since the rust experiment concluded with "rust is here to stay", i believe that allyesconfig should be building rust drivers too. Signed-off-by: Sasha Finkelstein --- Documentation/rust/quick-start.rst | 6 +++--- init/Kconfig | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index a6ec3fa94d3307..764c81d0dd5926 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -321,9 +321,9 @@ Configuration ------------- ``Rust support`` (``CONFIG_RUST``) needs to be enabled in the ``General setup`` -menu. The option is only shown if a suitable Rust toolchain is found (see -above), as long as the other requirements are met. In turn, this will make -visible the rest of options that depend on Rust. +menu. In turn, this will make visible the rest of options that depend on Rust. +You can check the value of ``RUST_IS_AVAILABLE`` to determine if your toolchain +is configured correctly. Afterwards, go to:: diff --git a/init/Kconfig b/init/Kconfig index 2937c4d308aecb..f7d4c7ea764fc8 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -2190,7 +2190,6 @@ config PROFILING config RUST bool "Rust support" depends on HAVE_RUST - depends on RUST_IS_AVAILABLE select EXTENDED_MODVERSIONS if MODVERSIONS depends on !MODVERSIONS || GENDWARFKSYMS depends on !GCC_PLUGIN_RANDSTRUCT From 818b181448ec3516cfc9f34209068a7702368fdd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 9 May 2026 11:58:52 +0200 Subject: [PATCH 03/25] driver-core: Add error message to device_links_missing_supplier WARN() Signed-off-by: Janne Grunau --- drivers/base/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index 4f27d350b5e69d..cefa596ee10755 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1003,6 +1003,7 @@ static void device_links_missing_supplier(struct device *dev) if (link->supplier->links.status == DL_DEV_DRIVER_BOUND) { WRITE_ONCE(link->status, DL_STATE_AVAILABLE); } else { + dev_err(dev, "devices misses supplier %s\n", dev_name(link->supplier)); WARN_ON(!device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)); WRITE_ONCE(link->status, DL_STATE_DORMANT); } From 9c73b2fa6c6112c93604669390609210a9069892 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:16:44 +0200 Subject: [PATCH 04/25] dt-bindings: gpio: apple,smc: Add compatible for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. Add a second compatible to handle this keys with an additional macsmc-gpio instance. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml index 42b1bc0a10c97a..b4063a9dd1248c 100644 --- a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml @@ -14,7 +14,9 @@ description: properties: compatible: - const: apple,smc-gpio + enum: + - apple,smc-gpio + - apple,smc-low-gpio gpio-controller: true From ed9f6ac1415b294ed8376cab3915156a2d1e95ea Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 29 May 2026 20:54:16 +0200 Subject: [PATCH 05/25] gpio: gpio-macsmc: Support 'gp00' GPIO keys Add support for SMC GPIO keys with a lower letter 'p' via the "apple,smc-low-gpio" compatible. This adds support for a second macsmc-gpio controller using 'gp00' keys. These keys are used on Apple M3 Pro and Max MacBooks in the controller for keyboard and trackpad and for the built-in DisplayPort to HDMI converter. Signed-off-by: Janne Grunau --- drivers/gpio/gpio-macsmc.c | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c index b0952d066a9dd0..c3ca445a85ac9d 100644 --- a/drivers/gpio/gpio-macsmc.c +++ b/drivers/gpio/gpio-macsmc.c @@ -75,6 +75,7 @@ struct macsmc_gpio { struct gpio_chip gc; int first_index; + smc_key base_key; }; static int macsmc_gpio_nr(smc_key key) @@ -88,15 +89,15 @@ static int macsmc_gpio_nr(smc_key key) return low | (high << 4); } -static int macsmc_gpio_key(unsigned int offset) +static int macsmc_gpio_key(smc_key base_key, unsigned int offset) { - return _SMC_KEY("gP\0\0") | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); + return base_key | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); } static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) { struct apple_smc *smc = smcgp->smc; - smc_key key = macsmc_gpio_key(0); + smc_key key = macsmc_gpio_key(smcgp->base_key, 0); smc_key first_key, last_key; int start, count, ret; @@ -143,7 +144,7 @@ static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 val; int ret; @@ -163,7 +164,7 @@ static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 cmd, val; int ret; @@ -186,7 +187,7 @@ static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); int ret; value |= CMD_OUTPUT; @@ -217,7 +218,7 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, if (ret < 0) return ret; - if (key > SMC_KEY(gPff)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) break; gpio_nr = macsmc_gpio_nr(key); @@ -232,10 +233,15 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, return 0; } +struct macsmc_gpio_of_match_data { + smc_key base_key; +}; + static int macsmc_gpio_probe(struct platform_device *pdev) { struct macsmc_gpio *smcgp; struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + const struct macsmc_gpio_of_match_data *data = of_device_get_match_data(&pdev->dev); smc_key key; int ret; @@ -245,6 +251,7 @@ static int macsmc_gpio_probe(struct platform_device *pdev) smcgp->dev = &pdev->dev; smcgp->smc = smc; + smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0"); smcgp->first_index = macsmc_gpio_find_first_gpio_index(smcgp); if (smcgp->first_index < 0) @@ -254,12 +261,15 @@ static int macsmc_gpio_probe(struct platform_device *pdev) if (ret < 0) return ret; - if (key > macsmc_gpio_key(MAX_GPIO - 1)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) return -ENODEV; dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key); - smcgp->gc.label = "macsmc-pmu-gpio"; + if (device_is_compatible(&pdev->dev, "apple,smc-low-gpio")) + smcgp->gc.label = "macsmc-pmu-low-gpio"; + else + smcgp->gc.label = "macsmc-pmu-gpio"; smcgp->gc.owner = THIS_MODULE; smcgp->gc.get = macsmc_gpio_get; smcgp->gc.set = macsmc_gpio_set; @@ -273,8 +283,23 @@ static int macsmc_gpio_probe(struct platform_device *pdev) return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp); } +static const struct macsmc_gpio_of_match_data macsmc_gpio_up_data = { + .base_key = _SMC_KEY("gP\0\0"), +}; + +static const struct macsmc_gpio_of_match_data macsmc_gpio_low_data = { + .base_key = _SMC_KEY("gp\0\0"), +}; + static const struct of_device_id macsmc_gpio_of_table[] = { - { .compatible = "apple,smc-gpio", }, + { + .compatible = "apple,smc-gpio", + .data = &macsmc_gpio_up_data, + }, + { + .compatible = "apple,smc-low-gpio", + .data = &macsmc_gpio_low_data, + }, {} }; MODULE_DEVICE_TABLE(of, macsmc_gpio_of_table); From b19682836884a6ade8f7565bc90b5d6a8ee520a9 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:20:39 +0200 Subject: [PATCH 06/25] mfd: macsmc: Add second gpio subdevice for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. These keys are handled by an additional macsmc-gpio instance using the "apple,smc-low-gpio" compatible. Signed-off-by: Janne Grunau --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 358feec2d088fc..fd34dae70452ae 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -48,6 +48,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_NAME("macsmc-input"), MFD_CELL_NAME("macsmc-power"), MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), + MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"), MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), From 9015099fc457dbd67181f415b257c201f89ee87c Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sun, 14 Jun 2026 22:34:12 +0200 Subject: [PATCH 07/25] phy: apple: atc: Handle dummy pipehandler transisions Signed-off-by: Janne Grunau --- drivers/phy/apple/atc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c index 4156fabad742cc..6d8888732a83fb 100644 --- a/drivers/phy/apple/atc.c +++ b/drivers/phy/apple/atc.c @@ -1133,6 +1133,8 @@ static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy, bool host) case ATCPHY_PIPEHANDLER_STATE_USB4: dev_warn(atcphy->dev, "ATCPHY_PIPEHANDLER_STATE_USB4 not implemented; falling back to USB2\n"); + fallthrough; + case ATCPHY_PIPEHANDLER_STATE_DUMMY: ret = atcphy_configure_pipehandler_dummy(atcphy); atcphy->pipehandler_up = false; break; From 8b2a84ceab06ee246b0ae329b2431beb029830c8 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 15 Jul 2026 12:29:19 +0200 Subject: [PATCH 08/25] fixup! media: apple: add avd driver Signed-off-by: Janne Grunau --- drivers/media/platform/apple/avd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/apple/avd/Kconfig b/drivers/media/platform/apple/avd/Kconfig index 43ec5fa52d4cd1..930b89d546cf56 100644 --- a/drivers/media/platform/apple/avd/Kconfig +++ b/drivers/media/platform/apple/avd/Kconfig @@ -4,7 +4,7 @@ config VIDEO_APPLE_AVD tristate "Apple Silicon Video Decoding driver" depends on VIDEO_DEV depends on MEDIA_CONTROLLER - depends on ARCH_APPLE || COMPILE_TEST + depends on ARCH_APPLE || (COMPILE_TEST && 64BIT) depends on OF_ADDRESS depends on V4L_PLATFORM_DRIVERS select V4L2_MEM2MEM_DEV From 18cc1cbc9c1de56cdee4e3f77495de2f9d99feae Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 15 Jul 2026 12:39:16 +0200 Subject: [PATCH 09/25] fixup! drm: apple: dptxep: Implement drive settings stuff Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index 384e3906494edc..e4939adcda1b2c 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -225,8 +225,8 @@ dptxport_call_set_drive_settings(struct apple_epic_service *service, request->unk1, request->unk2, request->unk3, request->unk4, request->unk5, request->unk6, request->unk7); - dptx->drive_settings[0] = cpu_to_le32(reply->unk5); - dptx->drive_settings[1] = cpu_to_le32(reply->unk7); + dptx->drive_settings[0] = le32_to_cpu(reply->unk5); + dptx->drive_settings[1] = le32_to_cpu(reply->unk7); return 0; } From c3301c32be071097e321e62ce503afe432f70ac1 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Wed, 15 Jul 2026 12:41:20 +0200 Subject: [PATCH 10/25] fixup! drm: apple: dptx: Fix get_drive_settings retcode Signed-off-by: Janne Grunau --- drivers/gpu/drm/apple/dptxep.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c index e4939adcda1b2c..1ea00602d476d8 100644 --- a/drivers/gpu/drm/apple/dptxep.c +++ b/drivers/gpu/drm/apple/dptxep.c @@ -193,7 +193,7 @@ dptxport_call_get_drive_settings(struct apple_epic_service *service, * retcode appears to be lane count, seeing 2 for USB-C dp alt mode * with lanes splitted for DP/USB3. */ - if (cpu_to_le32(reply->retcode) != dptx->lane_count) + if (le32_to_cpu(reply->retcode) != dptx->lane_count) dev_err(service->ep->dcp->dev, "get_drive_settings: unexpected retcode %d\n", reply->retcode); From 6226f1638b0beb3dd080b5498e78bf60fe5bbdd9 Mon Sep 17 00:00:00 2001 From: sofus Date: Wed, 15 Jul 2026 11:13:29 +0200 Subject: [PATCH 11/25] media: apple: avd: make get_ref_buf shared Signed-off-by: sofus --- drivers/media/platform/apple/avd/avd-drv.c | 19 +++++++++++++++++++ drivers/media/platform/apple/avd/avd-vp9.c | 18 ------------------ drivers/media/platform/apple/avd/avd.h | 2 ++ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index 773de35ce883a0..efc7aa86db1868 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -97,6 +97,25 @@ void avd_buf_free(struct avd_dev *avd, struct avd_buf *buf) memset(buf, 0, sizeof(*buf)); } +struct avd_decoded_buffer * +get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp) +{ + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; + struct vb2_buffer *buf; + + /* + * If a ref is unused or invalid, address of current destination + * buffer is returned. + */ + buf = vb2_find_buffer(cap_q, timestamp); + if (!buf) + buf = &dst->vb2_buf; + + return vb2_to_avd_decoded_buf(buf); +} + + static int avd_reset(struct avd_dev *avd) { int ret = 0; diff --git a/drivers/media/platform/apple/avd/avd-vp9.c b/drivers/media/platform/apple/avd/avd-vp9.c index dbc07aafa09008..8877511db78e7b 100644 --- a/drivers/media/platform/apple/avd/avd-vp9.c +++ b/drivers/media/platform/apple/avd/avd-vp9.c @@ -154,24 +154,6 @@ struct avd_vp9_ctx { u8 submit_num; }; -static struct avd_decoded_buffer * -get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp) -{ - struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; - struct vb2_queue *cap_q = &m2m_ctx->cap_q_ctx.q; - struct vb2_buffer *buf; - - /* - * If a ref is unused or invalid, address of current destination - * buffer is returned. - */ - buf = vb2_find_buffer(cap_q, timestamp); - if (!buf) - buf = &dst->vb2_buf; - - return vb2_to_avd_decoded_buf(buf); -} - static void set_refs(struct avd_ctx *ctx, struct avd_vp9_run *run) { const struct v4l2_ctrl_vp9_frame *frame = run->decode_params; diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index a736eaa2cc5213..88e7c77cf880dd 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -93,6 +93,8 @@ vb2_to_avd_decoded_buf(struct vb2_buffer *buf) { return container_of(buf, struct avd_decoded_buffer, base.vb.vb2_buf); } +struct avd_decoded_buffer * +get_ref_buf(struct avd_ctx *ctx, struct vb2_v4l2_buffer *dst, u64 timestamp); struct avd_coded_fmt_ops { void (*adjust_decoded_fmt)(struct avd_ctx *ctx, From 3f91b41f6e92daefb8b3f76fe56b9503c3c5ebb6 Mon Sep 17 00:00:00 2001 From: sofus Date: Wed, 15 Jul 2026 11:14:59 +0200 Subject: [PATCH 12/25] fixup! media: apple: avd: vp9: remove undefined var Signed-off-by: sofus --- drivers/media/platform/apple/avd/avd-vp9.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/apple/avd/avd-vp9.c b/drivers/media/platform/apple/avd/avd-vp9.c index 8877511db78e7b..fb423616aa7726 100644 --- a/drivers/media/platform/apple/avd/avd-vp9.c +++ b/drivers/media/platform/apple/avd/avd-vp9.c @@ -340,16 +340,16 @@ static void set_header(struct avd_ctx *ctx, struct avd_vp9_run *run) if (!(avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE)) pusha(vp9_ctx->bufs.pipe_state.addr, "pipe_state", 0); - pusha(vp9_ctx->bufs.color[0].addr, "hdr_e8_sps0_tile_addr_lsb8", i); - pusha(vp9_ctx->bufs.color[1].addr, "hdr_e8_sps0_tile_addr_lsb8", i); + pusha(vp9_ctx->bufs.color[0].addr, "hdr_e8_sps0_tile_addr_lsb8", 0); + pusha(vp9_ctx->bufs.color[1].addr, "hdr_e8_sps0_tile_addr_lsb8", 0); - pusha((u64)0, "hdr_e8_sps0_tile_addr_lsb8", i); + pusha((u64)0, "hdr_e8_sps0_tile_addr_lsb8", 0); /* not fatal */ - pusha(vp9_ctx->bufs.tiles[0].addr, "hdr_e8_sps0_tile_addr_lsb8", i); - pusha(vp9_ctx->bufs.tiles[1].addr, "hdr_e8_sps0_tile_addr_lsb8", i); + pusha(vp9_ctx->bufs.tiles[0].addr, "hdr_e8_sps0_tile_addr_lsb8", 0); + pusha(vp9_ctx->bufs.tiles[1].addr, "hdr_e8_sps0_tile_addr_lsb8", 0); /* fatal if missing / wrong */ - pusha(vp9_ctx->bufs.tiles[2].addr, "hdr_e8_sps0_tile_addr_lsb8", i); + pusha(vp9_ctx->bufs.tiles[2].addr, "hdr_e8_sps0_tile_addr_lsb8", 0); push(INST_DMA3, "cm3_dma_config_7"); From f4dd286f7888b348c757b9a2f28dd7bde4c3532b Mon Sep 17 00:00:00 2001 From: sofus Date: Wed, 15 Jul 2026 11:34:49 +0200 Subject: [PATCH 13/25] media: apple: avd: add hevc support Signed-off-by: sofus --- drivers/media/platform/apple/avd/Makefile | 2 +- drivers/media/platform/apple/avd/avd-drv.c | 19 +- drivers/media/platform/apple/avd/avd-hevc.c | 1158 +++++++++++++++++++ drivers/media/platform/apple/avd/avd-v4l2.c | 32 +- drivers/media/platform/apple/avd/avd.h | 9 + 5 files changed, 1208 insertions(+), 12 deletions(-) create mode 100644 drivers/media/platform/apple/avd/avd-hevc.c diff --git a/drivers/media/platform/apple/avd/Makefile b/drivers/media/platform/apple/avd/Makefile index 54ea67a589cdfd..6109d12b845274 100644 --- a/drivers/media/platform/apple/avd/Makefile +++ b/drivers/media/platform/apple/avd/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o +apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o avd-hevc.o obj-$(CONFIG_VIDEO_APPLE_AVD) += apple-avd.o diff --git a/drivers/media/platform/apple/avd/avd-drv.c b/drivers/media/platform/apple/avd/avd-drv.c index efc7aa86db1868..400cbe76cff8f7 100644 --- a/drivers/media/platform/apple/avd/avd-drv.c +++ b/drivers/media/platform/apple/avd/avd-drv.c @@ -46,6 +46,9 @@ void fill_rvra(struct avd_rvra *rvra, enum avd_image_fmt image_fmt, rvra->size = round_up(size0 + size1 + size2, 0x4000); rvra->size += (width < 1000 ? 0 : width < 1800 ? 2 : width < 3800 ? 3 : 9) * 0x4000; + /* TODO */ + rvra->size += + (height < 1000 ? 0 : height < 1800 ? 2 : height < 3800 ? 3 : 9) * 0x4000; rvra->offsets[1] = 0; rvra->offsets[0] = size0; @@ -445,6 +448,8 @@ static const struct avd_variant avd_t8103_variant = { .quirks = AVD_QUIRK_LSR | AVD_QUIRK_NO_PIPE_STATE, .vp_slot_offset = 0x4004, .submit_offset = 0x4014, + .submit_queue_max_offset = 0x4018, + .submit_queue_status_offset = 0x402c, /* (vp slots + 1) * 4 */ }; static const struct avd_variant avd_t6000_variant = { @@ -463,6 +468,8 @@ static const struct avd_variant avd_t6000_variant = { .quirks = AVD_QUIRK_LSR | AVD_QUIRK_NO_PIPE_STATE, .vp_slot_offset = 0xc, .submit_offset = 0x30, + .submit_queue_max_offset = 0x34, + .submit_queue_status_offset = 0x5c, }; static const struct avd_variant avd_t8112_variant = { @@ -481,6 +488,8 @@ static const struct avd_variant avd_t8112_variant = { .quirks = AVD_QUIRK_LSR, .vp_slot_offset = 0xc, .submit_offset = 0x30, + .submit_queue_max_offset = 0x34, + .submit_queue_status_offset = 0x5c, }; static const struct avd_variant avd_t6020_variant = { @@ -498,6 +507,8 @@ static const struct avd_variant avd_t6020_variant = { .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x30, + .submit_queue_max_offset = 0x34, + .submit_queue_status_offset = 0x5c, }; static const struct avd_variant avd_t8122_variant = { @@ -517,6 +528,8 @@ static const struct avd_variant avd_t8122_variant = { .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x40, + .submit_queue_max_offset = 0x44, + .submit_queue_status_offset = 0x78, /* v4 and never have fixed offsets */ }; static const struct avd_variant avd_t8140_variant = { @@ -537,6 +550,8 @@ static const struct avd_variant avd_t8140_variant = { .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x40, + .submit_queue_max_offset = 0x44, + .submit_queue_status_offset = 0x78, }; static const struct avd_variant avd_t8132_variant = { @@ -556,6 +571,8 @@ static const struct avd_variant avd_t8132_variant = { .revision = 4, .vp_slot_offset = 0xc, .submit_offset = 0x40, + .submit_queue_max_offset = 0x44, + .submit_queue_status_offset = 0x78, }; /* can also be derived from a version register */ @@ -675,8 +692,6 @@ static int avd_probe(struct platform_device *pdev) return ret; } -const struct avd_coded_fmt_ops avd_hevc_fmt_ops; - static void avd_remove(struct platform_device *pdev) { struct avd_dev *avd = platform_get_drvdata(pdev); diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c new file mode 100644 index 00000000000000..af0a4eabaf9f16 --- /dev/null +++ b/drivers/media/platform/apple/avd/avd-hevc.c @@ -0,0 +1,1158 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Apple AVD VPU codec driver + * + * Copyright The Asahi Linux Contributors + * Copyright 2023 Eileen Yoon + * + * Copyright (c) 2014 Rockchip Electronics Co., Ltd. + * Hertz Wong + * Herman Chen + * + * Copyright (C) 2014 Google, Inc. + * Tomasz Figa + */ + +#include "linux/dev_printk.h" +#include +#include + +#include +#include + +#include "avd.h" +#include "avd-inst.h" + +#define PPS_NUM 8 + +#define NEW_TILE_ID BIT(0) +#define NEW_SLICE BIT(1) + +static inline u32 sps_size(u32 w, u32 h) +{ + /* TODO: does it really need so much? */ + return (((w - 1) * (h - 1) / 0x10000) + 2) * 0x4000; +} + +struct avd_hevc_run { + struct avd_run base; + const struct v4l2_ctrl_hevc_slice_params *sl; + const struct v4l2_ctrl_hevc_decode_params *decode; + const struct v4l2_ctrl_hevc_sps *sps; + const struct v4l2_ctrl_hevc_pps *pps; + const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix; + const u32 *entry_point_offsets; + + int num_slices; + + struct run_addr { + dma_addr_t y; + dma_addr_t uv; + dma_addr_t sl; + dma_addr_t rvra; + dma_addr_t sps; + } addresses; +}; + +struct avd_hevc_ctx { + struct v4l2_ctrl_hevc_scaling_matrix scaling_matrix_cache; + + struct avd_h264_bufs { + struct avd_buf pps_tile[8]; + struct avd_buf inst; + struct avd_buf pipe_state; + } bufs; + + int submit_num; +}; + +/* clang-format off */ +static void stream_refs(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_decode_params *decode = run->decode; + const struct v4l2_ctrl_hevc_slice_params *sl = &run->sl[0]; + const struct v4l2_hevc_dpb_entry *dpb; + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + struct avd_dev *avd = ctx->dev; + struct avd_decoded_buffer *dst, *ref_buf; + + dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); + + push(INST_DMA2, "cm3_dma_config_6"); + pusha(hevc_ctx->bufs.pps_tile[1].addr, "hdr_9c_pps_tile_addr_lsb8", 7); + pusha(run->addresses.sps, "hdr_bc_sps_tile_addr_lsb8", + sl->slice_pic_order_cnt); + + push(INST_DMA3, "cm3_dma_config_7"); + push(INST_DMA3, "cm3_dma_config_8"); + push(INST_DMA3, "cm3_dma_config_9"); + push(INST_DMA3, "cm3_dma_config_a"); + + for (int i = 0; i < decode->num_active_dpb_entries; i++) { + dpb = &decode->dpb[i]; + + ref_buf = get_ref_buf(ctx, &dst->base.vb, dpb->timestamp); + + dma_addr_t rvra_addr = + vb2_dma_contig_plane_dma_addr(&ref_buf->base.vb.vb2_buf, 0) + + (ref_buf->base.vb.planes[0].length - sps_size(fmt_width(ctx), fmt_height(ctx)) - ref_buf->rvra.size); + + push((decode->num_active_dpb_entries - 1) << 28 + | 0x1000000 + | (!!(dpb->flags & V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) << 17) + | (((sl->slice_pic_order_cnt - dpb->pic_order_cnt_val) & 0x1ffff)), + "hdr_d0_ref_hdr"); + + push_rvra(avd, ctx, rvra_addr, ref_buf->rvra.offsets); + } +} + +static void set_scaling_lists(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_scaling_matrix *s = run->scaling_matrix; + struct avd_dev *avd = ctx->dev; + int i,j,k; + + u8 (*dc_16x16)[3] = (u8(*)[3])s->scaling_list_dc_coef_16x16; + u8 (*sc_4x4)[4][4] = (u8(*)[4][4])s->scaling_list_4x4; + u8 (*sc_8x8)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_8x8; + u8 (*sc_16x16)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_16x16; + u8 (*sc_32x32)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_32x32; + + /* + * this should presumably be how many of each are enabled? + * or some other scaling related thing + */ + push(0x127ffff, "hdr_7c_pps_scl_dims"); + + for (i = 0; i < 2; i++) + push(dc_16x16[i][0] << 16 + | dc_16x16[i][1] << 8 + | dc_16x16[i][2] << 0, "dc_16x16"); + + for (i = 0; i < 2; i++) + push(s->scaling_list_dc_coef_32x32[i] << 16, "dc_32x32"); + + /* transposed in stride 4 */ + for (i = 0; i < 6; i++) + for (j = 0; j < 4; j++) + push(sc_4x4[i][0][j] << 24 + | sc_4x4[i][1][j] << 16 + | sc_4x4[i][2][j] << 8 + | sc_4x4[i][3][j], "scaling_4x4"); + + /* transposed in stride 8 */ + for (i = 0; i < 6; i++) + for (j = 0; j < 2; j++) + for (k = 0; k < 8; k++) + push(sc_8x8[i][j][0][k] << 24 + | sc_8x8[i][j][1][k] << 16 + | sc_8x8[i][j][2][k] << 8 + | sc_8x8[i][j][3][k], "scaling_8x8"); + + for (i = 0; i < 6; i++) + for (j = 0; j < 2; j++) + for (k = 0; k < 8; k++) + push(sc_16x16[i][j][0][k] << 24 + | sc_16x16[i][j][1][k] << 16 + | sc_16x16[i][j][2][k] << 8 + | sc_16x16[i][j][3][k], "scaling_16x16"); + + for (i = 0; i < 2; i++) + for (j = 0; j < 2; j++) + for (k = 0; k < 8; k++) + push(sc_32x32[i][j][0][k] << 24 + | sc_32x32[i][j][1][k] << 16 + | sc_32x32[i][j][2][k] << 8 + | sc_32x32[i][j][3][k], "scaling_32x32"); + +} + +static void hevc_set_flags(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_decode_params *decode = run->decode; + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + struct avd_dev *avd = ctx->dev; + + u32 log2_ctb_size = ((sps->log2_min_luma_coding_block_size_minus3 + 3) + + sps->log2_diff_max_min_luma_coding_block_size); + + if (sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED) + push(0x1000 + | sps->pcm_sample_bit_depth_luma_minus1 << 8 + | sps->pcm_sample_bit_depth_chroma_minus1 << 4 + | sps->log2_min_pcm_luma_coding_block_size_minus3 << 2 + | (sps->log2_diff_max_min_pcm_luma_coding_block_size + + sps->log2_min_pcm_luma_coding_block_size_minus3) + , "hdr_30_sps_pcm"); + else + push(0, "hdr_30_sps_pcm"); + + /* + * RExt sets a few new flags here + */ + push((u32)BIT(3) + | !!(sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED) << 9, + "hdr_34_sps_flags"); + + push((log2_ctb_size - 3) << 3 + | pps->log2_parallel_merge_level_minus2 << 9 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED) << 12 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) << 13 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED) << 14 + | ((log2_ctb_size - pps->diff_cu_qp_delta_depth - 3) & 3) << 15 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED) << 17 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED) << 18 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED) << 19 + | !!(pps->flags & V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED) << 20 + | !!(!(decode->flags & V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC) + && sps->flags & V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) << 21 + , "hdr_5c_pps_flags"); + + push(swrap(pps->pps_cb_qp_offset, 1 << 5) << 5 + | swrap(pps->pps_cr_qp_offset, 1 << 5), + "hdr_60_pps_qp"); + + /* + * something like segment features? + */ + push(0, "hdr_64_zero"); + push(0, "hdr_68_zero"); + push(0, "hdr_6c_zero"); + push(0, "hdr_70_zero"); + push(0, "hdr_74_zero"); + push(0, "hdr_78_zero"); +} + +static void set_header(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + struct avd_dev *avd = ctx->dev; + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + u32 bytesperline; + u32 width = sps->pic_width_in_luma_samples; + u32 height = sps->pic_height_in_luma_samples; + + bool is_intra = run->sl[0].slice_type == V4L2_HEVC_SLICE_TYPE_I; + + push(0x2b000000 + | ctx->fifo_idx << 4 + | (avd->variant->revision == 3 ? 0x100 : 0x200), + "inst_fifo_start"); + + push(0x2db00000 + | 0x1000 + | (is_intra ? 0x2000 : 0) + | 0x2e0 + | (avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE ? 0 : 0x80000), + "hdr_34_start_hdr"); + + push(AVD_CODEC_HEVC, "hdr_50_mode"); + push(((height - 1) << 16) | (width - 1), "hdr_54_height_width"); + push(0, "hdr_58_pixfmt_zero"); + + push((((height - 1) >> 3) << 16) | ((width - 1) >> 3), + "hdr_28_height_width_shift3"); + + push(sps->chroma_format_idc << 24 + | sps->bit_depth_chroma_minus8 << 19 + | sps->bit_depth_luma_minus8 << 15 + | sps->log2_min_luma_coding_block_size_minus3 << 13 + | (sps->log2_diff_max_min_luma_coding_block_size + + sps->log2_min_luma_coding_block_size_minus3) << 11 + | sps->log2_min_luma_transform_block_size_minus2 << 9 + | (sps->log2_diff_max_min_luma_transform_block_size + + + sps->log2_min_luma_transform_block_size_minus2) << 7 + | sps->max_transform_hierarchy_depth_inter << 4 + | sps->max_transform_hierarchy_depth_intra << 1 + | !!(sps->flags & V4L2_HEVC_SPS_FLAG_AMP_ENABLED), + "hdr_2c_sps_txfm"); + + hevc_set_flags(ctx, run); + push(0x300000 + | (avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE ? 0 : 0x30), + "hdr_98_const_30"); + + push(INST_DMA2, "cm3_dma_config_1"); + push(INST_DMA1, "cm3_dma_config_2"); + + if (avd->variant->revision == 3) + push(0, "zero"); + + push(INST_DMA1, "cm3_dma_config_2"); + push(INST_DMA2, "cm3_dma_config_3"); + + if (avd->variant->revision == 3) + push(0, "zero"); + else if (!(avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE)) + pusha(hevc_ctx->bufs.pipe_state.addr, "pipe_state", 0); + + pusha(hevc_ctx->bufs.pps_tile[0].addr, "hdr_dc_pps_tile_addr_lsb8", 0); + pusha(hevc_ctx->bufs.pps_tile[2].addr, "hdr_dc_pps_tile_addr_lsb8", 1); + pusha(hevc_ctx->bufs.pps_tile[3].addr, "hdr_dc_pps_tile_addr_lsb8", 2); + + if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) { + pusha(hevc_ctx->bufs.pps_tile[4].addr, "hdr_dc_pps_tile_addr_lsb8", 3); + pusha(hevc_ctx->bufs.pps_tile[5].addr, "hdr_dc_pps_tile_addr_lsb8", 4); + pusha(hevc_ctx->bufs.pps_tile[6].addr, "hdr_dc_pps_tile_addr_lsb8", 8); + pusha(hevc_ctx->bufs.pps_tile[7].addr, "hdr_dc_pps_tile_addr_lsb8", 9); + } else { + pusha(0, "", 3); + pusha(0, "", 4); + pusha(hevc_ctx->bufs.pps_tile[4].addr, "hdr_dc_pps_tile_addr_lsb8", 8); + pusha(0, "", 9); + } + + push(INST_DMA3, "cm3_dma_config_5"); + + push_rvra(avd, ctx, run->addresses.rvra, ctx->rvra.offsets); + + push(0, "cm3_mark_end_section"); + + if (avd->variant->revision != 3) + push(0, "cm3_mark_end_section"); + + bytesperline = ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline; + if (avd->variant->quirks & AVD_QUIRK_LSR) + bytesperline = bytesperline >> 4; + + pusha(run->addresses.y, "hdr_1b4_y_addr_lsb8", 0); + push(bytesperline, "hdr_1bc_width_align"); + pusha(run->addresses.uv, "hdr_1b8_uv_addr_lsb8", 0); + push(bytesperline, "hdr_1c0_width_align"); + push(0, ""); + push(((height - 1) << 16) | (width - 1), "hdr_54_height_width"); + + if (!is_intra) + stream_refs(ctx, run); + + if ((sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED)) + set_scaling_lists(ctx, run); + else + push(0, "cm3_mark_end_section"); + +} + +static void stream_weights(struct avd_ctx *ctx, struct avd_hevc_run *run, + const struct v4l2_ctrl_hevc_slice_params *sl) +{ + int luma_weight_denom, chroma_weight_denom; + u8 chroma_log2_weight_denom; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + const struct v4l2_hevc_pred_weight_table *pred = &sl->pred_weight_table; + struct avd_dev *avd = ctx->dev; + bool has_luma_weights = ((pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED) + && sl->slice_type == V4L2_HEVC_SLICE_TYPE_P) + || ((pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED) + && sl->slice_type == V4L2_HEVC_SLICE_TYPE_B); + + const s8 (*delta_chroma_weights)[2]; + const s8 (*chroma_offsets)[2]; + const s8 *delta_luma_weights; + const s8 *luma_offsets; + + if (!has_luma_weights) { + push(0x2dd00000, "slc_76c_cmd_weights_denom"); + return; + } + + chroma_log2_weight_denom = pred->luma_log2_weight_denom + + pred->delta_chroma_log2_weight_denom; + + /* + * TODO: this looks weird + */ + push(0x2dd00000 + | (!has_luma_weights << 7) + | (has_luma_weights << 6) + | (!has_luma_weights << 5) + | (pred->luma_log2_weight_denom << 3) + | (chroma_log2_weight_denom & 7) + ,"slc_76c_cmd_weights_denom"); + + luma_weight_denom = 1 << pred->luma_log2_weight_denom; + chroma_weight_denom = 1 << chroma_log2_weight_denom; + + /* comes from 7.4.7.3 */ + + for (int y = 0; y < 2; y++) { + if (y == 1 && sl->slice_type != V4L2_HEVC_SLICE_TYPE_B) + break; + luma_offsets = y == 0 ? pred->luma_offset_l0 : pred->luma_offset_l1; + delta_luma_weights = (s8(*))(y == 0 ? + pred->delta_luma_weight_l0 : pred->delta_luma_weight_l1); + chroma_offsets = (s8(*)[2])(y == 0 ? + pred->chroma_offset_l0 : pred->chroma_offset_l1); + delta_chroma_weights = (s8(*)[2])(y == 0 ? + pred->delta_chroma_weight_l0 : pred->delta_chroma_weight_l1); + + int to = y == 0 ? sl->num_ref_idx_l0_active_minus1 + : sl->num_ref_idx_l1_active_minus1; + for (int i = 0; i < to + 1; i++) { + /* Avd only expects offsets/weights if they are not the default + * ones, otherwise we get artifacts */ + if((delta_luma_weights[i] != 0) || (luma_offsets[i] != 0)) { + push(0x2de00000 + | 1 << 14 + | y << 13 + | i << 9 + | ((delta_luma_weights[i] + luma_weight_denom) & 0x1ff), + "slc_luma_weights"); + push(0x2df00000 + | swrap(luma_offsets[i], 0x10000), + "slc_luma_offsets"); + } + + if ((delta_chroma_weights[i][0] != 0) + || (chroma_offsets[i][0] != 0) + || (delta_chroma_weights[i][1] != 0) + || (chroma_offsets[i][1] != 0)) { + push(0x2de00000 + | 2 << 14 + | y << 13 + | i << 9 + | ((delta_chroma_weights[i][0] + chroma_weight_denom) & 0x1ff), + "slc_chroma_weights[0]"); + push(0x2df00000 + | swrap(chroma_offsets[i][0], 0x10000), + "slc_chroma_offsets[0]"); + push(0x2de00000 + | 3 << 14 + | y << 13 + | i << 9 + | ((delta_chroma_weights[i][1] + chroma_weight_denom) & 0x1ff), + "slc_chroma_weights[1]"); + push(0x2df00000 + | swrap(chroma_offsets[i][1], 0x10000), + "slc_chroma_offsets[1]"); + } + } + } +} + +static void stream_slice_dqtblk(struct avd_ctx *ctx, struct avd_hevc_run *run, + const struct v4l2_ctrl_hevc_slice_params *sl) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + struct avd_dev *avd = ctx->dev; + + push(0x2d900000 + | (((pps->init_qp_minus26 + 26 + sl->slice_qp_delta) << 10) & 0x1fc00) + | swrap(pps->pps_cb_qp_offset + sl->slice_cb_qp_offset, 32) << 5 + | swrap(pps->pps_cr_qp_offset + sl->slice_cr_qp_offset, 32) + ,"slc_bcc_cmd_quantization"); + + push(0x2da00000 + | !!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA) << 6 + | !!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA) << 7 + | swrap(sl->slice_tc_offset_div2, 16) << 8 + | swrap(sl->slice_beta_offset_div2, 16) << 12 + /* i wonder what this should actually be */ + | !!(!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED) + && (sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED + || sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA + || pps->flags & (V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED | V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT))) << 16 + | !!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED) << 17 + | !!(!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) + || (pps->flags & V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED)) << 18 + | !!((sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED) + && ! (sps->flags & V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED)) << 19 + , "slc_bd0_cmd_deblocking_filter"); + + if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_B + || sl->slice_type == V4L2_HEVC_SLICE_TYPE_P) { + for (int i = 0; i < sl->num_ref_idx_l0_active_minus1 + 1; i++) + push(0x2dc00000 + | (0 << 8) + | ((i & 0xf) << 4) + | (sl->ref_idx_l0[i] & 0xf), + "reference_frames_l0"); + if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_B) + for (int i = 0; i < sl->num_ref_idx_l1_active_minus1 + 1; i++) + push(0x2dc00000 + | (1 << 8) + | ((i & 0xf) << 4) + | (sl->ref_idx_l1[i] & 0xf), + "reference_frames_l1"); + + stream_weights(ctx, run, sl); + } +} +static void stream_slice_mv(struct avd_ctx *ctx, struct avd_hevc_run *run, + const struct v4l2_ctrl_hevc_slice_params *sl, bool is_first) +{ + const struct v4l2_ctrl_hevc_decode_params *decode = run->decode; + struct avd_dev *avd = ctx->dev; + struct avd_decoded_buffer *dst, *ref; + bool ref_valid; + const u8 *ref_list; + + if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_I) { + push(0x2d000000 + | !!(sl->slice_type == V4L2_HEVC_SLICE_TYPE_I) << 17 + , "slc_a8c_cmd_ref_type"); + return; + } + /* bidirectional prediction */ + + ref_list = sl->slice_type == V4L2_HEVC_SLICE_TYPE_P ? sl->ref_idx_l0 + : sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0 + ? sl->ref_idx_l0 : sl->ref_idx_l1; + + dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf); + ref = get_ref_buf(ctx, &dst->base.vb, + decode->dpb[ref_list[sl->collocated_ref_idx]].timestamp); + + ref_valid = + !(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT) + && (sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) + && is_first + && !ref->hevc.is_intra; + + u32 x = 0; + + x |= (5 - sl->five_minus_max_num_merge_cand) << 1; + x |= !!((sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) + && !(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0)) << 4; + x |= !!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT) << 5; + if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_B) { + x |= !!!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO) << 6; + x |= sl->num_ref_idx_l1_active_minus1 << 7; + } + x |= sl->num_ref_idx_l0_active_minus1 << 11; + + x |= !!((sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) + || (sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT)) << 15; + + push(0x2d000000 + | x + | !!(sl->slice_type == V4L2_HEVC_SLICE_TYPE_P) << 16 + | ref_valid << 18 + , "slc_a8c_cmd_ref_type"); + + if (ref_valid) { + dma_addr_t sps_tile_addr = vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf, 0) + + (ref->base.vb.planes[0].length - sps_size(fmt_width(ctx), fmt_height(ctx))); + pusha(sps_tile_addr, "slc_bd4_sps_tile_addr2_lsb8", + decode->dpb[ref_list[sl->collocated_ref_idx]].pic_order_cnt_val); + } +} + +static void set_slice(struct avd_ctx *ctx, struct avd_hevc_run *run, + const struct v4l2_ctrl_hevc_slice_params *sl, + u32 size, u32 offset, u32 flags) +{ + struct avd_dev *avd = ctx->dev; + dma_addr_t slc_addr = run->addresses.sl + offset + sl->data_byte_offset; + push(0x2d800000 + | flags + | (u32)(slc_addr >> 32), "cm3_cmd_set_coded_slice"); + push((u32)(slc_addr & 0xffffffff), "slc_bd8_slice_addr"); + push(size, "slc_bdc_slice_size"); +} + +static int submit_slice_segment(struct avd_ctx *ctx, struct avd_hevc_run *run, + const struct v4l2_ctrl_hevc_slice_params *sl, + int row, int col, u32 col_bd[23], u32 row_bd[23], + u32 pic_in_cts_width, u32 pic_in_cts_height, + bool is_last, bool first_slice, + bool hflip, bool vflip, + u32 coded_flags, u32 last_tile_block) +{ + struct avd_dev *avd = ctx->dev; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + u32 tb_x, tb_y, tile_block, tile_boundary; + + if (coded_flags & NEW_SLICE) { + tb_x = sl->slice_segment_addr % pic_in_cts_width; + tb_y = sl->slice_segment_addr / pic_in_cts_width; + + tile_block = tb_y << 12 | tb_x; + + if (!(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT)) + last_tile_block = tile_block; + + /* + * tile block start + * CABAC window + */ + push(0x2c000000 | last_tile_block, "cm3_cmd_set_cabac_xy"); + + stream_slice_dqtblk(ctx, run, sl); + } else { + tile_boundary = row_bd[row] << 12 | col_bd[col]; + } + + if (coded_flags & NEW_TILE_ID) { + push(0x2a000000 | + (coded_flags & NEW_SLICE ? tile_block : tile_boundary), + "cm3_cmd_set_ctb_xy"); + + /* tile boundary end */ + if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) + push(((hflip ? 4 : 0) | (vflip ? 8 : 0)) << 28 + | col << 24 + | (row_bd[row + 1] - 1) << 12 + | (col_bd[col + 1] - 1), + "cm3_set_ctb_xy"); + else /* first slice, one CTB */ + push((pic_in_cts_height - 1) << 12 + | (pic_in_cts_width - 1), + "cm3_set_ctb_xy"); + } + + if (coded_flags & NEW_SLICE) + stream_slice_mv(ctx, run, sl, first_slice); + + /* current tile block / boundary ?? */ + /* Unlike entropy, motion vector window resets every time */ + push(1 << 24 | (coded_flags & NEW_SLICE ? tile_block : tile_boundary), + "cm3_set_mv_xy"); + + push(0x2b000000 | is_last << 10, "cm3_cmd_inst_fifo_end"); + + return last_tile_block; +} +/* clang-format on */ + +static void compute_tiles_uniform(struct avd_hevc_run *run, + u16 log2_min_cb_size, u16 width, u16 height, + s32 pic_in_cts_width, s32 pic_in_cts_height, + u16 *column_width, u16 *row_height) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + int i; + + for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++) + column_width[i] = ((i + 1) * pic_in_cts_width) / + (pps->num_tile_columns_minus1 + 1) - + (i * pic_in_cts_width) / + (pps->num_tile_columns_minus1 + 1); + + for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++) + row_height[i] = ((i + 1) * pic_in_cts_height) / + (pps->num_tile_rows_minus1 + 1) - + (i * pic_in_cts_height) / + (pps->num_tile_rows_minus1 + 1); +} + +static void compute_tiles_non_uniform(struct avd_hevc_run *run, + u16 log2_min_cb_size, u16 width, + u16 height, s32 pic_in_cts_width, + s32 pic_in_cts_height, u16 *column_width, + u16 *row_height) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + s32 sum = 0; + int i; + + for (i = 0; i < pps->num_tile_columns_minus1; i++) { + column_width[i] = pps->column_width_minus1[i] + 1; + sum += column_width[i]; + } + column_width[i] = pic_in_cts_width - sum; + + sum = 0; + for (i = 0; i < pps->num_tile_rows_minus1; i++) { + row_height[i] = pps->row_height_minus1[i] + 1; + sum += row_height[i]; + } + row_height[i] = pic_in_cts_height - sum; +} + +static void compute_bd(struct avd_hevc_run *run, u32 *col_bd, u32 *row_bd, + u16 *column_width, u16 *row_height) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + int i; + + for (col_bd[0] = 0, i = 0; i <= pps->num_tile_columns_minus1; i++) + col_bd[i + 1] = col_bd[i] + column_width[i]; + + for (row_bd[0] = 0, i = 0; i <= pps->num_tile_rows_minus1; i++) + row_bd[i + 1] = row_bd[i] + row_height[i]; +} + +static void compute_rs_to_ts(struct avd_hevc_run *run, u32 pic_in_ctbs_size, + u32 pic_in_ctbs_width, u32 *col_bd, u32 *row_bd, + u16 *col_width, u16 *row_height, + u32 *ctb_addr_rs_to_ts) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + int i, j, tb_x, tb_y, tile_x = 0, tile_y = 0; + u32 ctb_addr_rs; + + for (ctb_addr_rs = 0; ctb_addr_rs < pic_in_ctbs_size; ctb_addr_rs++) { + tb_x = ctb_addr_rs % pic_in_ctbs_width; + tb_y = ctb_addr_rs / pic_in_ctbs_width; + for (i = 0; i <= pps->num_tile_columns_minus1; i++) + if (tb_x >= col_bd[i]) + tile_x = i; + for (j = 0; j <= pps->num_tile_rows_minus1; j++) + if (tb_y >= row_bd[j]) + tile_y = j; + ctb_addr_rs_to_ts[ctb_addr_rs] = 0; + for (i = 0; i < tile_x; i++) + ctb_addr_rs_to_ts[ctb_addr_rs] += row_height[tile_y] * col_width[i]; + for (j = 0; j < tile_y; j++) + ctb_addr_rs_to_ts[ctb_addr_rs] += pic_in_ctbs_width * row_height[j]; + ctb_addr_rs_to_ts[ctb_addr_rs] += + (tb_y - row_bd[tile_y]) * col_width[tile_x] + tb_x - col_bd[tile_x]; + } +} + +static void compute_tile_ids(struct avd_hevc_run *run, u32 pic_in_ctbs_width, + u32 *col_bd, u32 *row_bd, u32 *ctb_addr_rs_to_ts, + u32 *tile_ids) +{ + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + int j, i, y, x; + u32 tile_idx; + for (j = 0, tile_idx = 0; j <= pps->num_tile_rows_minus1; j++) + for (i = 0; i <= pps->num_tile_columns_minus1; i++, tile_idx++) + for (y = row_bd[j]; y < row_bd[j + 1]; y++) + for (x = col_bd[i]; x < col_bd[i + 1]; x++) + tile_ids[ctb_addr_rs_to_ts[y * pic_in_ctbs_width + x]] + = tile_idx; +} + +static int avd_wait_submission_queue(struct avd_ctx *ctx) +{ + struct avd_dev *avd = ctx->dev; + u32 max = readl_relaxed(avd->ctrl + + avd->variant->submit_queue_max_offset + + (ctx->vp_slot) * 4); + u32 cur = readl_relaxed(avd->ctrl + + avd->variant->submit_queue_status_offset + + (ctx->vp_slot) * 4); + + if (cur == max) { + dev_err_ratelimited(avd->dev, "instruction que full! %d/%d", + cur, max); + return 1; + } + + if (cur >= max / 2) { + /* TODO: to high? low? Has weird side effects??? */ + usleep_range(100, 150); + } + return 0; +} + +struct sl_ctx { + u32 ctx_col; + u32 ctx_row; + s32 q1_col; + s32 q1_row; +}; + +/* TODO */ +static void stream_slices(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + const struct v4l2_ctrl_hevc_sps *sps = run->sps; + const struct v4l2_ctrl_hevc_pps *pps = run->pps; + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + const struct v4l2_ctrl_hevc_slice_params *sl; + bool tiles_enabled, is_last, first_slice, first_segment; + bool hflip, vflip; + int slice_segment_offset, entry_point_idx = 0, pos = 0, offset = 0; + int row, col, i, s, to; + int slice_flag, size, new_offset; + int tile_id, last_tile_id; + u16 log2_min_cb_size, width, height; + s32 max_cu_width, pic_in_ctbs_width, pic_in_ctbs_height, + pic_in_ctbs_size; + u32 num_cols, last_tile_block = 0; + u16 col_width[22] = {}, row_height[22] = {}; + u32 col_bd[23] = {}, row_bd[23] = {}; + u32 *ctb_addr_rs_to_ts = NULL, *tile_ids = NULL; + struct sl_ctx last = { + .q1_col = -1, + .q1_row = -1, + }; + + width = sps->pic_width_in_luma_samples; + height = sps->pic_height_in_luma_samples; + + log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3; + + num_cols = pps->num_tile_columns_minus1 + 1; + + max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size + + log2_min_cb_size); + pic_in_ctbs_width = (width + max_cu_width - 1) / max_cu_width; + pic_in_ctbs_height = (height + max_cu_width - 1) / max_cu_width; + pic_in_ctbs_size = pic_in_ctbs_height * pic_in_ctbs_width; + + tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED); + + ctb_addr_rs_to_ts = kzalloc( + sizeof(*ctb_addr_rs_to_ts) * pic_in_ctbs_size, GFP_KERNEL); + if (!ctb_addr_rs_to_ts) + goto done; + + tile_ids = kzalloc(sizeof(*tile_ids) * pic_in_ctbs_size, GFP_KERNEL); + if (!tile_ids) + goto done; + + if (tiles_enabled) { + if (pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING) { + compute_tiles_uniform(run, log2_min_cb_size, width, + height, pic_in_ctbs_width, + pic_in_ctbs_height, col_width, + row_height); + } else { + compute_tiles_non_uniform(run, log2_min_cb_size, width, + height, pic_in_ctbs_width, + pic_in_ctbs_height, col_width, + row_height); + } + compute_bd(run, col_bd, row_bd, col_width, row_height); + + /* + * 6.5.1 CTB raster and tile scanning conversion process + * (6-7) and (6-9) + * + * we really only need to know if tileidx != last tileidx + * so doing all this is stupid + */ + compute_rs_to_ts(run, pic_in_ctbs_size, pic_in_ctbs_width, + col_bd, row_bd, col_width, row_height, + ctb_addr_rs_to_ts); + compute_tile_ids(run, pic_in_ctbs_width, col_bd, row_bd, + ctb_addr_rs_to_ts, tile_ids); + } + + for (s = 0; s < run->num_slices; s++) { + sl = &run->sl[s]; + slice_segment_offset = 0; + to = tiles_enabled ? sl->num_entry_point_offsets + 1 : 1; + + for (i = 0; i < to; i++) { + is_last = i == to - 1 && s == run->num_slices - 1; + first_segment = i == 0; + first_slice = s == 0; + + if (tiles_enabled && to > 1) { + if (i < sl->num_entry_point_offsets) { + size = run->entry_point_offsets[entry_point_idx++]; + new_offset = size; + } else { + size = sl->bit_size / 8 - + sl->data_byte_offset - + slice_segment_offset; + new_offset = + size + sl->data_byte_offset; + } + } else { + size = (sl->bit_size) / 8 - + sl->data_byte_offset; + new_offset = size + sl->data_byte_offset; + } + + tile_id = tile_ids[ctb_addr_rs_to_ts[sl->slice_segment_addr]]; + last_tile_id = + first_slice ? + -1 : + tile_ids[ctb_addr_rs_to_ts[run->sl[s - 1].slice_segment_addr]]; + + slice_flag = 0; + if (first_slice || tile_id != last_tile_id || !first_segment) + slice_flag |= NEW_TILE_ID; + + if (first_segment) + slice_flag |= NEW_SLICE; + + vflip = false; + hflip = false; + + row = pos / num_cols; + col = pos % num_cols; + + /* + * not a fan of this, in JCT-VC-HEVC_V1 only TILES_B_Cisco_1 + * seems affected and it only seems to need hflip. + */ + if (slice_flag & NEW_TILE_ID) { + if ((col >= last.ctx_col && row > last.ctx_row) || + (col <= last.q1_col && row > last.q1_row)) + vflip = true; + + if (!(slice_flag & NEW_SLICE)) { + if (row && row == last.ctx_row + 1) { + hflip = true; + if (!vflip) { + last.q1_row = row; + last.q1_col = col; + } + } + } else { + last.ctx_row = row; + last.ctx_col = col; + last.q1_row = -1; + last.q1_col = -1; + } + } + + set_slice( + ctx, run, sl, size, + offset + slice_segment_offset, + /* makes WPP more reliable? But not really ?? */ + (sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT ? + slice_flag & ~NEW_SLICE : slice_flag) << 13); + + last_tile_block = submit_slice_segment( + ctx, run, sl, row, col, col_bd, row_bd, + pic_in_ctbs_width, pic_in_ctbs_height, is_last, + first_slice, hflip, vflip, slice_flag, + last_tile_block); + + if (slice_flag & NEW_TILE_ID) + pos++; + + slice_segment_offset += new_offset; + + if (avd_wait_submission_queue(ctx)) + goto done; + } + offset += sl->bit_size / 8; + } + + hevc_ctx->submit_num = pos; + +done: + if (ctb_addr_rs_to_ts) + kfree(ctb_addr_rs_to_ts); + + if (tile_ids) + kfree(tile_ids); +} + +static void update_dec_buf_info(struct avd_decoded_buffer *buf, + const struct v4l2_ctrl_hevc_slice_params *sl) +{ + buf->hevc.is_intra = sl->slice_type == V4L2_HEVC_SLICE_TYPE_I; +} + +static void avd_hevc_adjust_decoded_fmt(struct avd_ctx *ctx, + struct v4l2_pix_format_mplane *pix_mp) +{ + pix_mp->plane_fmt[0].sizeimage += + sps_size(pix_mp->width, pix_mp->height); +} + +static enum avd_image_fmt avd_hevc_get_image_fmt(struct avd_ctx *ctx, + struct v4l2_ctrl *ctrl) +{ + const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps; + + if (ctrl->id != V4L2_CID_STATELESS_HEVC_SPS) + return AVD_IMG_FMT_ANY; + + /* + * TODO: we can do up to 4:4:4 12 bit + * not sure if v4l2 supports RExt + */ + + if (sps->bit_depth_luma_minus8 == 0) { + if (sps->chroma_format_idc == 2) + return AVD_IMG_FMT_422_8BIT; + else + return AVD_IMG_FMT_420_8BIT; + } else if (sps->bit_depth_luma_minus8 == 2) { + if (sps->chroma_format_idc == 2) + return AVD_IMG_FMT_422_10BIT; + else + return AVD_IMG_FMT_420_10BIT; + } + + return AVD_IMG_FMT_ANY; +} + +static int avd_hevc_validate_sps(struct avd_ctx *ctx, + const struct v4l2_ctrl_hevc_sps *sps) +{ + if (sps->pic_width_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.width || + sps->pic_height_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.height) + return -EINVAL; + + return 0; +} + +static int avd_hevc_alloc_bufs(struct avd_ctx *ctx) +{ + struct avd_dev *dev = ctx->dev; + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + int ret; + + ret = avd_buf_alloc(dev, &hevc_ctx->bufs.inst, fifo_size()); + if (ret) + return ret; + + ret = avd_buf_alloc(dev, &hevc_ctx->bufs.pipe_state, 0x200); + if (ret) + return ret; + + for (int i = 0; i < PPS_NUM; i++) { + /* TODO: this is very waistfull */ + ret = avd_buf_alloc(dev, &hevc_ctx->bufs.pps_tile[i], + fmt_width(ctx) * fmt_height(ctx) / 8); + if (ret) + return ret; + } + + return 0; +} + +static int avd_hevc_start(struct avd_ctx *ctx) +{ + struct avd_hevc_ctx *hevc_ctx; + int ret; + + hevc_ctx = kzalloc(sizeof(*hevc_ctx), GFP_KERNEL); + if (!hevc_ctx) + return -ENOMEM; + + ctx->priv = hevc_ctx; + ret = avd_hevc_alloc_bufs(ctx); + if (ret) + goto err_free_ctx; + + return 0; + +err_free_ctx: + kfree(hevc_ctx); + ctx->priv = NULL; + return ret; +} + +static void avd_hevc_stop(struct avd_ctx *ctx) +{ + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + struct avd_dev *avd = ctx->dev; + + if (!hevc_ctx) + return; + + avd_buf_free(avd, &hevc_ctx->bufs.pipe_state); + avd_buf_free(avd, &hevc_ctx->bufs.inst); + + for (int i = 0; i < PPS_NUM; i++) + avd_buf_free(avd, &hevc_ctx->bufs.pps_tile[i]); + + free_vp_slot(avd, ctx); + free_inst_slot(avd, ctx); + + kfree(hevc_ctx); +} + +static void avd_hevc_run_preamble(struct avd_ctx *ctx, struct avd_hevc_run *run) +{ + struct v4l2_ctrl *ctrl; + u32 dst_len, sps_len; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_DECODE_PARAMS); + run->decode = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SLICE_PARAMS); + run->sl = ctrl ? ctrl->p_cur.p : NULL; + run->num_slices = ctrl ? ctrl->new_elems : 0; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, V4L2_CID_STATELESS_HEVC_SPS); + run->sps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, V4L2_CID_STATELESS_HEVC_PPS); + run->pps = ctrl ? ctrl->p_cur.p : NULL; + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_SCALING_MATRIX); + run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL; + + ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, + V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS); + run->entry_point_offsets = ctrl ? ctrl->p_cur.p : NULL; + + avd_run_preamble(ctx, &run->base); + + dst_len = run->base.bufs.dst->vb2_buf.planes[0].length; + + run->addresses.y = + vb2_dma_contig_plane_dma_addr(&run->base.bufs.dst->vb2_buf, 0); + + run->addresses.uv = + run->addresses.y + + ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline * + ALIGN(ctx->decoded_fmt.fmt.pix_mp.height, 16); + + run->addresses.sl = + vb2_dma_contig_plane_dma_addr(&run->base.bufs.src->vb2_buf, 0); + + sps_len = sps_size(fmt_width(ctx), fmt_height(ctx)); + + run->addresses.rvra = + run->addresses.y + (dst_len - sps_len - ctx->rvra.size); + + run->addresses.sps = run->addresses.y + (dst_len - sps_len); +} + +static int avd_hevc_run(struct avd_ctx *ctx) +{ + struct avd_dev *avd = ctx->dev; + struct avd_hevc_run run; + struct avd_hevc_ctx *hevc_ctx; + struct avd_decoded_buffer *dst; + int ret; + + avd_hevc_run_preamble(ctx, &run); + + avd_run_postamble(ctx, &run.base); + hevc_ctx = ctx->priv; + dst = vb2_to_avd_decoded_buf(&run.base.bufs.dst->vb2_buf); + update_dec_buf_info(dst, &run.sl[0]); + + ret = alloc_slots(avd, ctx, AVD_CODEC_HEVC); + if (ret) { + dev_err(avd->dev, "no free slots: %d", ret); + return ret; + } + avd->variant->configure_stream(avd, hevc_ctx->bufs.inst.addr, + ctx->fifo_idx, ctx->vp_slot); + set_header(ctx, &run); + + schedule_delayed_work(&ctx->watchdog_work, msecs_to_jiffies(2000)); + stream_slices(ctx, &run); + + return 0; +} + +static int avd_hevc_try_ctrl(struct avd_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS) + return avd_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps); + + return 0; +} + +static void avd_hevc_submit(struct avd_ctx *ctx) +{ + struct avd_hevc_ctx *hevc_ctx = ctx->priv; + struct avd_dev *avd = ctx->dev; + + for (int i = 0; i < hevc_ctx->submit_num; i++) { + writel(0x2b000000 + | (i == 0 ? (avd->variant->revision == 3 ? 0x100 : 0x200) : 0) + | (ctx->fifo_idx << 4) | avd->variant->fifo_slots, + avd->ctrl + avd->variant->submit_offset); + } +} + +const struct avd_coded_fmt_ops avd_hevc_fmt_ops = { + .adjust_decoded_fmt = avd_hevc_adjust_decoded_fmt, + .start = avd_hevc_start, + .stop = avd_hevc_stop, + .run = avd_hevc_run, + .submit = avd_hevc_submit, + .try_ctrl = avd_hevc_try_ctrl, + .get_image_fmt = avd_hevc_get_image_fmt, +}; diff --git a/drivers/media/platform/apple/avd/avd-v4l2.c b/drivers/media/platform/apple/avd/avd-v4l2.c index 6f690761844a36..9f2571d304b26c 100644 --- a/drivers/media/platform/apple/avd/avd-v4l2.c +++ b/drivers/media/platform/apple/avd/avd-v4l2.c @@ -173,9 +173,9 @@ static const struct avd_ctrl_desc avd_hevc_ctrl_descs[] = { }, { .cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE, - .cfg.min = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, - .cfg.max = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, - .cfg.def = V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED, + .cfg.min = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, + .cfg.max = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, + .cfg.def = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED, }, { .cfg.id = V4L2_CID_STATELESS_HEVC_START_CODE, @@ -183,6 +183,12 @@ static const struct avd_ctrl_desc avd_hevc_ctrl_descs[] = { .cfg.def = V4L2_STATELESS_HEVC_START_CODE_NONE, .cfg.max = V4L2_STATELESS_HEVC_START_CODE_NONE, }, + { + .cfg.id = V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS, + .cfg.dims = { 256 }, + .cfg.max = 0xffffffff, + .cfg.step = 1, + }, { .cfg.id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, .cfg.min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, @@ -207,9 +213,18 @@ static const struct avd_decoded_fmt_desc avd_hevc_decoded_fmts[] = { .image_fmt = AVD_IMG_FMT_420_8BIT, }, { - .fourcc = V4L2_PIX_FMT_NV15, + .fourcc = V4L2_PIX_FMT_P010, .image_fmt = AVD_IMG_FMT_420_10BIT, }, + { + .fourcc = V4L2_PIX_FMT_NV16, + .image_fmt = AVD_IMG_FMT_422_8BIT, + }, + { + /* TODO: missing P210 */ + .fourcc = V4L2_PIX_FMT_P010, + .image_fmt = AVD_IMG_FMT_422_10BIT, + }, }; static const struct avd_ctrl_desc avd_h264_ctrl_descs[] = { @@ -335,10 +350,10 @@ static const struct avd_coded_fmt_desc avd_coded_fmts[] = { .fourcc = V4L2_PIX_FMT_HEVC_SLICE, .frmsize = { .min_width = 64, - .max_width = 4096, - .step_width = 16, + .max_width = 16384, + .step_width = 64, .min_height = 64, - .max_height = 4096, + .max_height = 16384, .step_height = 16, }, .ctrls = &avd_hevc_ctrls, @@ -385,8 +400,7 @@ static const struct avd_coded_fmt_desc avd_coded_fmts[] = { static bool avd_is_capable(struct avd_ctx *ctx, unsigned int capability) { - return capability & (AVD_CAPABILITY_H264 | AVD_CAPABILITY_VP9); - /* return (ctx->dev->variant->capabilities & capability) == capability; */ + return (ctx->dev->variant->capabilities & capability) == capability; } static const struct avd_coded_fmt_desc * diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h index 88e7c77cf880dd..a12b42e2a68461 100644 --- a/drivers/media/platform/apple/avd/avd.h +++ b/drivers/media/platform/apple/avd/avd.h @@ -70,6 +70,12 @@ struct avd_vp9_decoded_buffer_info { unsigned int bit_depth : 4; }; +struct avd_hevc_decoded_buffer_info { + /* Info needed when the decoded frame serves as a reference frame. */ + bool is_intra; +}; + + struct avd_rvra { u32 offsets[4]; /* sizes or offsets */ u32 size; @@ -84,6 +90,7 @@ struct avd_decoded_buffer { union { struct avd_vp9_decoded_buffer_info vp9; + struct avd_hevc_decoded_buffer_info hevc; }; }; @@ -146,6 +153,8 @@ struct avd_variant { /* just for convenience */ u32 vp_slot_offset; u32 submit_offset; + u32 submit_queue_max_offset; + u32 submit_queue_status_offset; unsigned int quirks; }; From e0cfe0bf598094787b36bc6cec5253386d91015d Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 21 Jul 2026 00:20:01 +0200 Subject: [PATCH 14/25] dt-bindings: spmi: apple,spmi: Add t603x Add t603x compatibles and interrupt support Signed-off-by: Sasha Finkelstein --- .../devicetree/bindings/spmi/apple,spmi.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/devicetree/bindings/spmi/apple,spmi.yaml b/Documentation/devicetree/bindings/spmi/apple,spmi.yaml index f7920dd3a59b62..fceed47b2cd17f 100644 --- a/Documentation/devicetree/bindings/spmi/apple,spmi.yaml +++ b/Documentation/devicetree/bindings/spmi/apple,spmi.yaml @@ -20,6 +20,8 @@ properties: - items: - enum: - apple,t6020-spmi + - apple,t6030-spmi + - apple,t6031-spmi - apple,t8012-spmi - apple,t8015-spmi - apple,t8122-spmi @@ -35,6 +37,18 @@ properties: reg: maxItems: 1 + interrupts: + maxItems: 1 + description: Optional, operates in polled mode if not present + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + power-domains: + maxItems: 1 + required: - compatible - reg From f7a15b638c85ce6d0be7ca72f9b949c12db97193 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 22 Jun 2025 15:29:38 +0200 Subject: [PATCH 15/25] spmi: apple: Validate FIFO state Check for data before reading the body of a reply, and check for end of data afterwards. Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/spmi-apple-controller.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 87e3ee9d4f2aa5..ac5a85212f2759 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -79,6 +79,10 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, /* Read SPMI data reply */ while (len_read < len) { + if (readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY) { + dev_err(&ctrl->dev, "FIFO lacks reply data, controller stuck?\n"); + return -EIO; + } rsp = readl(spmi->regs + SPMI_RSP_REG); i = 0; while ((len_read < len) && (i < 4)) { @@ -87,6 +91,9 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, } } + if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) + dev_warn(&ctrl->dev, "FIFO has extra data\n"); + return 0; } @@ -116,6 +123,9 @@ static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, /* Discard */ readl(spmi->regs + SPMI_RSP_REG); + if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) + dev_warn(&ctrl->dev, "FIFO has extra data\n"); + return 0; } From c327d228e8f3f6ba89fee8a93a844e10b766667f Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 22 Jun 2025 18:20:14 +0200 Subject: [PATCH 16/25] spmi: apple: check transaction status Check for parity errors and missing command ACKs Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/spmi-apple-controller.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index ac5a85212f2759..837dbdc797a2de 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -23,6 +23,12 @@ #define SPMI_CMD_REG 0x4 #define SPMI_RSP_REG 0x8 +/* SPMI_RSP_REG reply word */ +#define SPMI_REPLY_FRAME_PARITY_OFFSET 16 +#define SPMI_REPLY_ACK BIT(15) +#define SPMI_REPLY_SLAVE_ID GENMASK(14, 8) +#define SPMI_REPLY_CMD GENMASK(7, 0) + #define SPMI_RX_FIFO_EMPTY BIT(24) #define REG_POLL_INTERVAL_US 10000 @@ -63,7 +69,7 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, { struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); - u32 rsp; + u32 reply, rsp; size_t len_read = 0; u8 i; int ret; @@ -74,8 +80,7 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (ret) return ret; - /* Discard SPMI reply status */ - readl(spmi->regs + SPMI_RSP_REG); + reply = readl(spmi->regs + SPMI_RSP_REG); /* Read SPMI data reply */ while (len_read < len) { @@ -94,6 +99,10 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) dev_warn(&ctrl->dev, "FIFO has extra data\n"); + if ((~reply >> SPMI_REPLY_FRAME_PARITY_OFFSET) & ((1 << len) - 1)) { + dev_err(&ctrl->dev, "some frames failed parity check\n"); + return -EIO; + } return 0; } @@ -102,6 +111,7 @@ static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, { struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); + u32 reply; size_t i = 0, j; int ret; @@ -120,12 +130,15 @@ static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (ret) return ret; - /* Discard */ - readl(spmi->regs + SPMI_RSP_REG); + reply = readl(spmi->regs + SPMI_RSP_REG); if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) dev_warn(&ctrl->dev, "FIFO has extra data\n"); + if (!(reply & SPMI_REPLY_ACK)) { + dev_err(&ctrl->dev, "command not acknowledged\n"); + return -EIO; + } return 0; } From 9e79d0350b329ce571634e5b54dbc782e54d0062 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 14 Sep 2025 14:49:37 +0200 Subject: [PATCH 17/25] spmi: apple: Implement remaining commands Add support for zero write and power management commands Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/spmi-apple-controller.c | 106 +++++++++++++++++---------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 837dbdc797a2de..cda306d3a074f7 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -42,9 +42,9 @@ struct apple_spmi { readl_poll_timeout((spmi)->regs + (reg), (val), (cond), \ REG_POLL_INTERVAL_US, REG_POLL_TIMEOUT_US) -static inline u32 apple_spmi_pack_cmd(u8 opc, u8 sid, u16 saddr, size_t len) +static inline u32 apple_spmi_pack_cmd(u8 opc, u8 sid, u16 param) { - return opc | sid << 8 | saddr << 16 | (len - 1) | (1 << 15); + return opc | sid << 8 | param << 16 | (1 << 15); } /* Wait for Rx FIFO to have something */ @@ -64,18 +64,27 @@ static int apple_spmi_wait_rx_not_empty(struct spmi_controller *ctrl) return 0; } -static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, - u16 saddr, u8 *buf, size_t len) +static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 param, const u8 *buf, size_t len, u8 *ibuf, size_t ilen) { struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); - u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); + u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, param); u32 reply, rsp; size_t len_read = 0; - u8 i; + size_t i = 0, j; int ret; writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); + while (i < len) { + j = 0; + spmi_cmd = 0; + while ((j < 4) & (i < len)) + spmi_cmd |= buf[i++] << (j++ * 8); + + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); + } + ret = apple_spmi_wait_rx_not_empty(ctrl); if (ret) return ret; @@ -83,15 +92,15 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, reply = readl(spmi->regs + SPMI_RSP_REG); /* Read SPMI data reply */ - while (len_read < len) { + while (len_read < ilen) { if (readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY) { dev_err(&ctrl->dev, "FIFO lacks reply data, controller stuck?\n"); return -EIO; } rsp = readl(spmi->regs + SPMI_RSP_REG); i = 0; - while ((len_read < len) && (i < 4)) { - buf[len_read++] = ((0xff << (8 * i)) & rsp) >> (8 * i); + while ((len_read < ilen) && (i < 4)) { + ibuf[len_read++] = ((0xff << (8 * i)) & rsp) >> (8 * i); i += 1; } } @@ -99,47 +108,67 @@ static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) dev_warn(&ctrl->dev, "FIFO has extra data\n"); - if ((~reply >> SPMI_REPLY_FRAME_PARITY_OFFSET) & ((1 << len) - 1)) { + if (!ilen && !(reply & SPMI_REPLY_ACK)) { + dev_err(&ctrl->dev, "command not acknowledged\n"); + return -EIO; + } + if ((~reply >> SPMI_REPLY_FRAME_PARITY_OFFSET) & ((1 << ilen) - 1)) { dev_err(&ctrl->dev, "some frames failed parity check\n"); return -EIO; } return 0; } -static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, - u16 saddr, const u8 *buf, size_t len) +/* Send a raw command with 1..16 input data frames */ +static int spmi_raw_cmd_input(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 param, u8 *buf, size_t len) { - struct apple_spmi *spmi = spmi_controller_get_drvdata(ctrl); - u32 spmi_cmd = apple_spmi_pack_cmd(opc, sid, saddr, len); - u32 reply; - size_t i = 0, j; - int ret; - - writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); + return spmi_raw_cmd(ctrl, opc, sid, param, NULL, 0, buf, len); +} - while (i < len) { - j = 0; - spmi_cmd = 0; - while ((j < 4) & (i < len)) - spmi_cmd |= buf[i++] << (j++ * 8); +/* Send a raw command with (optional) body and an input ACK */ +static int spmi_raw_cmd_ack(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 param, const u8 *buf, size_t len) +{ + return spmi_raw_cmd(ctrl, opc, sid, param, buf, len, NULL, 0); +} - writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); +static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 saddr, u8 *buf, size_t len) +{ + switch (opc) { + case SPMI_CMD_EXT_READ: + case SPMI_CMD_EXT_READL: + return spmi_raw_cmd_input(ctrl, opc | (len - 1), sid, saddr, buf, len); + case SPMI_CMD_READ: + return spmi_raw_cmd_input(ctrl, opc | saddr, sid, saddr, buf, len); } + return -EINVAL; +} - ret = apple_spmi_wait_rx_not_empty(ctrl); - if (ret) - return ret; - - reply = readl(spmi->regs + SPMI_RSP_REG); - - if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) - dev_warn(&ctrl->dev, "FIFO has extra data\n"); - - if (!(reply & SPMI_REPLY_ACK)) { - dev_err(&ctrl->dev, "command not acknowledged\n"); - return -EIO; +static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, + u16 saddr, const u8 *buf, size_t len) +{ + switch (opc) { + case SPMI_CMD_WRITE: + return spmi_raw_cmd_ack(ctrl, opc | saddr, sid, buf[0] << 8 | saddr, NULL, 0); + case SPMI_CMD_ZERO_WRITE: + return spmi_raw_cmd_ack(ctrl, opc | buf[0], sid, buf[0] << 8 | saddr, NULL, 0); + case SPMI_CMD_EXT_WRITE: + case SPMI_CMD_EXT_WRITEL: + return spmi_raw_cmd_ack(ctrl, opc | (len - 1), sid, saddr, buf, len); } - return 0; + return -EINVAL; +} + +static int spmi_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) +{ + if ( + opc == SPMI_CMD_RESET || opc == SPMI_CMD_SLEEP || + opc == SPMI_CMD_SHUTDOWN || opc == SPMI_CMD_WAKEUP + ) + return spmi_raw_cmd_ack(ctrl, opc, sid, 0, NULL, 0); + return -EINVAL; } static int apple_spmi_probe(struct platform_device *pdev) @@ -162,6 +191,7 @@ static int apple_spmi_probe(struct platform_device *pdev) ctrl->read_cmd = spmi_read_cmd; ctrl->write_cmd = spmi_write_cmd; + ctrl->cmd = spmi_cmd; ret = devm_spmi_controller_add(&pdev->dev, ctrl); if (ret) From 7f403a76a1c5826dabddbde8f58ecf250fb3ec3e Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 21 Sep 2025 12:28:14 +0200 Subject: [PATCH 18/25] spmi: apple: lock around FIFOs The driver was missing locking around register interactions Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/spmi-apple-controller.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index cda306d3a074f7..ad3d8af09d7445 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -17,6 +17,7 @@ #include #include #include +#include /* SPMI Controller Registers */ #define SPMI_STATUS_REG 0 @@ -36,6 +37,7 @@ struct apple_spmi { void __iomem *regs; + struct mutex fifo_lock; }; #define poll_reg(spmi, reg, val, cond) \ @@ -74,6 +76,10 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, size_t i = 0, j; int ret; + ret = mutex_lock_interruptible(&spmi->fifo_lock); + if (ret) + return ret; + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); while (i < len) { @@ -87,7 +93,7 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, ret = apple_spmi_wait_rx_not_empty(ctrl); if (ret) - return ret; + goto out; reply = readl(spmi->regs + SPMI_RSP_REG); @@ -95,7 +101,8 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, while (len_read < ilen) { if (readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY) { dev_err(&ctrl->dev, "FIFO lacks reply data, controller stuck?\n"); - return -EIO; + ret = -EIO; + goto out; } rsp = readl(spmi->regs + SPMI_RSP_REG); i = 0; @@ -108,6 +115,8 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (!(readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY)) dev_warn(&ctrl->dev, "FIFO has extra data\n"); + mutex_unlock(&spmi->fifo_lock); + if (!ilen && !(reply & SPMI_REPLY_ACK)) { dev_err(&ctrl->dev, "command not acknowledged\n"); return -EIO; @@ -117,6 +126,10 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, return -EIO; } return 0; + +out: + mutex_unlock(&spmi->fifo_lock); + return ret; } /* Send a raw command with 1..16 input data frames */ @@ -182,6 +195,7 @@ static int apple_spmi_probe(struct platform_device *pdev) return -ENOMEM; spmi = spmi_controller_get_drvdata(ctrl); + mutex_init(&spmi->fifo_lock); spmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(spmi->regs)) From c6b431ac60a113c98a8dc9970577af28059660e0 Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 21 Sep 2025 17:43:30 +0200 Subject: [PATCH 19/25] spmi: apple: use IRQ for RX FIFO if possible The IRQ fires as soon as the reply is available, which is usually takes a few us instead of the 10ms sleep interval for polling Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/spmi-apple-controller.c | 97 +++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index ad3d8af09d7445..6f4ddc5e602f1a 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -18,12 +18,20 @@ #include #include #include +#include +#include /* SPMI Controller Registers */ #define SPMI_STATUS_REG 0 #define SPMI_CMD_REG 0x4 #define SPMI_RSP_REG 0x8 +#define SPMI_IRQ_MASK_BASE 0x20 +#define SPMI_IRQ_ACK_BASE 0x60 +#define SPMI_IRQ_USER_SIZE 0x20 + +#define SPMI_IRQ_FIFO_RX 0 + /* SPMI_RSP_REG reply word */ #define SPMI_REPLY_FRAME_PARITY_OFFSET 16 #define SPMI_REPLY_ACK BIT(15) @@ -38,6 +46,8 @@ struct apple_spmi { void __iomem *regs; struct mutex fifo_lock; + bool fifo_rx_irq; + struct completion fifo_rx; }; #define poll_reg(spmi, reg, val, cond) \ @@ -56,7 +66,19 @@ static int apple_spmi_wait_rx_not_empty(struct spmi_controller *ctrl) int ret; u32 status; - ret = poll_reg(spmi, SPMI_STATUS_REG, status, !(status & SPMI_RX_FIFO_EMPTY)); + if (spmi->fifo_rx_irq) { + ret = wait_for_completion_timeout(&spmi->fifo_rx, + usecs_to_jiffies(REG_POLL_TIMEOUT_US)); + if (!ret) + ret = -ETIMEDOUT; + else if (readl(spmi->regs + SPMI_STATUS_REG) & SPMI_RX_FIFO_EMPTY) + ret = -EIO; + else + ret = 0; + } else { + ret = poll_reg(spmi, SPMI_STATUS_REG, status, !(status & SPMI_RX_FIFO_EMPTY)); + } + if (ret) { dev_err(&ctrl->dev, "failed to wait for RX FIFO not empty\n"); @@ -80,6 +102,8 @@ static int spmi_raw_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, if (ret) return ret; + reinit_completion(&spmi->fifo_rx); + writel(spmi_cmd, spmi->regs + SPMI_CMD_REG); while (i < len) { @@ -184,10 +208,68 @@ static int spmi_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) return -EINVAL; } +static void apple_spmi_irq_ack_raw(struct apple_spmi *spmi, u32 irq) +{ + u32 __iomem *reg = spmi->regs + SPMI_IRQ_ACK_BASE + (irq / 32) * 4; + + writel(BIT(irq % 32), reg); +} + +static void apple_spmi_irq_mask_raw(struct apple_spmi *spmi, u32 irq) +{ + u32 __iomem *reg = spmi->regs + SPMI_IRQ_MASK_BASE + (irq / 32) * 4; + + writel(readl(reg) & ~BIT(irq % 32), reg); +} + +static void apple_spmi_irq_unmask_raw(struct apple_spmi *spmi, u32 irq) +{ + u32 __iomem *reg = spmi->regs + SPMI_IRQ_MASK_BASE + (irq / 32) * 4; + + writel(readl(reg) | BIT(irq % 32), reg); +} + +static irqreturn_t apple_spmi_irq_handler(int irq, void *dev_id) +{ + struct apple_spmi *spmi = dev_id; + bool handled = false; + u32 val, offset, bit; + + val = readl(spmi->regs + SPMI_IRQ_ACK_BASE + SPMI_IRQ_USER_SIZE); + if (val & BIT(SPMI_IRQ_FIFO_RX)) { + complete(&spmi->fifo_rx); + apple_spmi_irq_ack_raw(spmi, SPMI_IRQ_USER_SIZE * 8 + SPMI_IRQ_FIFO_RX); + handled = true; + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int apple_spmi_init_irq(struct platform_device *pdev, + struct apple_spmi *spmi, int irq) +{ + int ret; + + for (size_t offset = 0; offset < SPMI_IRQ_USER_SIZE + 4; offset += 4) { + writel(0, spmi->regs + SPMI_IRQ_MASK_BASE + offset); + writel(U32_MAX, spmi->regs + SPMI_IRQ_ACK_BASE + offset); + } + + spmi->fifo_rx_irq = true; + apple_spmi_irq_unmask_raw(spmi, SPMI_IRQ_USER_SIZE * 8 + SPMI_IRQ_FIFO_RX); + + ret = devm_request_any_context_irq(&pdev->dev, irq, apple_spmi_irq_handler, 0, NULL, spmi); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "failed to request IRQ\n"); + + return 0; +} + static int apple_spmi_probe(struct platform_device *pdev) { struct apple_spmi *spmi; struct spmi_controller *ctrl; + int irq; int ret; ctrl = devm_spmi_controller_alloc(&pdev->dev, sizeof(*spmi)); @@ -196,6 +278,7 @@ static int apple_spmi_probe(struct platform_device *pdev) spmi = spmi_controller_get_drvdata(ctrl); mutex_init(&spmi->fifo_lock); + init_completion(&spmi->fifo_rx); spmi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(spmi->regs)) @@ -207,11 +290,23 @@ static int apple_spmi_probe(struct platform_device *pdev) ctrl->write_cmd = spmi_write_cmd; ctrl->cmd = spmi_cmd; + irq = platform_get_irq_optional(pdev, 0); + if (irq < 0 && irq != -ENXIO) + return irq; + if (irq >= 0) { + ret = apple_spmi_init_irq(pdev, spmi, irq); + if (ret) + return ret; + } + ret = devm_spmi_controller_add(&pdev->dev, ctrl); if (ret) return dev_err_probe(&pdev->dev, ret, "spmi_controller_add failed\n"); + dev_info(&pdev->dev, irq >= 0 ? "Initialized with IRQ" : + "Initialized without IRQ, falling back to polling"); + return 0; } From 2f282ea5c1b712b44967e8e7e38bee4adbafc3ee Mon Sep 17 00:00:00 2001 From: Alba Mendez Date: Sun, 21 Sep 2025 17:49:28 +0200 Subject: [PATCH 20/25] spmi: apple: interrupt controller functionality Add support for interrupts sent by slave devices. Signed-off-by: Alba Mendez Signed-off-by: Sasha Finkelstein --- drivers/spmi/Kconfig | 1 + drivers/spmi/spmi-apple-controller.c | 149 ++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig index a80cf4047b86e3..c9eff3d470a104 100644 --- a/drivers/spmi/Kconfig +++ b/drivers/spmi/Kconfig @@ -13,6 +13,7 @@ if SPMI config SPMI_APPLE tristate "Apple SoC SPMI Controller platform driver" + select IRQ_DOMAIN_HIERARCHY depends on ARCH_APPLE || COMPILE_TEST help If you say yes to this option, support will be included for the diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c index 6f4ddc5e602f1a..3d35d858025a23 100644 --- a/drivers/spmi/spmi-apple-controller.c +++ b/drivers/spmi/spmi-apple-controller.c @@ -20,6 +20,9 @@ #include #include #include +#include +#include +#include /* SPMI Controller Registers */ #define SPMI_STATUS_REG 0 @@ -48,6 +51,9 @@ struct apple_spmi { struct mutex fifo_lock; bool fifo_rx_irq; struct completion fifo_rx; + struct irq_domain *irqd; + raw_spinlock_t irq_mask_lock; + u64 irq_mask_cache[SPMI_IRQ_USER_SIZE / sizeof(u64)]; }; #define poll_reg(spmi, reg, val, cond) \ @@ -229,11 +235,123 @@ static void apple_spmi_irq_unmask_raw(struct apple_spmi *spmi, u32 irq) writel(readl(reg) | BIT(irq % 32), reg); } +static void apple_spmi_irq_ack(struct irq_data *d) +{ + struct apple_spmi *spmi = irq_data_get_irq_chip_data(d); + + apple_spmi_irq_ack_raw(spmi, d->hwirq); +} + +static void apple_spmi_irq_mask(struct irq_data *d) +{ + struct apple_spmi *spmi = irq_data_get_irq_chip_data(d); + unsigned long flags; + + raw_spin_lock_irqsave(&spmi->irq_mask_lock, flags); + apple_spmi_irq_mask_raw(spmi, d->hwirq); + spmi->irq_mask_cache[d->hwirq / 64] &= ~BIT(d->hwirq % 64); + raw_spin_unlock_irqrestore(&spmi->irq_mask_lock, flags); +} + +static void apple_spmi_irq_unmask(struct irq_data *d) +{ + struct apple_spmi *spmi = irq_data_get_irq_chip_data(d); + unsigned long flags; + + raw_spin_lock_irqsave(&spmi->irq_mask_lock, flags); + apple_spmi_irq_unmask_raw(spmi, d->hwirq); + spmi->irq_mask_cache[d->hwirq / 64] |= BIT(d->hwirq % 64); + raw_spin_unlock_irqrestore(&spmi->irq_mask_lock, flags); +} + +static int apple_spmi_irq_set_type(struct irq_data *d, unsigned int type) +{ + /* all interrupts have MSI semantics */ + return type == IRQ_TYPE_EDGE_RISING ? 0 : -EINVAL; +} + +static struct irq_chip apple_spmi_irq_chip = { + .name = "apple_spmi", + .irq_mask = apple_spmi_irq_mask, + .irq_unmask = apple_spmi_irq_unmask, + .irq_ack = apple_spmi_irq_ack, + .irq_set_type = apple_spmi_irq_set_type, + .flags = IRQCHIP_ONESHOT_SAFE, +}; + +static int apple_spmi_irq_domain_map(struct irq_domain *irqd, + unsigned int irq, irq_hw_number_t hw) +{ + irq_domain_set_info(irqd, irq, hw, &apple_spmi_irq_chip, irqd->host_data, + handle_edge_irq, NULL, NULL); + return 0; +} + +static int apple_spmi_irq_domain_translate(struct irq_domain *irqd, + struct irq_fwspec *fwspec, + unsigned long *hwirq, + unsigned int *type) +{ + u32 *args = fwspec->param; + + if (fwspec->param_count != 2) + return -EINVAL; + + if (args[0] >= SPMI_IRQ_USER_SIZE * 8) + return -EINVAL; + *hwirq = args[0]; + *type = args[1] & IRQ_TYPE_SENSE_MASK; + return 0; +} + +static int apple_spmi_irq_domain_alloc(struct irq_domain *irqd, unsigned int virq, + unsigned int nr_irqs, void *arg) +{ + unsigned int type = IRQ_TYPE_NONE; + struct irq_fwspec *fwspec = arg; + irq_hw_number_t hwirq; + int i, ret; + + ret = apple_spmi_irq_domain_translate(irqd, fwspec, &hwirq, &type); + if (ret) + return ret; + + if (hwirq + nr_irqs > SPMI_IRQ_USER_SIZE * 8) + return -EINVAL; + + for (i = 0; i < nr_irqs; i++) { + ret = apple_spmi_irq_domain_map(irqd, virq + i, hwirq + i); + if (ret) + return ret; + } + + return 0; +} + +static void apple_spmi_irq_domain_free(struct irq_domain *irqd, unsigned int virq, + unsigned int nr_irqs) +{ + int i; + + for (i = 0; i < nr_irqs; i++) { + struct irq_data *d = irq_domain_get_irq_data(irqd, virq + i); + + irq_set_handler(virq + i, NULL); + irq_domain_reset_irq_data(d); + } +} + +static const struct irq_domain_ops apple_spmi_irq_domain_ops = { + .translate = apple_spmi_irq_domain_translate, + .alloc = apple_spmi_irq_domain_alloc, + .free = apple_spmi_irq_domain_free, +}; + static irqreturn_t apple_spmi_irq_handler(int irq, void *dev_id) { struct apple_spmi *spmi = dev_id; bool handled = false; - u32 val, offset, bit; + u64 val, offset, bit; val = readl(spmi->regs + SPMI_IRQ_ACK_BASE + SPMI_IRQ_USER_SIZE); if (val & BIT(SPMI_IRQ_FIFO_RX)) { @@ -242,6 +360,23 @@ static irqreturn_t apple_spmi_irq_handler(int irq, void *dev_id) handled = true; } + for (offset = 0; offset < SPMI_IRQ_USER_SIZE; offset += sizeof(val)) { + val = readq(spmi->regs + SPMI_IRQ_ACK_BASE + offset); + /** + * because of other masters in the bus, we're going to get a multitude of + * interrupts we're not interested in. irq_resolve_mapping isn't very + * optimized for the nonexistent path, so instead we mask with (a locally + * cached version of) the IRQ mask + */ + val &= spmi->irq_mask_cache[offset / sizeof(val)]; + while (val) { + bit = __builtin_ctzll(val); + generic_handle_domain_irq(spmi->irqd, offset * 8 + bit); + handled = true; + val &= ~BIT(bit); + } + } + return handled ? IRQ_HANDLED : IRQ_NONE; } @@ -249,12 +384,24 @@ static int apple_spmi_init_irq(struct platform_device *pdev, struct apple_spmi *spmi, int irq) { int ret; + struct irq_domain_info info = { + .fwnode = pdev->dev.fwnode, + .hwirq_max = ~0U, + .ops = &apple_spmi_irq_domain_ops, + .host_data = spmi, + }; + + raw_spin_lock_init(&spmi->irq_mask_lock); for (size_t offset = 0; offset < SPMI_IRQ_USER_SIZE + 4; offset += 4) { writel(0, spmi->regs + SPMI_IRQ_MASK_BASE + offset); writel(U32_MAX, spmi->regs + SPMI_IRQ_ACK_BASE + offset); } + spmi->irqd = devm_irq_domain_instantiate(&pdev->dev, &info); + if (IS_ERR(spmi->irqd)) + return PTR_ERR(spmi->irqd); + spmi->fifo_rx_irq = true; apple_spmi_irq_unmask_raw(spmi, SPMI_IRQ_USER_SIZE * 8 + SPMI_IRQ_FIFO_RX); From 30c2f6fabb07ecf9a649663e09a2d80925ae356e Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 21 Jul 2026 00:35:54 +0200 Subject: [PATCH 21/25] dts: apple: t603x: Add usb-related spmi controllers Signed-off-by: Sasha Finkelstein --- arch/arm64/boot/dts/apple/t6030.dtsi | 26 +++++++++++++++++++++ arch/arm64/boot/dts/apple/t6031-die0.dtsi | 28 +++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t6030.dtsi b/arch/arm64/boot/dts/apple/t6030.dtsi index 2c2c388524317d..0bc566d99deef6 100644 --- a/arch/arm64/boot/dts/apple/t6030.dtsi +++ b/arch/arm64/boot/dts/apple/t6030.dtsi @@ -808,6 +808,8 @@ reg = <0x3 0x64714000 0x0 0x100>; #address-cells = <2>; #size-cells = <0>; + interrupt-parent = <&aic>; + interrupts = ; pmic1: pmic@b { compatible = "apple,chapel-pmic", "apple,spmi-nvmem"; @@ -860,6 +862,30 @@ }; }; + nub_spmi_a0: spmi@364908000 { + compatible = "apple,t6030-spmi", "apple,t8103-spmi"; + reg = <0x3 0x64908000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_nub_spmi_a0>; + }; + + nub_spmi_a1: spmi@364a08000 { + compatible = "apple,t6030-spmi", "apple,t8103-spmi"; + reg = <0x3 0x64a08000 0x0 0x100>; + #address-cells = <2>; + #size-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_nub_spmi_a1>; + }; + smc_mbox: mbox@36c408000 { compatible = "apple,t6030-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x3 0x6c408000 0x0 0x4000>; diff --git a/arch/arm64/boot/dts/apple/t6031-die0.dtsi b/arch/arm64/boot/dts/apple/t6031-die0.dtsi index 9591bace819be7..41abb97d7f9e3e 100644 --- a/arch/arm64/boot/dts/apple/t6031-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t6031-die0.dtsi @@ -37,6 +37,8 @@ reg = <0x2 0xa1014000 0x0 0x100>; #address-cells = <2>; #size-cells = <0>; + interrupt-parent = <&aic>; + interrupts = ; pmic1: pmic@b { compatible = "apple,chapel-pmic", "apple,spmi-nvmem"; @@ -89,6 +91,32 @@ }; }; + nub_spmi_a0: spmi@2a1188000 { + compatible = "apple,t6031-spmi", "apple,t8103-spmi"; + #address-cells = <2>; + #size-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + + reg = <0x2 0xa1188000 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_nub_spmi_a0>; + }; + + nub_spmi_a1: spmi@2a1198000 { + compatible = "apple,t6031-spmi", "apple,t8103-spmi"; + #address-cells = <2>; + #size-cells = <0>; + #interrupt-cells = <2>; + interrupt-controller; + + reg = <0x2 0xa1198000 0x0 0x100>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_nub_spmi_a1>; + }; + smc_mbox: mbox@2a4408000 { compatible = "apple,t6031-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x2 0xa4408000 0x0 0x4000>; From 8307d3b5e67331978e46a5bfa0cefe6aa6cdf616 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 22 Jul 2026 19:46:16 +0200 Subject: [PATCH 22/25] dt-bindings: usb: tps6598x: Add sn201202x/ACE3 A variant of tps6598x that is attached to a SPMI bus and is found on Apple devices starting from the M3 series Signed-off-by: Sasha Finkelstein --- .../bindings/usb/apple,sn201202x.yaml | 78 +++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 79 insertions(+) create mode 100644 Documentation/devicetree/bindings/usb/apple,sn201202x.yaml diff --git a/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml new file mode 100644 index 00000000000000..84d230ccccc7f5 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/apple,sn201202x.yaml @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/usb/apple,sn201202x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments sn201202x Type-C Power Delivery controller + +maintainers: + - Sasha Finkelstein + +description: | + Texas Instruments sn201202x Type-C Power Delivery controller + + A variant of tps6598x controller also known as Apple ACE3 + present on hardware with Apple SoCs starting from the M3. + +properties: + compatible: + enum: + - apple,sn201202x + + reg: + minItems: 1 + + interrupts: + items: + - description: Primary irq used for tps6598x events + - description: Logical register selection completed + - description: Standby command completed + - description: Wakeup command completed + + interrupt-names: + items: + - const: irq + - const: select + - const: sleep + - const: wake + + connector: + $ref: /schemas/connector/usb-connector.yaml# + +required: + - compatible + - reg + - interrupts + - interrupt-names + +additionalProperties: false + +examples: + - | + #include + #include + spmi { + #address-cells = <2>; + #size-cells = <0>; + usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + interrupts = <11 IRQ_TYPE_EDGE_RISING>, + <13 IRQ_TYPE_EDGE_RISING>, + <17 IRQ_TYPE_EDGE_RISING>, + <19 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + + connector { + compatible = "usb-c-connector"; + label = "USB-C"; + + port { + typec_ep: endpoint { + remote-endpoint = <&otg_ep>; + }; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index cde843b4f4d966..fc0de1b96cb6ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2555,6 +2555,7 @@ F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml +F: Documentation/devicetree/bindings/usb/apple,sn201202x.yaml F: Documentation/devicetree/bindings/watchdog/apple,wdt.yaml F: Documentation/hwmon/macsmc-hwmon.rst F: arch/arm64/boot/dts/apple/ From cb9363ef13601b1882bfbb2ad2ff25bbf10b2380 Mon Sep 17 00:00:00 2001 From: Alyssa Milburn Date: Sat, 30 May 2026 16:28:22 +0200 Subject: [PATCH 23/25] usb: typec: tipd: Factor out i2c specifics Make the core driver more bus-agnostic to prepare for SPMI variants of the tipd chip Signed-off-by: Alyssa Milburn Signed-off-by: Sasha Finkelstein --- drivers/usb/typec/tipd/Makefile | 2 +- drivers/usb/typec/tipd/{core.c => core.h} | 109 ++++++---------------- drivers/usb/typec/tipd/i2c.c | 86 +++++++++++++++++ 3 files changed, 115 insertions(+), 82 deletions(-) rename drivers/usb/typec/tipd/{core.c => core.h} (95%) create mode 100644 drivers/usb/typec/tipd/i2c.c diff --git a/drivers/usb/typec/tipd/Makefile b/drivers/usb/typec/tipd/Makefile index aa439f80a889ec..ba38daee9e7251 100644 --- a/drivers/usb/typec/tipd/Makefile +++ b/drivers/usb/typec/tipd/Makefile @@ -2,5 +2,5 @@ CFLAGS_trace.o := -I$(src) obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o -tps6598x-y := core.o +tps6598x-y := i2c.o tps6598x-$(CONFIG_TRACING) += trace.o diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.h similarity index 95% rename from drivers/usb/typec/tipd/core.c rename to drivers/usb/typec/tipd/core.h index d0b769333bd998..1ba29e439e01c0 100644 --- a/drivers/usb/typec/tipd/core.c +++ b/drivers/usb/typec/tipd/core.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -167,6 +166,7 @@ struct tps6598x { struct device *dev; struct regmap *regmap; struct mutex lock; /* device lock */ + int irq; u8 i2c_protocol:1; struct gpio_desc *reset; @@ -231,6 +231,13 @@ static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-"; */ #define TPS_MAX_LEN 64 +static struct tps6598x *tps6598x_from_device(struct device *dev) +{ + struct i2c_client *client = i2c_verify_client(dev); + struct tps6598x *tps = i2c_get_clientdata(client); + return tps; +} + static int tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len) { @@ -1738,27 +1745,13 @@ static void cd321x_remove(struct tps6598x *tps) cancel_delayed_work_sync(&cd321x->update_work); } -static int tps6598x_probe(struct i2c_client *client) +static int tps6598x_probe(struct tps6598x *tps) { - const struct tipd_data *data; - struct tps6598x *tps; struct fwnode_handle *fwnode; u32 status; u32 vid; int ret; - data = i2c_get_match_data(client); - if (!data) - return -EINVAL; - - tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL); - if (!tps) - return -ENOMEM; - - mutex_init(&tps->lock); - tps->dev = &client->dev; - tps->data = data; - tps->reset = devm_gpiod_get_optional(tps->dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(tps->reset)) return dev_err_probe(tps->dev, PTR_ERR(tps->reset), @@ -1766,23 +1759,12 @@ static int tps6598x_probe(struct i2c_client *client) if (tps->reset) msleep(TPS_SETUP_MS); - tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config); - if (IS_ERR(tps->regmap)) - return PTR_ERR(tps->regmap); - if (!device_is_compatible(tps->dev, "ti,tps25750")) { ret = tps6598x_read32(tps, TPS_REG_VID, &vid); if (ret < 0 || !vid) return -ENODEV; } - /* - * Checking can the adapter handle SMBus protocol. If it can not, the - * driver needs to take care of block reads separately. - */ - if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) - tps->i2c_protocol = true; - if (tps->data->switch_power_state) { ret = tps->data->switch_power_state(tps, TPS_SYSTEM_POWER_STATE_S0); if (ret) @@ -1816,7 +1798,7 @@ static int tps6598x_probe(struct i2c_client *client) * with existing DT files, we work around this by deleting any * fwnode_links to/from this fwnode. */ - fwnode = device_get_named_child_node(&client->dev, "connector"); + fwnode = device_get_named_child_node(tps->dev, "connector"); if (fwnode) fw_devlink_purge_absent_suppliers(fwnode); @@ -1842,14 +1824,14 @@ static int tps6598x_probe(struct i2c_client *client) goto err_unregister_port; ret = tps->data->connect(tps, status); if (ret) - dev_err(&client->dev, "failed to register partner\n"); + dev_err(tps->dev, "failed to register partner\n"); } - if (client->irq) { - ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, + if (tps->irq) { + ret = devm_request_threaded_irq(tps->dev, tps->irq, NULL, tps->data->irq_handler, IRQF_SHARED | IRQF_ONESHOT, - dev_name(&client->dev), tps); + dev_name(tps->dev), tps); } else { dev_warn(tps->dev, "Unable to find the interrupt, switching to polling\n"); INIT_DELAYED_WORK(&tps->wq_poll, tps6598x_poll_work); @@ -1860,13 +1842,12 @@ static int tps6598x_probe(struct i2c_client *client) if (ret) goto err_disconnect; - i2c_set_clientdata(client, tps); fwnode_handle_put(fwnode); tps->wakeup = device_property_read_bool(tps->dev, "wakeup-source"); - if (tps->wakeup && client->irq) { - devm_device_init_wakeup(&client->dev); - enable_irq_wake(client->irq); + if (tps->wakeup && tps->irq) { + devm_device_init_wakeup(tps->dev); + enable_irq_wake(tps->irq); } return 0; @@ -1888,14 +1869,12 @@ static int tps6598x_probe(struct i2c_client *client) return ret; } -static void tps6598x_remove(struct i2c_client *client) +static void tps6598x_remove(struct tps6598x *tps) { - struct tps6598x *tps = i2c_get_clientdata(client); - - if (!client->irq) + if (!tps->irq) cancel_delayed_work_sync(&tps->wq_poll); else - devm_free_irq(tps->dev, client->irq, tps); + devm_free_irq(tps->dev, tps->irq, tps); if (tps->data->remove) tps->data->remove(tps); @@ -1913,17 +1892,16 @@ static void tps6598x_remove(struct i2c_client *client) static int __maybe_unused tps6598x_suspend(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct tps6598x *tps = i2c_get_clientdata(client); + struct tps6598x *tps = tps6598x_from_device(dev); if (tps->wakeup) { - disable_irq(client->irq); - enable_irq_wake(client->irq); + disable_irq(tps->irq); + enable_irq_wake(tps->irq); } else if (tps->reset) { gpiod_set_value_cansleep(tps->reset, 1); } - if (!client->irq) + if (!tps->irq) cancel_delayed_work_sync(&tps->wq_poll); return 0; @@ -1931,8 +1909,7 @@ static int __maybe_unused tps6598x_suspend(struct device *dev) static int __maybe_unused tps6598x_resume(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - struct tps6598x *tps = i2c_get_clientdata(client); + struct tps6598x *tps = tps6598x_from_device(dev); int ret; ret = tps6598x_check_mode(tps); @@ -1946,14 +1923,14 @@ static int __maybe_unused tps6598x_resume(struct device *dev) } if (tps->wakeup) { - disable_irq_wake(client->irq); - enable_irq(client->irq); + disable_irq_wake(tps->irq); + enable_irq(tps->irq); } else if (tps->reset) { gpiod_set_value_cansleep(tps->reset, 0); msleep(TPS_SETUP_MS); } - if (!client->irq) + if (!tps->irq) queue_delayed_work(system_power_efficient_wq, &tps->wq_poll, msecs_to_jiffies(POLL_INTERVAL)); @@ -2018,33 +1995,3 @@ static const struct tipd_data tps25750_data = { .reset = tps25750_reset, .connect = tps6598x_connect, }; - -static const struct of_device_id tps6598x_of_match[] = { - { .compatible = "ti,tps6598x", &tps6598x_data}, - { .compatible = "apple,cd321x", &cd321x_data}, - { .compatible = "ti,tps25750", &tps25750_data}, - {} -}; -MODULE_DEVICE_TABLE(of, tps6598x_of_match); - -static const struct i2c_device_id tps6598x_id[] = { - { "tps6598x", (kernel_ulong_t)&tps6598x_data }, - { } -}; -MODULE_DEVICE_TABLE(i2c, tps6598x_id); - -static struct i2c_driver tps6598x_i2c_driver = { - .driver = { - .name = "tps6598x", - .pm = &tps6598x_pm_ops, - .of_match_table = tps6598x_of_match, - }, - .probe = tps6598x_probe, - .remove = tps6598x_remove, - .id_table = tps6598x_id, -}; -module_i2c_driver(tps6598x_i2c_driver); - -MODULE_AUTHOR("Heikki Krogerus "); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("TI TPS6598x USB Power Delivery Controller Driver"); diff --git a/drivers/usb/typec/tipd/i2c.c b/drivers/usb/typec/tipd/i2c.c new file mode 100644 index 00000000000000..6b244c5f84a6fc --- /dev/null +++ b/drivers/usb/typec/tipd/i2c.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for TI TPS6598x USB Power Delivery controller family + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus + */ + +#include + +#include "core.h" + +static int tps6598x_probe_i2c(struct i2c_client *client) +{ + const struct tipd_data *data; + struct tps6598x *tps; + int ret; + + data = i2c_get_match_data(client); + if (!data) + return -EINVAL; + + tps = devm_kzalloc(&client->dev, data->tps_struct_size, GFP_KERNEL); + if (!tps) + return -ENOMEM; + + mutex_init(&tps->lock); + tps->dev = &client->dev; + tps->data = data; + tps->irq = client->irq; + + tps->regmap = devm_regmap_init_i2c(client, &tps6598x_regmap_config); + if (IS_ERR(tps->regmap)) + return PTR_ERR(tps->regmap); + + /* + * Checking can the adapter handle SMBus protocol. If it can not, the + * driver needs to take care of block reads separately. + */ + if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + tps->i2c_protocol = true; + + ret = tps6598x_probe(tps); + + if (ret == 0) + i2c_set_clientdata(client, tps); + + return ret; +} + +static void tps6598x_remove_i2c(struct i2c_client *client) +{ + struct tps6598x *tps = i2c_get_clientdata(client); + + tps6598x_remove(tps); +} + +static const struct of_device_id tps6598x_of_match[] = { + { .compatible = "ti,tps6598x", &tps6598x_data}, + { .compatible = "apple,cd321x", &cd321x_data}, + { .compatible = "ti,tps25750", &tps25750_data}, + {} +}; +MODULE_DEVICE_TABLE(of, tps6598x_of_match); + +static const struct i2c_device_id tps6598x_id[] = { + { "tps6598x", (kernel_ulong_t)&tps6598x_data }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tps6598x_id); + +static struct i2c_driver tps6598x_i2c_driver = { + .driver = { + .name = "tps6598x", + .pm = &tps6598x_pm_ops, + .of_match_table = tps6598x_of_match, + }, + .probe = tps6598x_probe_i2c, + .remove = tps6598x_remove_i2c, + .id_table = tps6598x_id, +}; +module_i2c_driver(tps6598x_i2c_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TI TPS6598x USB Power Delivery Controller Driver"); From e983e3bcf0fd2867c7a945b7c1950a9a1c293896 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 22 Jul 2026 18:51:20 +0200 Subject: [PATCH 24/25] usb: typec: tipd: Add sn201202x support Add support for sn201202x (aka ACE3), a tipd variant that uses a very similar register map, that is exposed over a "logical register" interface on the SPMI bus. Co-developed-by: Alyssa Milburn Signed-off-by: Alyssa Milburn Signed-off-by: Sasha Finkelstein --- MAINTAINERS | 1 + drivers/usb/typec/tipd/Kconfig | 14 ++ drivers/usb/typec/tipd/Makefile | 12 +- drivers/usb/typec/tipd/core.h | 14 +- drivers/usb/typec/tipd/spmi.c | 297 ++++++++++++++++++++++++++++++++ 5 files changed, 332 insertions(+), 6 deletions(-) create mode 100644 drivers/usb/typec/tipd/spmi.c diff --git a/MAINTAINERS b/MAINTAINERS index fc0de1b96cb6ac..6aae4c2c46f332 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2587,6 +2587,7 @@ F: drivers/soc/apple/* F: drivers/spi/spi-apple.c F: drivers/spmi/spmi-apple-controller.c F: drivers/usb/dwc3/dwc3-apple.c +F: drivers/usb/typec/tipd/spmi.c F: drivers/video/backlight/apple_dwi_bl.c F: drivers/watchdog/apple_wdt.c F: include/dt-bindings/interrupt-controller/apple-aic.h diff --git a/drivers/usb/typec/tipd/Kconfig b/drivers/usb/typec/tipd/Kconfig index b8271529307286..6aa2fa439fddcd 100644 --- a/drivers/usb/typec/tipd/Kconfig +++ b/drivers/usb/typec/tipd/Kconfig @@ -10,3 +10,17 @@ config TYPEC_TPS6598X If you choose to build this driver as a dynamically linked module, the module will be called tps6598x.ko. + +config TYPEC_SN201202X + tristate "TI SN201202x USB Power Delivery controller support" + select POWER_SUPPLY + select REGMAP_I2C + select USB_ROLE_SWITCH + select REGMAP_SPMI + help + Say Y here to enable support for SN201202x, a TPS6598x variant + that uses SPMI as a host bus, and is found on Apple devices starting + from the M3 series. + + If you choose to build this driver as a dynamically linked module, the + module will be called sn201202x.ko. diff --git a/drivers/usb/typec/tipd/Makefile b/drivers/usb/typec/tipd/Makefile index ba38daee9e7251..9a547bb14831b0 100644 --- a/drivers/usb/typec/tipd/Makefile +++ b/drivers/usb/typec/tipd/Makefile @@ -1,6 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 -CFLAGS_trace.o := -I$(src) +CFLAGS_trace.o := -I$(src) -obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o -tps6598x-y := i2c.o -tps6598x-$(CONFIG_TRACING) += trace.o +obj-$(CONFIG_TYPEC_TPS6598X) += tps6598x.o +tps6598x-y := i2c.o +tps6598x-$(CONFIG_TRACING) += trace.o + +obj-$(CONFIG_TYPEC_SN201202X) += sn201202x.o +sn201202x-y := spmi.o +sn201202x-$(CONFIG_TRACING) += trace.o diff --git a/drivers/usb/typec/tipd/core.h b/drivers/usb/typec/tipd/core.h index 1ba29e439e01c0..fed326a9788d73 100644 --- a/drivers/usb/typec/tipd/core.h +++ b/drivers/usb/typec/tipd/core.h @@ -25,6 +25,10 @@ #include "tps6598x.h" #include "trace.h" +#ifdef CONFIG_TYPEC_SN201202X +#include +#endif + /* Register offsets */ #define TPS_REG_VID 0x00 #define TPS_REG_MODE 0x03 @@ -234,8 +238,14 @@ static const char *tps6598x_psy_name_prefix = "tps6598x-source-psy-"; static struct tps6598x *tps6598x_from_device(struct device *dev) { struct i2c_client *client = i2c_verify_client(dev); - struct tps6598x *tps = i2c_get_clientdata(client); - return tps; +#ifdef CONFIG_TYPEC_SN201202X + if (!client) { + struct spmi_device *device = to_spmi_device(dev); + + return spmi_device_get_drvdata(device); + } +#endif + return i2c_get_clientdata(client); } static int diff --git a/drivers/usb/typec/tipd/spmi.c b/drivers/usb/typec/tipd/spmi.c new file mode 100644 index 00000000000000..f94453131ea294 --- /dev/null +++ b/drivers/usb/typec/tipd/spmi.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#include "core.h" + +struct sn201202x { + struct cd321x cd; + struct completion select_completion; + struct completion sleep_completion; + struct completion wake_completion; + struct spmi_device *sdev; +}; + +#define tps_to_sn(tps) container_of_const((tps), struct sn201202x, cd.tps) + +static int regmap_sn201202x_select_reg(struct spmi_device *sdev, u8 reg) +{ + int err; + u8 val; + bool warned = false; + struct tps6598x *tps = spmi_device_get_drvdata(sdev); + struct sn201202x *sn = tps_to_sn(tps); + + reinit_completion(&sn->select_completion); + err = spmi_register_zero_write(sdev, reg); + if (err) + return err; + + if (!wait_for_completion_timeout(&sn->select_completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + + while (1) { + err = spmi_register_read(sdev, 0, &val); + if (err) + return err; + if (val == (reg | 0x80)) { + if (!warned) { + dev_warn(tps->dev, + "Got interrupt but selection not complete?\n"); + warned = true; + } + msleep(20); + continue; + } + if (val == reg) + break; + return -EIO; + } + + return 0; +} + +static int regmap_sn201202x_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + int err; + unsigned int offset = 0x20; + size_t len; + u8 addr; + + WARN_ON(reg_size != 1); + WARN_ON(val_size > 0x40); + + addr = *(u8 *)reg; + + err = regmap_sn201202x_select_reg(context, addr); + if (err) + return err; + + while (val_size) { + len = min_t(size_t, val_size, 16); + err = spmi_ext_register_read(context, offset, val, len); + if (err) + return err; + offset += len; + val += len; + val_size -= len; + } + + return 0; +} + +static int regmap_sn201202x_write(void *context, const void *data, + size_t count) +{ + int err = 0; + unsigned int offset = 0xa0; + size_t len; + u8 addr; + + WARN_ON(count < 1); + + addr = *(u8 *)data; + data += 1; + count -= 1; + + WARN_ON(count > 0x40); + + err = regmap_sn201202x_select_reg(context, addr); + if (err) + return err; + + while (count) { + len = min_t(size_t, count, 16); + err = spmi_ext_register_write(context, offset, data, len); + if (err) + return err; + offset += len; + data += len; + count -= len; + } + + return err; +} + +static irqreturn_t sn201202x_irq(int irq, void *data) +{ + struct completion *c = data; + + complete(c); + return IRQ_HANDLED; +} + +static const struct regmap_bus regmap_sn201202x = { + .read = regmap_sn201202x_read, + .write = regmap_sn201202x_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct regmap *__devm_regmap_init_sn201202x(struct spmi_device *sdev, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + return __devm_regmap_init(&sdev->dev, ®map_sn201202x, sdev, config, + lock_key, lock_name); +} + +#define devm_regmap_init_sn201202x(dev, config) \ + __regmap_lockdep_wrapper(__devm_regmap_init_sn201202x, #config, \ + dev, config) + +static const struct tipd_data sn201202x_data = { + .irq_handler = cd321x_interrupt, + .irq_mask1 = APPLE_CD_REG_INT_POWER_STATUS_UPDATE | + APPLE_CD_REG_INT_DATA_STATUS_UPDATE | + APPLE_CD_REG_INT_PLUG_EVENT, + .tps_struct_size = sizeof(struct sn201202x), + .remove = cd321x_remove, + .register_port = cd321x_register_port, + .unregister_port = cd321x_unregister_port, + .trace_data_status = trace_cd321x_data_status, + .trace_power_status = trace_tps6598x_power_status, + .trace_status = trace_tps6598x_status, + .init = cd321x_init, + .read_data_status = cd321x_read_data_status, + .reset = cd321x_reset, + .switch_power_state = cd321x_switch_power_state, + .connect = cd321x_connect, +}; + +static const struct of_device_id sn201202x_of_match[] = { + { .compatible = "apple,sn201202x", &sn201202x_data}, + {} +}; + +static int sn201202x_probe(struct spmi_device *device) +{ + const struct of_device_id *match; + const struct tipd_data *data; + struct sn201202x *sn; + struct tps6598x *tps; + int irq_select, irq_sleep, irq_wake; + int ret; + + match = of_match_device(sn201202x_of_match, &device->dev); + if (!match) + return -EINVAL; + data = match->data; + + sn = devm_kzalloc(&device->dev, data->tps_struct_size, GFP_KERNEL); + if (!sn) + return -ENOMEM; + sn->sdev = device; + tps = &sn->cd.tps; + + mutex_init(&tps->lock); + tps->dev = &device->dev; + tps->data = data; + + tps->irq = of_irq_get_byname(device->dev.of_node, "irq"); + if (tps->irq < 0) + return tps->irq; + irq_select = of_irq_get_byname(device->dev.of_node, "select"); + if (irq_select < 0) + return irq_select; + irq_sleep = of_irq_get_byname(device->dev.of_node, "sleep"); + if (irq_sleep < 0) + return irq_sleep; + irq_wake = of_irq_get_byname(device->dev.of_node, "wake"); + if (irq_wake < 0) + return irq_wake; + + init_completion(&sn->select_completion); + init_completion(&sn->sleep_completion); + init_completion(&sn->wake_completion); + + ret = devm_request_threaded_irq(&device->dev, irq_select, NULL, sn201202x_irq, + IRQF_ONESHOT, NULL, &sn->select_completion); + if (ret) + return ret; + ret = devm_request_threaded_irq(&device->dev, irq_sleep, NULL, sn201202x_irq, + IRQF_ONESHOT, NULL, &sn->sleep_completion); + if (ret) + return ret; + ret = devm_request_threaded_irq(&device->dev, irq_wake, NULL, sn201202x_irq, + IRQF_ONESHOT, NULL, &sn->wake_completion); + if (ret) + return ret; + + tps->regmap = devm_regmap_init_sn201202x(device, &tps6598x_regmap_config); + if (IS_ERR(tps->regmap)) + return PTR_ERR(tps->regmap); + + ret = spmi_command_wakeup(device); + if (ret) + return ret; + if (!wait_for_completion_timeout(&sn->wake_completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + + spmi_device_set_drvdata(device, tps); + return tps6598x_probe(tps); +} + +static void sn201202x_remove(struct spmi_device *device) +{ + struct tps6598x *tps = spmi_device_get_drvdata(device); + + tps6598x_remove(tps); +} + +static int __maybe_unused sn201202x_resume(struct device *dev) +{ + struct tps6598x *tps = dev_get_drvdata(dev); + struct sn201202x *sn = tps_to_sn(tps); + int err; + + err = spmi_command_wakeup(sn->sdev); + if (err) + return err; + if (!wait_for_completion_timeout(&sn->wake_completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + return tps6598x_resume(dev); +} + +static int __maybe_unused sn201202x_suspend(struct device *dev) +{ + struct tps6598x *tps = dev_get_drvdata(dev); + struct sn201202x *sn = tps_to_sn(tps); + int err; + + err = tps6598x_suspend(dev); + if (err) + return err; + err = spmi_command_sleep(sn->sdev); + if (err) + return err; + if (!wait_for_completion_timeout(&sn->sleep_completion, msecs_to_jiffies(100))) + return -ETIMEDOUT; + return 0; +} + +MODULE_DEVICE_TABLE(of, sn201202x_of_match); + +static const struct dev_pm_ops sn201202x_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sn201202x_suspend, sn201202x_resume) +}; + +static struct spmi_driver sn201202x_driver = { + .driver = { + .name = "sn201202x", + .pm = &sn201202x_pm_ops, + .of_match_table = sn201202x_of_match, + }, + .probe = sn201202x_probe, + .remove = sn201202x_remove, +}; +module_spmi_driver(sn201202x_driver); + +MODULE_AUTHOR("Heikki Krogerus "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TI SN201202x USB Power Delivery Controller Driver"); From 7eeab09175074656bdee26971d84795e8676c7bb Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Wed, 22 Jul 2026 20:31:45 +0200 Subject: [PATCH 25/25] dts: apple: t603x: Add ACE3 pd controllers Signed-off-by: Sasha Finkelstein --- .../arm64/boot/dts/apple/t603x-j514-j516.dtsi | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi index f868eeb8ace8d9..b2ff8864b323e3 100644 --- a/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi +++ b/arch/arm64/boot/dts/apple/t603x-j514-j516.dtsi @@ -288,6 +288,50 @@ }; }; +&nub_spmi_a0 { + hpm0: usb-pd@c { + compatible = "apple,sn201202x"; + reg = <0xc SPMI_USID>; + interrupts = <11 IRQ_TYPE_EDGE_RISING>, + <13 IRQ_TYPE_EDGE_RISING>, + <17 IRQ_TYPE_EDGE_RISING>, + <19 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + }; + + hpm1: usb-pd@a { + compatible = "apple,sn201202x"; + reg = <0xa SPMI_USID>; + interrupts = <37 IRQ_TYPE_EDGE_RISING>, + <39 IRQ_TYPE_EDGE_RISING>, + <43 IRQ_TYPE_EDGE_RISING>, + <45 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + }; + + hpm5: usb-pd@6 { + compatible = "apple,sn201202x"; + reg = <0x6 SPMI_USID>; + interrupts = <89 IRQ_TYPE_EDGE_RISING>, + <91 IRQ_TYPE_EDGE_RISING>, + <95 IRQ_TYPE_EDGE_RISING>, + <97 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + }; +}; + +&nub_spmi_a1 { + hpm2: usb-pd@8 { + compatible = "apple,sn201202x"; + reg = <0x8 SPMI_USID>; + interrupts = <63 IRQ_TYPE_EDGE_RISING>, + <65 IRQ_TYPE_EDGE_RISING>, + <69 IRQ_TYPE_EDGE_RISING>, + <71 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "irq", "select", "sleep", "wake"; + }; +}; + #ifndef NO_ISP_YET &isp { apple,platform-id = <9>;