* [RFC PATCH 0/2] x86/usb: Add Yoga Book XMM7260 modem power sequencing @ 2026-08-26 13:24 Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 1/2] xhci: Restore SSIC state on the Lenovo Yoga Book Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver Maurizio Casciano 0 siblings, 2 replies; 4+ messages in thread From: Maurizio Casciano @ 2026-08-26 13:24 UTC (permalink / raw) To: Hans de Goede, Mathias Nyman Cc: Ilpo Jarvinen, Greg Kroah-Hartman, platform-driver-x86, linux-usb, linux-kernel, Maurizio Casciano The Lenovo Yoga Book YB1-X91L connects an Intel XMM7260 modem through a Cherry Trail xHCI SSIC port. Firmware exposes an INT34D0 ACPI device, but the modem requires both SSIC controller restoration and an ACPI/PMIC power sequence before its USB boot function can transition to MBIM. Add a DMI-scoped xHCI quirk which restores the SSIC vendor state across setup and resume, followed by a platform driver which performs the firmware power sequence and coordinates xHCI runtime PM while waiting for the runtime function. This is an RFC because live XMM7260-to-MBIM enumeration and suspend/resume acceptance are not yet complete. Feedback is particularly requested on the xHCI/platform-driver split and the ownership of the SSIC sequencing. Maurizio Casciano (2): xhci: Restore SSIC state on the Lenovo Yoga Book platform/x86: Add Cherry Trail XMM7260 power driver MAINTAINERS | 1 + drivers/platform/x86/intel/Kconfig | 16 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/cht_modem.c | 418 +++++++++++++++++++++++++ drivers/usb/host/xhci-pci.c | 114 ++++++- drivers/usb/host/xhci-ring.c | 31 ++ drivers/usb/host/xhci.h | 1 + 7 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/x86/intel/cht_modem.c base-commit: 45c13f3f9e3bb15fd89ff2864c6f627a3b4b4229 -- 2.53.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* [RFC PATCH 1/2] xhci: Restore SSIC state on the Lenovo Yoga Book 2026-08-26 13:24 [RFC PATCH 0/2] x86/usb: Add Yoga Book XMM7260 modem power sequencing Maurizio Casciano @ 2026-08-26 13:24 ` Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver Maurizio Casciano 1 sibling, 0 replies; 4+ messages in thread From: Maurizio Casciano @ 2026-08-26 13:24 UTC (permalink / raw) To: Hans de Goede, Mathias Nyman Cc: Ilpo Jarvinen, Greg Kroah-Hartman, platform-driver-x86, linux-usb, linux-kernel, Maurizio Casciano 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/574bae692716f1b14093497bfab8a007fe8e460b/drivers/usb/host/xhci-intel-cap.c Signed-off-by: Maurizio Casciano <mauriziocasciano7@gmail.com> 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 <linux/slab.h> #include <linux/module.h> #include <linux/acpi.h> +#include <linux/dmi.h> #include <linux/reset.h> #include <linux/suspend.h> #include <linux/bitfield.h> @@ -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 @@ -99,6 +111,87 @@ static const char hcd_name[] = "xhci_hcd"; static struct hc_driver __read_mostly xhci_pci_hc_driver; +static const struct dmi_system_id xhci_yogabook_ssic_dmi_table[] = { + { + .matches = { + 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 = (u8 __iomem *)xhci->cap_regs; + int offset; + int i; + + offset = 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 = 0; i < SSIC_PORT_NUM; i++) { + void __iomem *reg = base + offset + SSIC_ACCESS_CTRL + + i * SSIC_ACCESS_CTRL_OFFSET; + u32 val = 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 = (u8 __iomem *)xhci->cap_regs; + void __iomem *reg; + int host_cap; + u32 val; + + host_cap = 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 = base + host_cap + DUAL_ROLE_CFG0; + val = 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 = (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 = base + SSIC_PORT_CFG2; + val = readl(reg) & ~SSIC_RETRAIN_TIMEOUT; + writel(val, reg); + readl(reg); + + reg = base + SSIC_SS_PORT_LINK_CTRL; + val = 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_device *hdev, @@ -383,6 +476,12 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) if (pdev->vendor == PCI_VENDOR_ID_INTEL && pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) xhci->quirks |= XHCI_SSIC_PORT_UNUSED; + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI && + dmi_check_system(xhci_yogabook_ssic_dmi_table)) { + xhci->quirks |= XHCI_YOGABOOK_SSIC; + xhci_yogabook_ssic_prepare_reset(xhci); + } if (pdev->vendor == PCI_VENDOR_ID_INTEL && (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI || pdev->device == PCI_DEVICE_ID_INTEL_SUNRISEPOINT_LP_XHCI || @@ -586,6 +685,9 @@ static int xhci_pci_setup(struct usb_hcd *hcd) xhci->allow_single_roothub = 1; + if (xhci->quirks & XHCI_YOGABOOK_SSIC) + xhci_yogabook_ssic_configure(xhci); + if (xhci->quirks & XHCI_PME_STUCK_QUIRK) xhci_pme_acpi_rtd3_enable(pdev); @@ -857,6 +959,7 @@ static int xhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) struct pci_dev *pdev = to_pci_dev(hcd->self.controller); bool power_lost = msg.event == PM_EVENT_RESTORE; bool is_auto_resume = msg.event == PM_EVENT_AUTO_RESUME; + int ret; reset_control_reset(xhci->reset); @@ -881,13 +984,22 @@ static int xhci_pci_resume(struct usb_hcd *hcd, pm_message_t msg) if (pdev->vendor == PCI_VENDOR_ID_INTEL) usb_enable_intel_xhci_ports(pdev); + 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); if (xhci->quirks & XHCI_PME_STUCK_QUIRK) xhci_pme_quirk(hcd); - return xhci_resume(xhci, power_lost, is_auto_resume); + ret = xhci_resume(xhci, power_lost, is_auto_resume); + if (!ret && (xhci->quirks & XHCI_YOGABOOK_SSIC)) + xhci_yogabook_ssic_configure(xhci); + + return ret; } 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" +#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); +static void xhci_yogabook_ssic_update_retrain(struct xhci_hcd *xhci, + struct xhci_port *port, + u32 portsc) +{ + u8 __iomem *base = (u8 __iomem *)xhci->cap_regs; + void __iomem *reg = base + YOGABOOK_SSIC_RETRAIN_CFG2; + u32 val; + + if (!(xhci->quirks & XHCI_YOGABOOK_SSIC) || + port->rhub != &xhci->usb3_rhub || + port->hcd_portnum + 1 != YOGABOOK_SSIC_USB3_PORT || + !(portsc & (PORT_CSC | PORT_PLC))) + return; + + val = readl(reg); + if (!(portsc & PORT_CONNECT)) + val &= ~YOGABOOK_SSIC_RETRAIN_TIMEOUT; + else if ((portsc & PORT_PLS_MASK) == XDEV_U0) + val |= 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); trace_xhci_handle_port_status(port, portsc); + xhci_yogabook_ssic_update_retrain(xhci, port, portsc); if (hcd->state == 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) unsigned int num_active_eps; unsigned int limit_active_eps; -- 2.53.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver 2026-08-26 13:24 [RFC PATCH 0/2] x86/usb: Add Yoga Book XMM7260 modem power sequencing Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 1/2] xhci: Restore SSIC state on the Lenovo Yoga Book Maurizio Casciano @ 2026-08-26 13:24 ` Maurizio Casciano 2026-09-18 16:00 ` Ilpo Järvinen 1 sibling, 1 reply; 4+ messages in thread From: Maurizio Casciano @ 2026-08-26 13:24 UTC (permalink / raw) To: Hans de Goede, Mathias Nyman Cc: Ilpo Jarvinen, Greg Kroah-Hartman, platform-driver-x86, linux-usb, linux-kernel, Maurizio Casciano 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/574bae692716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_acpi.c Link: https://github.com/jekhor/yogabook-linux-android-kernel/blob/574bae692716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_pmic.c Link: https://github.com/jekhor/yogabook-linux/blob/96acd46c5a03565a114a0c6602734bb02717639a/devices/YB1-X91L/acpi/DSDT.dsl Signed-off-by: Maurizio Casciano <mauriziocasciano7@gmail.com> 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-driver-x86/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/olpc/ F: drivers/platform/x86/ +F: drivers/platform/x86/intel/cht_modem.c F: include/linux/platform_data/x86/ X86 PLATFORM UV HPE SUPERDOME FLEX diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/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. +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/intel/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) += vsec.o intel-target-$(CONFIG_INTEL_BYTCRC_PWRSRC) += bytcrc_pwrsrc.o intel-target-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += bxtwc_tmu.o intel-target-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += chtdc_ti_pwrbtn.o +intel-target-$(CONFIG_INTEL_CHT_MODEM) += cht_modem.o intel-target-$(CONFIG_INTEL_CHTWC_INT33FE) += chtwc_int33fe.o intel-target-$(CONFIG_X86_ANDROID_TABLETS) += crystal_cove_charger.o intel-target-$(CONFIG_INTEL_MRFLD_PWRBTN) += 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 <linux/acpi.h> +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/usb.h> +#include <linux/uuid.h> +#include <linux/workqueue.h> + +#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 = + 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[] = { + { + .matches = { + 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] = { + { + .buffer = { + .type = ACPI_TYPE_BUFFER, + .length = sizeof(cht_modem_dsm_guid), + .pointer = (u8 *)&cht_modem_dsm_guid, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = CHT_MODEM_DSM_REVISION, + }, + }, + { + .integer = { + .type = ACPI_TYPE_INTEGER, + .value = function, + }, + }, + { + .package = { + .type = ACPI_TYPE_PACKAGE, + .count = 0, + .elements = NULL, + }, + }, + }; + struct acpi_object_list input = { + .count = ARRAY_SIZE(params), + .pointer = params, + }; + acpi_status status; + + /* These firmware functions perform an action without returning data. */ + status = 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 = on ? CHT_MODEM_PMIC_CTRL_ON : 0; + int ret; + + ret = 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 = 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 = data; + u16 product; + u16 status; + + if (le16_to_cpu(udev->descriptor.idVendor) != + CHT_MODEM_USB_VENDOR_ID) + return 0; + + product = le16_to_cpu(udev->descriptor.idProduct); + if (product == CHT_MODEM_USB_MBIM_PRODUCT_ID) { + state->runtime = true; + return 1; + } + + if (product != CHT_MODEM_USB_BOOT_PRODUCT_ID) + return 0; + + state->boot = true; + state->status_ret = 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 = + container_of(to_delayed_work(work), struct cht_modem, + trigger_status_work); + struct cht_modem_usb_state state = { }; + + 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 != -ENODEV && + state.status_ret != -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 = + 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 = 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 = 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 = acpi_dev_get_first_match_dev(CHT_MODEM_PMIC_HID, NULL, -1); + if (!adev) + return ERR_PTR(-EPROBE_DEFER); + + pmic_dev = get_device(acpi_get_first_physical_node(adev)); + acpi_dev_put(adev); + if (!pmic_dev) + return ERR_PTR(-EPROBE_DEFER); + + regmap = 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 = 0; + + mutex_lock(&modem->lock); + if (!modem->powered) { + ret = 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 = cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_RESET); + if (!ret) { + modem->powered = 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 = cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_POWER_OFF); + if (!ret) + ret = cht_modem_set_pmic_power(modem, false); + if (!ret) + modem->powered = 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 = devm_kzalloc(&pdev->dev, sizeof(*modem), GFP_KERNEL); + if (!modem) + return -ENOMEM; + + modem->dev = &pdev->dev; + modem->pmic_regmap = 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 = 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 = 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 = 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 = 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 = 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[] = { + { "INT34D0" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cht_modem_acpi_ids); + +static struct platform_driver cht_modem_driver = { + .driver = { + .name = "intel-cht-modem", + .acpi_match_table = cht_modem_acpi_ids, + }, + .probe = cht_modem_probe, + .remove = cht_modem_remove, + .shutdown = 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"); -- 2.53.0 ^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver 2026-08-26 13:24 ` [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver Maurizio Casciano @ 2026-09-18 16:00 ` Ilpo Järvinen 0 siblings, 0 replies; 4+ messages in thread From: Ilpo Järvinen @ 2026-09-18 16:00 UTC (permalink / raw) To: Maurizio Casciano Cc: Hans de Goede, Mathias Nyman, Greg Kroah-Hartman, platform-driver-x86, linux-usb, LKML On Wed, 26 Aug 2026, Maurizio Casciano wrote: > 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/574bae692716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_acpi.c > Link: https://github.com/jekhor/yogabook-linux-android-kernel/blob/574bae692716f1b14093497bfab8a007fe8e460b/drivers/staging/modem_control/mcd_pmic.c > Link: https://github.com/jekhor/yogabook-linux/blob/96acd46c5a03565a114a0c6602734bb02717639a/devices/YB1-X91L/acpi/DSDT.dsl > Signed-off-by: Maurizio Casciano <mauriziocasciano7@gmail.com> > 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-driver-x86/list/ > T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git > F: drivers/platform/olpc/ > F: drivers/platform/x86/ > +F: drivers/platform/x86/intel/cht_modem.c > F: include/linux/platform_data/x86/ > > X86 PLATFORM UV HPE SUPERDOME FLEX > diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/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. > > +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/intel/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) += vsec.o > intel-target-$(CONFIG_INTEL_BYTCRC_PWRSRC) += bytcrc_pwrsrc.o > intel-target-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += bxtwc_tmu.o > intel-target-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += chtdc_ti_pwrbtn.o > +intel-target-$(CONFIG_INTEL_CHT_MODEM) += cht_modem.o > intel-target-$(CONFIG_INTEL_CHTWC_INT33FE) += chtwc_int33fe.o > intel-target-$(CONFIG_X86_ANDROID_TABLETS) += crystal_cove_charger.o > intel-target-$(CONFIG_INTEL_MRFLD_PWRBTN) += 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 <linux/acpi.h> > +#include <linux/bitops.h> > +#include <linux/delay.h> > +#include <linux/device.h> > +#include <linux/dmi.h> Do you miss something from Kconfig? > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/mutex.h> > +#include <linux/pci.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/usb.h> > +#include <linux/uuid.h> > +#include <linux/workqueue.h> > + > +#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) Add include for BIT() and GENMASK() > +#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) Why are the is jiffies? You should define them in msecs or so and convert while giving them as input to something that takes jiffies. Make sure to add the include for the msecs_to_jiffies(). Also add the unit into the define's name > + > +static const guid_t cht_modem_dsm_guid = > + 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[] = { > + { > + .matches = { > + 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] = { > + { > + .buffer = { > + .type = ACPI_TYPE_BUFFER, > + .length = sizeof(cht_modem_dsm_guid), > + .pointer = (u8 *)&cht_modem_dsm_guid, > + }, > + }, > + { > + .integer = { > + .type = ACPI_TYPE_INTEGER, > + .value = CHT_MODEM_DSM_REVISION, > + }, > + }, > + { > + .integer = { > + .type = ACPI_TYPE_INTEGER, > + .value = function, > + }, > + }, > + { > + .package = { > + .type = ACPI_TYPE_PACKAGE, > + .count = 0, > + .elements = NULL, > + }, > + }, > + }; > + struct acpi_object_list input = { > + .count = ARRAY_SIZE(params), Add include. > + .pointer = params, > + }; > + acpi_status status; > + > + /* These firmware functions perform an action without returning data. */ > + status = 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, Add include. > + acpi_format_exception(status)); > + return -EIO; > + } > + > + return 0; > +} > + > +static int cht_modem_set_pmic_power(struct cht_modem *modem, bool on) > +{ > + unsigned int value = on ? CHT_MODEM_PMIC_CTRL_ON : 0; > + int ret; > + > + ret = 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 = 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 = data; > + u16 product; > + u16 status; > + > + if (le16_to_cpu(udev->descriptor.idVendor) != > + CHT_MODEM_USB_VENDOR_ID) Fits to one line. > + return 0; > + > + product = le16_to_cpu(udev->descriptor.idProduct); Add include. > + if (product == CHT_MODEM_USB_MBIM_PRODUCT_ID) { > + state->runtime = true; > + return 1; > + } > + > + if (product != CHT_MODEM_USB_BOOT_PRODUCT_ID) > + return 0; > + > + state->boot = true; > + state->status_ret = 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 = > + container_of(to_delayed_work(work), struct cht_modem, Add include. > + trigger_status_work); > + struct cht_modem_usb_state state = { }; > + > + mutex_lock(&modem->lock); Use guard() and return directly without gotos. > + 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 != -ENODEV && > + state.status_ret != -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"); Will this spam logs for what is "normal" behavior? Perhaps convert the if/else to switch/case if possible. > + } > + > + /* 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 = > + 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 = 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 = 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 = acpi_dev_get_first_match_dev(CHT_MODEM_PMIC_HID, NULL, -1); > + if (!adev) > + return ERR_PTR(-EPROBE_DEFER); > + > + pmic_dev = get_device(acpi_get_first_physical_node(adev)); Please move the variable declaration here (as per the long comment in cleanup.h) and use __free(put_device). > + acpi_dev_put(adev); > + if (!pmic_dev) > + return ERR_PTR(-EPROBE_DEFER); > + > + regmap = 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 = 0; > + > + mutex_lock(&modem->lock); guard() + direct return without goto. > + if (!modem->powered) { > + ret = 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 = cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_RESET); > + if (!ret) { Please reverse the logic and return error directly, the current code flow is very confusing as it hides the else branch is doing error handling / rollback. Using guard() will make this easy for you. > + modem->powered = 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 = cht_modem_evaluate_dsm(modem, CHT_MODEM_DSM_POWER_OFF); > + if (!ret) Convert to guard and handle errors first to make the code easier to follow. > + ret = cht_modem_set_pmic_power(modem, false); > + if (!ret) > + modem->powered = 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 = devm_kzalloc(&pdev->dev, sizeof(*modem), GFP_KERNEL); > + if (!modem) > + return -ENOMEM; > + > + modem->dev = &pdev->dev; > + modem->pmic_regmap = cht_modem_get_pmic_regmap(&pdev->dev); > + if (IS_ERR(modem->pmic_regmap)) Add include. > + return dev_err_probe(&pdev->dev, PTR_ERR(modem->pmic_regmap), > + "failed to get Whiskey Cove PMIC regmap\n"); Please use braces for multi-line constructs. > + > + mutex_init(&modem->lock); devm_mutex_init() + don't forget to add error handling. > + 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 = 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 = 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 = NULL; Why is this needed? Just return ret directly here and remove the extra else. > + } else { > + mod_delayed_work(system_dfl_wq, &modem->trigger_status_work, > + CHT_MODEM_STATUS_TRIGGER_DELAY); > + } > + > + return ret; After the change mentioned above, this can be just: return 0; > +} > + > +static void cht_modem_remove(struct platform_device *pdev) > +{ > + struct cht_modem *modem = 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); Why doesn't this and probe's rollback match? probe calls cht_modem_release_xhci_work() and this open codes the same? > + cht_modem_power_off(modem); > + pci_dev_put(modem->xhci); > +} > + > +static void cht_modem_shutdown(struct platform_device *pdev) > +{ > + struct cht_modem *modem = 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[] = { > + { "INT34D0" }, > + { } > +}; > +MODULE_DEVICE_TABLE(acpi, cht_modem_acpi_ids); > + > +static struct platform_driver cht_modem_driver = { > + .driver = { > + .name = "intel-cht-modem", > + .acpi_match_table = cht_modem_acpi_ids, > + }, > + .probe = cht_modem_probe, > + .remove = cht_modem_remove, > + .shutdown = 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"); > -- i. ^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-18 16:00 UTC | newest] Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-08-26 13:24 [RFC PATCH 0/2] x86/usb: Add Yoga Book XMM7260 modem power sequencing Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 1/2] xhci: Restore SSIC state on the Lenovo Yoga Book Maurizio Casciano 2026-08-26 13:24 ` [RFC PATCH 2/2] platform/x86: Add Cherry Trail XMM7260 power driver Maurizio Casciano 2026-09-18 16:00 ` Ilpo Järvinen
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®