From nobody Mon Sep 28 04:53:54 2026 Received: from mail-ej1-f54.google.com (mail-ej1-f54.google.com [209.85.218.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1587E429014 for ; Wed, 26 Aug 2026 13:24:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787750655; cv=none; b=ROzTEUEViNci4XAkkX3oA7ntRR3BXua4tCfMZyipWRv7JsWqJ9bQesby23iEmvZfoXVxDKV0ylqtpi3yGa46uS4RA40XGUnSFUQqVEw7QSc5CfCae05fnp0K/B3OCJEWJqXHpERD8u7VWNQoJfTGDCpuPgdAYRPl18dMcj1Mmks= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787750655; c=relaxed/simple; bh=MWrTq1IYqx8eEXDgACyPRrwYWOF3/1aPM3t+cHWXvqo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jDjFtgT7HHvTu/VdMSYxkjw8pAwORuxh4xx6io3yJVh0DrNc/dc2y55fQ8e+qgR4r/gA8gnNwl3qpJWrvUaU+9rsbP9Pde8RYLUSq9tiRqBJO2W+okWRKx94Asu6EZRlucw3vi3s4lVfcQ9NtF/j3h0vmJ3SOKb+tr8FHZ7vGx0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=hZYZLiIH; arc=none smtp.client-ip=209.85.218.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hZYZLiIH" Received: by mail-ej1-f54.google.com with SMTP id a640c23a62f3a-c160420289bso123149366b.0 for ; Wed, 26 Aug 2026 06:24:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787750651; x=1788355451; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=K9JiN/Q5of2Un3vhVZlsQfZMPr7ConSac3y0iRHMAXU=; b=hZYZLiIHTZ41xg1TYR4+kKlXn92AIGegaI4B3SA+U7uCeMNOsLDu6waJh+4NxpPfdi 1tegFszhT0a/HqjzCHA05gy9jGS3YsPmsMPci1OQ98xL5mRLdgKLfjn5MmyGMhN5MWrh mHWU9WZynz/0Kvacx7DSH2qE5U/sV4Ixuo1yaahkJFAEAB+SUzGsprCXRiOM+txWVHfy 5tZPPeeGUsYoVg7CugVFxLcQMhmBz48cM+BLKjOLiYTY1VCZ6DEWnNmG8qlHGu9ZhWDj lEkBLRlsePzu9KYKnvaCkW/txoGsYyhvMmuOEzVYFS+devTbbquf7dXoTp5TvaNqk7tv ktzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787750651; x=1788355451; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=K9JiN/Q5of2Un3vhVZlsQfZMPr7ConSac3y0iRHMAXU=; b=DdUu+5hCDhzKZgGWS4wuLlTcdB3r+b2S1qoNu7NbmL0EyYsxAwSLjTxEvL3dqqeD4e Isu0mtM8q7+m+yNRw6JYWde9VRDUoH9w3kjyYC1WKvKVDew0HpQEABRhxq9TIJ1C3/uA endoHET4GozTlqmM5nZxk2+bEJDmQDTeGFXMEAiR+rSJI836c2j+y+lq/FKfbZTNYouU 8baiP5oMmHQ1JgAdaQQMt03QbKk8C5hJp5Z0UZtqkN+Pppu1JT4wxwrX4LXOkdPDEj45 0orvGED5j9UnxF/sYO/Tqh4wWNufiK29LyPcqm/1GFj838P5LStQo0tV8moMX9tPpcob oj+Q== X-Forwarded-Encrypted: i=1; AHgh+RqNDWV6JMpFVLG6N1m/zptWTeQzZUPKCiSlu360vY3lTqiST5jAsrjBPqaEua5JZ0IoOlJYmRkw5Ks4cX8=@vger.kernel.org X-Gm-Message-State: AFuF++n+TFHF2b/a3fknQ2DLSy9jBlIRTOAbiAUOqCLQwLQtrB1wTDZD PLArGrS/de448zkMP3x/28Bx+RaVS0oxRF3MDT4iiuDUgcL6fU2npF1L X-Gm-Gg: AR+sD12cI3sxLrH0MnNIEPdSgmvsNMr5SmSNtF8hxYm8AdQM5+WkuO80afabnu4kKLe yAEd0A6uzmVWgIrXrXN2yeWwOJW4dBcmj1h3yDJdnW9/6lyqZOsIWDewbgmHm/e02o6lycbm7qw aJj16wwTIwYVNpWayk+bbLgJI/kPhKEKmvimROfmCYoADGuXbVPj6MF3iAWMqHwlSRVMyspETlK KeYSBNEoF4KOV9jFwHQ+l8TssPwZqdW+h7LAzcrh2HQjLXsjwZyP2oQbknUekoosmMI+JBaDngJ nBbMdecwePWPhHpfYUEDuMYxjYBwpa/Eg/hDnhvgSFgcvqPFs/8QzvU35cELtCxkmEc8Opr4i2P dwjKQuZCN1yKRWfqQHruPjErFbHtbbmfwVZJZc8WWH1nX4dF7eNrimH0YRgQYyt5nEmzOLKaeLf UED23C9imjY9kCFHs0fM37GnKVEHMapMFjY8khbxCEc+XeaP//4X1/iYnLiOtQw3aGAIRklNOof 0BmoI2369aX0svBNBE/tgGm0koKFKWqE6m7WlZgqDPVmostjdd80yk= X-Received: by 2002:a17:907:6093:b0:c08:417e:3696 with SMTP id a640c23a62f3a-c250c318ec5mr803788066b.20.1787750651038; Wed, 26 Aug 2026 06:24:11 -0700 (PDT) Received: from 1Z10 ([37.163.238.51]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c250a88ae4dsm648518666b.39.2026.08.26.06.24.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Aug 2026 06:24:10 -0700 (PDT) From: Maurizio Casciano To: Hans de Goede , Mathias Nyman Cc: Ilpo Jarvinen , Greg Kroah-Hartman , platform-driver-x86@vger.kernel.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Maurizio Casciano Subject: [RFC PATCH 1/2] xhci: Restore SSIC state on the Lenovo Yoga Book Date: Wed, 26 Aug 2026 15:24:02 +0200 Message-ID: <20260826132403.3345072-2-mauriziocasciano7@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260826132403.3345072-1-mauriziocasciano7@gmail.com> References: <20260826132403.3345072-1-mauriziocasciano7@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Lenovo Yoga Book YB1-X91L connects its XMM7260 modem through the Cherry Trail xHCI SSIC port. Firmware leaves SSIC vendor registers in a state which prevents reliable enumeration after controller setup and power transitions. Add a DMI-scoped quirk which restores the SSIC register banks, configures link retraining and reapplies the state after resume. The register definitions and sequence are derived from Intel's GPL-2.0 xHCI vendor-capability implementation. Link: https://github.com/jekhor/yogabook-linux-android-kernel/blob/574bae69= 2716f1b14093497bfab8a007fe8e460b/drivers/usb/host/xhci-intel-cap.c Signed-off-by: Maurizio Casciano Assisted-by: LLM sparse --- drivers/usb/host/xhci-pci.c | 114 ++++++++++++++++++++++++++++++++++- drivers/usb/host/xhci-ring.c | 31 ++++++++++ drivers/usb/host/xhci.h | 1 + 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index a8889081ae82..39d3ffa64185 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,17 @@ #define SSIC_PORT_CFG2_OFFSET 0x30 #define PROG_DONE (1 << 30) #define SSIC_PORT_UNUSED (1 << 31) +#define SSIC_RETRAIN_TIMEOUT GENMASK(24, 21) +#define SSIC_SS_PORT_LINK_CTRL 0x80ec +#define SSIC_SS_PORT_LINK_CTRL_U3_MASK GENMASK(11, 9) +#define SSIC_ACCESS_CTRL 0x4 +#define SSIC_ACCESS_CTRL_OFFSET 0x110 +#define SSIC_ACCESS_CTRL_REGISTER_BANK_VALID BIT(25) +#define XHCI_EXT_CAPS_INTEL_HOST 192 +#define XHCI_EXT_CAPS_INTEL_SSIC 196 +#define XHCI_EXT_CAPS_INTEL_SSIC_PROFILE 197 +#define DUAL_ROLE_CFG0 0x68 +#define EN_PIPE_4_1_SYNC_PHY_STATUS BIT(23) #define SPARSE_DISABLE_BIT 17 #define SPARSE_CNTL_ENABLE 0xC12C =20 @@ -99,6 +111,87 @@ static const char hcd_name[] =3D "xhci_hcd"; =20 static struct hc_driver __read_mostly xhci_pci_hc_driver; =20 +static const struct dmi_system_id xhci_yogabook_ssic_dmi_table[] =3D { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91L"), + }, + }, + { } +}; + +static int xhci_yogabook_ssic_ext_cap(struct xhci_hcd *xhci, int id) +{ + return xhci_find_next_ext_cap(xhci->cap_regs, 0, id); +} + +static void xhci_yogabook_ssic_restore_banks(struct xhci_hcd *xhci) +{ + u8 __iomem *base =3D (u8 __iomem *)xhci->cap_regs; + int offset; + int i; + + offset =3D xhci_yogabook_ssic_ext_cap(xhci, + XHCI_EXT_CAPS_INTEL_SSIC_PROFILE); + if (!offset) { + xhci_warn(xhci, "Yoga Book SSIC profile capability is missing\n"); + return; + } + + for (i =3D 0; i < SSIC_PORT_NUM; i++) { + void __iomem *reg =3D base + offset + SSIC_ACCESS_CTRL + + i * SSIC_ACCESS_CTRL_OFFSET; + u32 val =3D readl(reg); + + writel(val | SSIC_ACCESS_CTRL_REGISTER_BANK_VALID, reg); + readl(reg); + } +} + +static void xhci_yogabook_ssic_prepare_reset(struct xhci_hcd *xhci) +{ + u8 __iomem *base =3D (u8 __iomem *)xhci->cap_regs; + void __iomem *reg; + int host_cap; + u32 val; + + host_cap =3D xhci_yogabook_ssic_ext_cap(xhci, + XHCI_EXT_CAPS_INTEL_HOST); + if (!host_cap) { + xhci_warn(xhci, "Yoga Book Intel host capability is missing\n"); + return; + } + + reg =3D base + host_cap + DUAL_ROLE_CFG0; + val =3D readl(reg) | EN_PIPE_4_1_SYNC_PHY_STATUS; + writel(val, reg); + readl(reg); +} + +static void xhci_yogabook_ssic_configure(struct xhci_hcd *xhci) +{ + u8 __iomem *base =3D (u8 __iomem *)xhci->cap_regs; + void __iomem *reg; + u32 val; + + if (!xhci_yogabook_ssic_ext_cap(xhci, XHCI_EXT_CAPS_INTEL_SSIC)) + xhci_warn(xhci, "Yoga Book Intel SSIC capability is missing\n"); + + reg =3D base + SSIC_PORT_CFG2; + val =3D readl(reg) & ~SSIC_RETRAIN_TIMEOUT; + writel(val, reg); + readl(reg); + + reg =3D base + SSIC_SS_PORT_LINK_CTRL; + val =3D readl(reg) & ~SSIC_SS_PORT_LINK_CTRL_U3_MASK; + writel(val, reg); + readl(reg); + + xhci_yogabook_ssic_restore_banks(xhci); + xhci_info(xhci, "Yoga Book XMM7260 SSIC initialization applied\n"); +} + static int xhci_pci_setup(struct usb_hcd *hcd); static int xhci_pci_run(struct usb_hcd *hcd); static int xhci_pci_update_hub_device(struct usb_hcd *hcd, struct usb_devi= ce *hdev, @@ -383,6 +476,12 @@ static void xhci_pci_quirks(struct device *dev, struct= xhci_hcd *xhci) if (pdev->vendor =3D=3D PCI_VENDOR_ID_INTEL && pdev->device =3D=3D PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) xhci->quirks |=3D XHCI_SSIC_PORT_UNUSED; + if (pdev->vendor =3D=3D PCI_VENDOR_ID_INTEL && + pdev->device =3D=3D PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI && + dmi_check_system(xhci_yogabook_ssic_dmi_table)) { + xhci->quirks |=3D XHCI_YOGABOOK_SSIC; + xhci_yogabook_ssic_prepare_reset(xhci); + } if (pdev->vendor =3D=3D PCI_VENDOR_ID_INTEL && (pdev->device =3D=3D PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || pdev->device =3D=3D PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || @@ -586,6 +685,9 @@ static int xhci_pci_setup(struct usb_hcd *hcd) =20 xhci->allow_single_roothub =3D 1; =20 + if (xhci->quirks & XHCI_YOGABOOK_SSIC) + xhci_yogabook_ssic_configure(xhci); + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) xhci_pme_acpi_rtd3_enable(pdev); =20 @@ -857,6 +959,7 @@ static int xhci_pci_resume(struct usb_hcd *hcd, pm_mess= age_t msg) struct pci_dev *pdev =3D to_pci_dev(hcd->self.controller); bool power_lost =3D msg.event =3D=3D PM_EVENT_RESTORE; bool is_auto_resume =3D msg.event =3D=3D PM_EVENT_AUTO_RESUME; + int ret; =20 reset_control_reset(xhci->reset); =20 @@ -881,13 +984,22 @@ static int xhci_pci_resume(struct usb_hcd *hcd, pm_me= ssage_t msg) if (pdev->vendor =3D=3D PCI_VENDOR_ID_INTEL) usb_enable_intel_xhci_ports(pdev); =20 + if (xhci->quirks & XHCI_YOGABOOK_SSIC) { + xhci_yogabook_ssic_prepare_reset(xhci); + xhci_yogabook_ssic_restore_banks(xhci); + } + if (xhci->quirks & XHCI_SSIC_PORT_UNUSED) xhci_ssic_port_unused_quirk(hcd, false); =20 if (xhci->quirks & XHCI_PME_STUCK_QUIRK) xhci_pme_quirk(hcd); =20 - return xhci_resume(xhci, power_lost, is_auto_resume); + ret =3D xhci_resume(xhci, power_lost, is_auto_resume); + if (!ret && (xhci->quirks & XHCI_YOGABOOK_SSIC)) + xhci_yogabook_ssic_configure(xhci); + + return ret; } =20 static int xhci_pci_poweroff_late(struct usb_hcd *hcd, bool do_wakeup) diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 97a1b53c18ef..4ec5dcd1bad5 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -62,10 +62,40 @@ #include "xhci.h" #include "xhci-trace.h" =20 +#define YOGABOOK_SSIC_RETRAIN_CFG2 0x880c +#define YOGABOOK_SSIC_RETRAIN_TIMEOUT GENMASK(24, 21) +#define YOGABOOK_SSIC_USB3_PORT 5 + static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd, u32 field1, u32 field2, u32 field3, u32 field4, bool command_must_succeed); =20 +static void xhci_yogabook_ssic_update_retrain(struct xhci_hcd *xhci, + struct xhci_port *port, + u32 portsc) +{ + u8 __iomem *base =3D (u8 __iomem *)xhci->cap_regs; + void __iomem *reg =3D base + YOGABOOK_SSIC_RETRAIN_CFG2; + u32 val; + + if (!(xhci->quirks & XHCI_YOGABOOK_SSIC) || + port->rhub !=3D &xhci->usb3_rhub || + port->hcd_portnum + 1 !=3D YOGABOOK_SSIC_USB3_PORT || + !(portsc & (PORT_CSC | PORT_PLC))) + return; + + val =3D readl(reg); + if (!(portsc & PORT_CONNECT)) + val &=3D ~YOGABOOK_SSIC_RETRAIN_TIMEOUT; + else if ((portsc & PORT_PLS_MASK) =3D=3D XDEV_U0) + val |=3D YOGABOOK_SSIC_RETRAIN_TIMEOUT; + else + return; + + writel(val, reg); + readl(reg); +} + /* * Returns zero if the TRB isn't in this segment, otherwise it returns the= DMA * address of the TRB. @@ -2035,6 +2065,7 @@ static void handle_port_status(struct xhci_hcd *xhci,= union xhci_trb *event) hcd->self.busnum, hcd_portnum + 1, port_id, portsc); =20 trace_xhci_handle_port_status(port, portsc); + xhci_yogabook_ssic_update_retrain(xhci, port, portsc); =20 if (hcd->state =3D=3D HC_STATE_SUSPENDED) { xhci_dbg(xhci, "resume root hub\n"); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index c7bfa7f028d3..28072cfbd39e 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1647,6 +1647,7 @@ struct xhci_hcd { #define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48) #define XHCI_ETRON_HOST BIT_ULL(49) #define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50) +#define XHCI_YOGABOOK_SSIC BIT_ULL(51) =20 unsigned int num_active_eps; unsigned int limit_active_eps; --=20 2.53.0 From nobody Mon Sep 28 04:53:54 2026 Received: from mail-ej1-f50.google.com (mail-ej1-f50.google.com [209.85.218.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C69594248A9 for ; Wed, 26 Aug 2026 13:24:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787750657; cv=none; b=p3lH+HIlFTidCWHocPuvZyJP/qTjG3r7VXFX+/WiulJsjfhN7PkD8cwlVhn3FYPR76PraPVWfKGNjsRIrx+nLX9myneoFlPzw8u6uTODV/Fm5tjeo7KAwLBXRC2JsbeZE5ZzWjHQzzsAJafqIqnCcAEUErNESJ9aX6DDT64Zv6A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787750657; c=relaxed/simple; bh=qlQUSYAqWutliK4r1zb1xrvURDYIiJX9EeG0XtqOjjE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ClRTCBkHZ0oeEu7AM5OjRinER3g0AMCmH6H0Hrt8WULZIU9Pom4/Vy7t7b4Hrm8rBC/ikPs4nDxIB+XtxbyTdhXG/BBvnW+CjWcGFl8l0o6DHZ7ADmM/ln+G3r2rGabEZ5ZPVvS8oufGcF0ZrEE+oyEJC8oOCVqqF9w76gImvG0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=Rnvg4uaW; arc=none smtp.client-ip=209.85.218.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Rnvg4uaW" Received: by mail-ej1-f50.google.com with SMTP id a640c23a62f3a-c1670dad7a8so149987166b.3 for ; Wed, 26 Aug 2026 06:24:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787750653; x=1788355453; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=vHlfE1utSrpFggpS3CJMaH3o/iytPwgXlz1qOpxIICk=; b=Rnvg4uaWSYIJXNEZYF7wfkdC0i+J1HzIN+S4KZz6ahFvRaZbb0T3vg/9fuLG2FMPnP N2NdThVDv9QpfAkUCljkHVaKTJ35aZFuQK+LE18DwsqClP0yhLDoCxIsWsAUp3Tqu8K8 v6LnEglzOkZasYzjVhbj40/6p1w474ji7fud/pv2nI7J4S2CuT4/7gqjh+wMOyLsyCsu wdwCVvayjvOBQGQNEO26oab4m0erzMlI1Kz4mmjPPY4Ia4QBBQC+hxPzd+Eb5n+80WN3 4n6k/a1ne/I0aKYLccaYowXpezKiXjPGo5lrJVEtV/zcGUJ/Kxrw3a4JTuDXBDVh9Fmp NvXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787750653; x=1788355453; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=vHlfE1utSrpFggpS3CJMaH3o/iytPwgXlz1qOpxIICk=; b=jE1hMmW25m/yQj4oorTUvOpVc07KiLJFHsUE/+PW7wVPqeaFl8gsS0hsPEyU8xCTIc ZSQb4jV6LHkTDNaXVck0RraqKpulJbVoKpBEBlZHDsW7D4fjWQzsnK83OP6jiC5a8K/w u8A4A5NJQPM2sxyNTMC+YQE+pKREcao5FuSX4YSfgcj/flQp4OpxqLE1Zi7gESoIOStV GDoP5kkQoPDXOs0DIKaLFYJEGjdosdNTr3fSUJwNMYrpv4lHMonB6Yjk0v66ulOuKkfQ PhseLh1GGkjIQjtvy1Ka9fx7SxT/Dnx4VBgiPXnvxIB7aVnnq6mALXOQWiAs7ZvzdY9E 3TCQ== X-Forwarded-Encrypted: i=1; AHgh+RqsUreVKbsZyFV6yBPHbG42DOLbeZt32MaTBoPCdr+CdDZIn003+iZTCTavBdVhc3azSbYZW0ZEnXQ04t8=@vger.kernel.org X-Gm-Message-State: AFuF++mUh2ASne2X8mvzcSQk9Bp7CdGN+66KJ3GowcG4jtKdWk+22tz6 VOQkUUwnUEf1ZP4RZXxnNOF9wu5k67mj8ALvzh7wfzsiA92JQ2W1mbNXNjIOTPET X-Gm-Gg: AR+sD12tIu6o/uYbJNFjERPyhOWv7xQqctuH0xY7ib2Qe0Py6tozWbuN8LLFggKq1fo KZ9bGiLlORYYT+ztrPA2RdO4/khTXkpGJKliLawtzS2Q17rmy/lgbi8Y4OQuPytfXlwtEzCDqHb ZGN84ZySWNLiHy5IrUrMzEsdOgu8/0KiZTWAT+tfs14yO4VlAiIeoUknWjAR1AGypC8WDmaMIXz wbCXHFgniVJYi13dUeLIGaff4Jj7rMvyxqMNBA9R7QcGqiaVABDa32g4HG9PIm0ZLOnDkyI8nHv GFw6VjMt7npM3DUD2GHGCa+DAhG2k7KFgaz5s3IOmpXW7Iq0uCjlieOuw4eaQmeRaRA6JdjKO1g 9xnWynYbPCLmImrvuxI0C3RbmeVfstHKOD7pzcGEBHT8xjBW9ggWPaB65B+dzRmug3JCGkzaTfb YEhzxtP3UVoHlGm//bkpVWnvzkt86l/X4I0Nsx2bnncBlv5IHLq/Xd7lp2VdpkU9+KW57i/mIQL 3kV74LgaiHOaY7xeAF3g9WKncDgINCHnQqwJK4porc= X-Received: by 2002:a17:907:9815:b0:c20:5798:7b9f with SMTP id a640c23a62f3a-c250bc1567amr880907666b.10.1787750652793; Wed, 26 Aug 2026 06:24:12 -0700 (PDT) Received: from 1Z10 ([37.163.238.51]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c250a88ae4dsm648518666b.39.2026.08.26.06.24.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 26 Aug 2026 06:24:12 -0700 (PDT) From: Maurizio Casciano To: Hans de Goede , Mathias Nyman Cc: Ilpo Jarvinen , Greg Kroah-Hartman , platform-driver-x86@vger.kernel.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Maurizio Casciano Subject: [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver Date: Wed, 26 Aug 2026 15:24:03 +0200 Message-ID: <20260826132403.3345072-3-mauriziocasciano7@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260826132403.3345072-1-mauriziocasciano7@gmail.com> References: <20260826132403.3345072-1-mauriziocasciano7@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Lenovo Yoga Book YB1-X91L exposes its XMM7260 modem as an INT34D0 ACPI device and connects it to the Cherry Trail xHCI SSIC port. The modem needs an ACPI _DSM and PMIC power sequence before its USB boot function can switch to the MBIM runtime function. Add a DMI-scoped platform driver which performs that sequence, holds xHCI runtime PM during enumeration and retries the boot-device status request until MBIM appears or the enumeration window expires. The firmware interface and PMIC data come from the Yoga Book ACPI tables; the sequencing model is based on Intel's GPL-2.0 modem-control code. The driver depends on the preceding xHCI SSIC restore quirk: without that quirk the controller cannot reliably enumerate the modem after setup or power transitions. Link: https://github.com/jekhor/yogabook-linux-android-kernel/blob/574bae69= 2716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_acpi.c Link: https://github.com/jekhor/yogabook-linux-android-kernel/blob/574bae69= 2716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_pmic.c Link: https://github.com/jekhor/yogabook-linux/blob/96acd46c5a03565a114a0c6= 602734bb02717639a/devices/YB1-X91L/acpi/DSDT.dsl Signed-off-by: Maurizio Casciano Assisted-by: LLM sparse --- MAINTAINERS | 1 + drivers/platform/x86/intel/Kconfig | 16 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/cht_modem.c | 418 +++++++++++++++++++++++++ 4 files changed, 436 insertions(+) create mode 100644 drivers/platform/x86/intel/cht_modem.c diff --git a/MAINTAINERS b/MAINTAINERS index 24ca91ce5d86..ac39ab76f7c6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -29578,6 +29578,7 @@ Q: https://patchwork.kernel.org/project/platform-dr= iver-x86/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-driver= s-x86.git F: drivers/platform/olpc/ F: drivers/platform/x86/ +F: drivers/platform/x86/intel/cht_modem.c F: include/linux/platform_data/x86/ =20 X86 PLATFORM UV HPE SUPERDOME FLEX diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/inte= l/Kconfig index 2900407d6095..0c81fe46c105 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -115,6 +115,22 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. =20 +config INTEL_CHT_MODEM + tristate "Intel Cherry Trail ACPI modem power driver" + depends on ACPI + depends on INTEL_SOC_PMIC_CHTWC + depends on USB + depends on USB_XHCI_PCI + help + This driver controls the firmware power sequence for Intel XMM + modems connected to the Cherry Trail xHCI SSIC port and described + by the INT34D0 ACPI device. Currently this supports the Lenovo + Yoga Book YB1-X91L. + + Build this driver into the kernel when the modem must be powered + before the built-in xHCI controller probes. If built as a module, + it will be called intel-cht_modem. + config INTEL_CHTWC_INT33FE tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver" depends on X86 && ACPI && I2C && REGULATOR diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/int= el/Makefile index 138b13756158..5ceeccd75c36 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -32,6 +32,7 @@ intel-target-$(CONFIG_INTEL_VSEC) +=3D vsec.o intel-target-$(CONFIG_INTEL_BYTCRC_PWRSRC) +=3D bytcrc_pwrsrc.o intel-target-$(CONFIG_INTEL_BXTWC_PMIC_TMU) +=3D bxtwc_tmu.o intel-target-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) +=3D chtdc_ti_pwrbtn.o +intel-target-$(CONFIG_INTEL_CHT_MODEM) +=3D cht_modem.o intel-target-$(CONFIG_INTEL_CHTWC_INT33FE) +=3D chtwc_int33fe.o intel-target-$(CONFIG_X86_ANDROID_TABLETS) +=3D crystal_cove_charger.o intel-target-$(CONFIG_INTEL_MRFLD_PWRBTN) +=3D mrfld_pwrbtn.o diff --git a/drivers/platform/x86/intel/cht_modem.c b/drivers/platform/x86/= intel/cht_modem.c new file mode 100644 index 000000000000..30665b660f0c --- /dev/null +++ b/drivers/platform/x86/intel/cht_modem.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Intel Cherry Trail ACPI modem power driver + * + * Copyright (C) 2008, 2013 Intel Corporation + * Copyright (C) 2026 Maurizio Casciano + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CHT_MODEM_DSM_REVISION 0 +#define CHT_MODEM_DSM_POWER_OFF 1 +#define CHT_MODEM_DSM_RESET 3 + +/* ACPI's MCD0001 PMIC package for the XMM7260_CONF_3 configuration. */ +#define CHT_MODEM_PMIC_HID "INT34D3" +#define CHT_MODEM_PMIC_CTRL_REG 0x6e29 +#define CHT_MODEM_PMIC_CTRL_MASK GENMASK(1, 0) +#define CHT_MODEM_PMIC_CTRL_ON BIT(0) +#define CHT_MODEM_PMIC_POWER_DELAY_US 20000 + +#define PCI_DEVICE_ID_INTEL_CHT_XHCI 0x22b5 +#define CHT_MODEM_USB_VENDOR_ID 0x8087 +#define CHT_MODEM_USB_BOOT_PRODUCT_ID 0x07ef +#define CHT_MODEM_USB_MBIM_PRODUCT_ID 0x0911 +#define CHT_MODEM_STATUS_TRIGGER_DELAY (50 * HZ) +#define CHT_MODEM_STATUS_RETRY_DELAY (5 * HZ) +#define CHT_MODEM_ENUMERATION_TIMEOUT (90 * HZ) + +static const guid_t cht_modem_dsm_guid =3D + GUID_INIT(0xac340cb7, 0xe901, 0x45bf, + 0xb7, 0xe6, 0x2b, 0x34, 0xec, 0x93, 0x1e, 0x23); + +struct cht_modem { + struct device *dev; + struct regmap *pmic_regmap; + struct pci_dev *xhci; + struct delayed_work trigger_status_work; + struct delayed_work release_xhci_work; + /* Protects powered and serializes firmware operations. */ + struct mutex lock; + bool xhci_runtime_held; + bool powered; +}; + +struct cht_modem_usb_state { + bool boot; + bool runtime; + int status_ret; +}; + +static const struct dmi_system_id cht_modem_dmi_table[] =3D { + { + .matches =3D { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91L"), + }, + }, + { } +}; + +static int cht_modem_evaluate_dsm(struct cht_modem *modem, u64 function) +{ + union acpi_object params[4] =3D { + { + .buffer =3D { + .type =3D ACPI_TYPE_BUFFER, + .length =3D sizeof(cht_modem_dsm_guid), + .pointer =3D (u8 *)&cht_modem_dsm_guid, + }, + }, + { + .integer =3D { + .type =3D ACPI_TYPE_INTEGER, + .value =3D CHT_MODEM_DSM_REVISION, + }, + }, + { + .integer =3D { + .type =3D ACPI_TYPE_INTEGER, + .value =3D function, + }, + }, + { + .package =3D { + .type =3D ACPI_TYPE_PACKAGE, + .count =3D 0, + .elements =3D NULL, + }, + }, + }; + struct acpi_object_list input =3D { + .count =3D ARRAY_SIZE(params), + .pointer =3D params, + }; + acpi_status status; + + /* These firmware functions perform an action without returning data. */ + status =3D acpi_evaluate_object(ACPI_HANDLE(modem->dev), "_DSM", &input, + NULL); + if (ACPI_FAILURE(status)) { + dev_err(modem->dev, "_DSM function %llu failed: %s\n", function, + acpi_format_exception(status)); + return -EIO; + } + + return 0; +} + +static int cht_modem_set_pmic_power(struct cht_modem *modem, bool on) +{ + unsigned int value =3D on ? CHT_MODEM_PMIC_CTRL_ON : 0; + int ret; + + ret =3D regmap_update_bits(modem->pmic_regmap, CHT_MODEM_PMIC_CTRL_REG, + CHT_MODEM_PMIC_CTRL_MASK, value); + if (ret) + dev_err(modem->dev, "failed to set modem PMIC power: %d\n", ret); + + return ret; +} + +static void cht_modem_release_xhci_runtime(struct cht_modem *modem) +{ + if (!modem->xhci_runtime_held) + return; + + pm_runtime_put(&modem->xhci->dev); + modem->xhci_runtime_held =3D false; + dev_info(modem->dev, + "released xHCI runtime hold after modem enumeration window\n"); +} + +static int cht_modem_check_usb_device(struct usb_device *udev, void *data) +{ + struct cht_modem_usb_state *state =3D data; + u16 product; + u16 status; + + if (le16_to_cpu(udev->descriptor.idVendor) !=3D + CHT_MODEM_USB_VENDOR_ID) + return 0; + + product =3D le16_to_cpu(udev->descriptor.idProduct); + if (product =3D=3D CHT_MODEM_USB_MBIM_PRODUCT_ID) { + state->runtime =3D true; + return 1; + } + + if (product !=3D CHT_MODEM_USB_BOOT_PRODUCT_ID) + return 0; + + state->boot =3D true; + state->status_ret =3D usb_get_std_status(udev, USB_RECIP_DEVICE, 0, + &status); + return 1; +} + +static void cht_modem_trigger_status_work(struct work_struct *work) +{ + struct cht_modem *modem =3D + container_of(to_delayed_work(work), struct cht_modem, + trigger_status_work); + struct cht_modem_usb_state state =3D { }; + + mutex_lock(&modem->lock); + if (!modem->powered || !modem->xhci_runtime_held) + goto out; + + usb_for_each_dev(&state, cht_modem_check_usb_device); + if (state.runtime) { + dev_info(modem->dev, "XMM7260 MBIM runtime interface detected\n"); + goto out; + } + + if (state.boot) { + if (state.status_ret && state.status_ret !=3D -ENODEV && + state.status_ret !=3D -ESHUTDOWN) + dev_warn(modem->dev, + "XMM7260 boot-interface GET_STATUS failed: %d\n", + state.status_ret); + else + dev_info(modem->dev, + "triggered XMM7260 boot-interface GET_STATUS\n"); + } + + /* Retry until the runtime interface appears or the hold expires. */ + mod_delayed_work(system_dfl_wq, &modem->trigger_status_work, + CHT_MODEM_STATUS_RETRY_DELAY); +out: + mutex_unlock(&modem->lock); +} + +static void cht_modem_release_xhci_work(struct work_struct *work) +{ + struct cht_modem *modem =3D + container_of(to_delayed_work(work), struct cht_modem, + release_xhci_work); + + cancel_delayed_work(&modem->trigger_status_work); + mutex_lock(&modem->lock); + cht_modem_release_xhci_runtime(modem); + mutex_unlock(&modem->lock); +} + +static int cht_modem_hold_xhci_runtime(struct cht_modem *modem) +{ + modem->xhci =3D pci_get_device(PCI_VENDOR_ID_INTEL, + PCI_DEVICE_ID_INTEL_CHT_XHCI, NULL); + if (!modem->xhci) + return -EPROBE_DEFER; + + /* + * The XMM7260 first enumerates as 8087:07ef and takes roughly 48 + * seconds to re-enumerate as the 8087:0911 MBIM modem. Keep xHCI in + * D0 across that window; otherwise SSIC link training stops in RxDetect. + */ + pm_runtime_get_noresume(&modem->xhci->dev); + modem->xhci_runtime_held =3D true; + mod_delayed_work(system_dfl_wq, &modem->release_xhci_work, + CHT_MODEM_ENUMERATION_TIMEOUT); + + return 0; +} + +static struct regmap *cht_modem_get_pmic_regmap(struct device *dev) +{ + struct acpi_device *adev; + struct device *pmic_dev; + struct regmap *regmap; + + adev =3D acpi_dev_get_first_match_dev(CHT_MODEM_PMIC_HID, NULL, -1); + if (!adev) + return ERR_PTR(-EPROBE_DEFER); + + pmic_dev =3D get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!pmic_dev) + return ERR_PTR(-EPROBE_DEFER); + + regmap =3D dev_get_regmap(pmic_dev, NULL); + if (!regmap) { + put_device(pmic_dev); + return ERR_PTR(-EPROBE_DEFER); + } + + if (!device_link_add(dev, pmic_dev, DL_FLAG_AUTOREMOVE_CONSUMER)) { + put_device(pmic_dev); + return ERR_PTR(-ENOMEM); + } + + put_device(pmic_dev); + return regmap; +} + +static int cht_modem_power_on(struct cht_modem *modem) +{ + int ret =3D 0; + + mutex_lock(&modem->lock); + if (!modem->powered) { + ret =3D cht_modem_set_pmic_power(modem, true); + if (ret) + goto out; + + usleep_range(CHT_MODEM_PMIC_POWER_DELAY_US, + CHT_MODEM_PMIC_POWER_DELAY_US + 1000); + /* MRST also cycles the SSIC pull-down/pull-up state around MDON. */ + ret =3D cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_RESET); + if (!ret) { + modem->powered =3D true; + dev_info(modem->dev, + "powered on and holding xHCI for SSIC enumeration\n"); + } else { + cht_modem_set_pmic_power(modem, false); + } + } + +out: + mutex_unlock(&modem->lock); + + return ret; +} + +static void cht_modem_power_off(struct cht_modem *modem) +{ + int ret; + + mutex_lock(&modem->lock); + if (modem->powered) { + ret =3D cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_POWER_OFF); + if (!ret) + ret =3D cht_modem_set_pmic_power(modem, false); + if (!ret) + modem->powered =3D false; + } + mutex_unlock(&modem->lock); +} + +static int cht_modem_probe(struct platform_device *pdev) +{ + struct cht_modem *modem; + int ret; + + if (!dmi_check_system(cht_modem_dmi_table)) + return -ENODEV; + + /* + * INT34D0 advertises functions 0 and 1 only, despite also implementing + * the power-on function used below. + */ + if (!acpi_check_dsm(ACPI_HANDLE(&pdev->dev), &cht_modem_dsm_guid, + CHT_MODEM_DSM_REVISION, + BIT(CHT_MODEM_DSM_POWER_OFF))) + return -ENODEV; + + modem =3D devm_kzalloc(&pdev->dev, sizeof(*modem), GFP_KERNEL); + if (!modem) + return -ENOMEM; + + modem->dev =3D &pdev->dev; + modem->pmic_regmap =3D cht_modem_get_pmic_regmap(&pdev->dev); + if (IS_ERR(modem->pmic_regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(modem->pmic_regmap), + "failed to get Whiskey Cove PMIC regmap\n"); + + mutex_init(&modem->lock); + INIT_DELAYED_WORK(&modem->release_xhci_work, + cht_modem_release_xhci_work); + INIT_DELAYED_WORK(&modem->trigger_status_work, + cht_modem_trigger_status_work); + platform_set_drvdata(pdev, modem); + + ret =3D cht_modem_hold_xhci_runtime(modem); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to hold Cherry Trail xHCI runtime PM\n"); + + ret =3D cht_modem_power_on(modem); + if (ret) { + cancel_delayed_work_sync(&modem->release_xhci_work); + cht_modem_release_xhci_runtime(modem); + pci_dev_put(modem->xhci); + modem->xhci =3D NULL; + } else { + mod_delayed_work(system_dfl_wq, &modem->trigger_status_work, + CHT_MODEM_STATUS_TRIGGER_DELAY); + } + + return ret; +} + +static void cht_modem_remove(struct platform_device *pdev) +{ + struct cht_modem *modem =3D platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&modem->trigger_status_work); + cancel_delayed_work_sync(&modem->release_xhci_work); + mutex_lock(&modem->lock); + cht_modem_release_xhci_runtime(modem); + mutex_unlock(&modem->lock); + cht_modem_power_off(modem); + pci_dev_put(modem->xhci); +} + +static void cht_modem_shutdown(struct platform_device *pdev) +{ + struct cht_modem *modem =3D platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&modem->trigger_status_work); + cancel_delayed_work_sync(&modem->release_xhci_work); + cht_modem_power_off(modem); +} + +static const struct acpi_device_id cht_modem_acpi_ids[] =3D { + { "INT34D0" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cht_modem_acpi_ids); + +static struct platform_driver cht_modem_driver =3D { + .driver =3D { + .name =3D "intel-cht-modem", + .acpi_match_table =3D cht_modem_acpi_ids, + }, + .probe =3D cht_modem_probe, + .remove =3D cht_modem_remove, + .shutdown =3D cht_modem_shutdown, +}; + +static int __init cht_modem_init(void) +{ + return platform_driver_register(&cht_modem_driver); +} +subsys_initcall(cht_modem_init); + +static void __exit cht_modem_exit(void) +{ + platform_driver_unregister(&cht_modem_driver); +} +module_exit(cht_modem_exit); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI modem power driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0