mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/3] soc: qcom: Add Qualcomm PMIC GIO framework and RTC driver using it
@ 2026-11-11  3:28 Fenglin Wu
  2026-11-11  3:28 ` [PATCH 1/3] soc: qcom: Add pmic-gio GIO framework driver Fenglin Wu
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Fenglin Wu @ 2026-11-11  3:28 UTC (permalink / raw)
  To: linux-arm-msm, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
	Alexandre Belloni
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa,
	linux-kernel, linux-rtc, Fenglin Wu, Tingguo Cheng,
	Konrad Dybcio

Qualcomm SoCCP battery-management firmware on the Glymur platform
introduces a General I/O (GIO) interface. GIO exposes firmware
functionality through fixed-size register blocks grouped into
virtual peripherals, including core, battery, USB-C, service,
settings, debug, and OEM, etc. These blocks require block-aligned
transactions, while asynchronous firmware events are reported
as alerts identified by peripheral and alert IDs.

This series introduces a transport-agnostic PMIC GIO framework that
exposes the register space through regmap, tracks transport
availability, and routes alerts through a virtual IRQ domain.
It also provides managed interfaces for registering transports,
state notifiers, IRQ mappings, and auxiliary consumer devices.

It then adds a PMIC GLINK transport instance that converts GIO
block read/write transactions and alert notifications exchanged
over RPMSG into regmap accesses and IRQ delivery, and updated Glymur
to instantiate this GIO client while retaining the existing pmic_glink
clients.

Also, add a new RTC driver based on GIO interface to demonstrate
GIO usage, by accessing the firmware TAD real-time register block
for reading and setting the system time through the GIO regmap.

This also addresses a concern on the Glymur platform, where no
writable RTC device is available to retain the system time during
device reboot.

The battery manager, UCSI, and USB-C altmode functions currently
provided by dedicated PMIC GLINK clients can also be implemented
using the GIO interface. Changes have been made in qcom_battmgr/
ucsi_glink/pmic_glink_altmode drivers and tested on Glymur CRD
device by using the new GIO interface, including SSR scenarios.

This primarily helped validate the function and API sanity of
routing GIO alert messages through virtual interrupts, which
could not be done in the RTC driver because it does not need to
handle alert messages.

However, these client changes are not included in this series to
reduce the risk of introducing the GIO framework while migrating
all existing functionalities at the same time. They could be posted
to migrate these client drivers to their corresponding GIO auxiliary
devices and remove the legacy clients from Glymur once the GIO
implementations have been independently reviewed and accepted.

Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>

---
Fenglin Wu (3):
      soc: qcom: Add pmic-gio GIO framework driver
      soc: qcom: pmic_glink: Add GIO pmic_glink instance
      rtc: Add Qualcomm GIO RTC driver

 drivers/rtc/Kconfig                    |  11 +
 drivers/rtc/Makefile                   |   1 +
 drivers/rtc/rtc-qcom-gio.c             | 153 +++++++
 drivers/soc/qcom/Kconfig               |  29 ++
 drivers/soc/qcom/Makefile              |   2 +
 drivers/soc/qcom/pmic_gio.c            | 707 +++++++++++++++++++++++++++++++++
 drivers/soc/qcom/pmic_glink.c          |  26 +-
 drivers/soc/qcom/pmic_glink_gio.c      | 571 ++++++++++++++++++++++++++
 include/linux/soc/qcom/pmic_gio.h      | 154 +++++++
 include/linux/soc/qcom/pmic_gio_regs.h | 239 +++++++++++
 include/linux/soc/qcom/pmic_glink.h    |   1 +
 11 files changed, 1892 insertions(+), 2 deletions(-)
---
base-commit: 14825c9204bad7f4e0bd3a528cbf4a7e24e757d0
change-id: 20261011-gio-framework-rtc-driver-2026-10-12-8f26c1e53d4b

Best regards,
--  
Fenglin Wu <fenglin.wu@oss.qualcomm.com>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/3] soc: qcom: Add pmic-gio GIO framework driver
  2026-11-11  3:28 [PATCH 0/3] soc: qcom: Add Qualcomm PMIC GIO framework and RTC driver using it Fenglin Wu
@ 2026-11-11  3:28 ` Fenglin Wu
  2026-11-11  3:28 ` [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance Fenglin Wu
  2026-11-11  3:28 ` [PATCH 3/3] rtc: Add Qualcomm GIO RTC driver Fenglin Wu
  2 siblings, 0 replies; 5+ messages in thread
From: Fenglin Wu @ 2026-11-11  3:28 UTC (permalink / raw)
  To: linux-arm-msm, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
	Alexandre Belloni
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa,
	linux-kernel, linux-rtc, Fenglin Wu

Qualcomm SoCCP battery-management firmware on Glymur platform
introduces a new interface which is called General I/O (GIO).
It exposes functions through fixed-size register blocks grouped
into virtual peripherals such as core, battery, USB-C, service,
settings, debug, and OEM, etc. The data blocks must be accessed
using block-aligned transactions. It also generates alert
notifications which could be identified by a pair of peripheral
and alert ID.

Add a transport-agnostic PMIC GIO framework that validates register
accesses against the common GIO register definitions, and exposes the
firmware register space through regmap using transport-provided read
and write operations. The framework tracks transport availability
and notify consumers when its state changes, and route alerts through
a virtual IRQ domain while retaining their payload data. It also
provides a group of managed helpers for registering GIO transports,
state notifiers, IRQ mappings, and auxiliary consumer devices required
by the transport and the consumer drivers.

Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 drivers/soc/qcom/Kconfig               |  16 +
 drivers/soc/qcom/Makefile              |   1 +
 drivers/soc/qcom/pmic_gio.c            | 707 +++++++++++++++++++++++++++++++++
 include/linux/soc/qcom/pmic_gio.h      | 154 +++++++
 include/linux/soc/qcom/pmic_gio_regs.h | 239 +++++++++++
 5 files changed, 1117 insertions(+)

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 535c8619197b..266a1babc108 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -121,6 +121,22 @@ config QCOM_PMIC_GLINK
 	  Say yes here to support USB-C and battery status on modern Qualcomm
 	  platforms.
 
+config QCOM_PMIC_GIO
+	tristate "Qualcomm PMIC GIO framework"
+	depends on ARM64 || COMPILE_TEST
+	select AUXILIARY_BUS
+	select IRQ_DOMAIN
+	select REGMAP
+	help
+	  A generic framework driver for Qualcomm's General I/O (GIO) interface
+	  that is used to access SoCCP battery-managed firmware. It provides a
+	  register-space abstraction built on top of transport-supplied
+	  read/write operations, along with a virtual irqchip that distributes
+	  async alert messages to the consumers.
+
+	  Say yes here to enable the GIO interface on a SoCCP based battery
+	  management firmware platform.
+
 config QCOM_RAMP_CTRL
 	tristate "Qualcomm Ramp Controller driver"
 	help
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index bddd6f0a5836..c3170757b012 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_QCOM_OCMEM)	+= ocmem.o
 obj-$(CONFIG_QCOM_PD_MAPPER)	+= qcom_pd_mapper.o
 obj-$(CONFIG_QCOM_PDR_HELPERS)	+= pdr_interface.o
 obj-$(CONFIG_QCOM_PDR_MSG)	+= qcom_pdr_msg.o
+obj-$(CONFIG_QCOM_PMIC_GIO)	+= pmic_gio.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink_altmode.o
 obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG)	+= pmic_pdcharger_ulog.o
diff --git a/drivers/soc/qcom/pmic_gio.c b/drivers/soc/qcom/pmic_gio.c
new file mode 100644
index 000000000000..7237bf1c549c
--- /dev/null
+++ b/drivers/soc/qcom/pmic_gio.c
@@ -0,0 +1,707 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/soc/qcom/pmic_gio.h>
+#include <linux/soc/qcom/pmic_gio_regs.h>
+#include <linux/unaligned.h>
+
+#define PMIC_GIO_REG_ADDR_BYTES		4
+#define PMIC_GIO_MAX_XFER_SIZE		1024
+
+struct pmic_gio_reg_block {
+	u32 base;
+	u16 size;
+	bool writeable;
+};
+
+/*
+ * Every register block defined for the GIO peripherals. The firmware
+ * requires all registers of a block to be fetched/stored in a single
+ * block-aligned transaction.
+ */
+static const struct pmic_gio_reg_block pmic_gio_reg_blocks[] = {
+	/* CORE */
+	{ GIO_CORE_IF_VERSION_ADDR, GIO_CORE_IF_VERSION_SIZE, false },
+	{ GIO_CORE_ALERT_STATUS_ADDR, GIO_CORE_ALERT_STATUS_SIZE, false },
+	{ GIO_CORE_LAST_ALERT_ADDR, GIO_CORE_LAST_ALERT_SIZE, false },
+	{ GIO_CORE_GLOBAL_ALERT_CTRL_ADDR, GIO_CORE_GLOBAL_ALERT_CTRL_SIZE, true },
+	{ GIO_CORE_HOST_STATE_ADDR, GIO_CORE_HOST_STATE_SIZE, true },
+	{ GIO_CORE_SYSTEM_STATE_ADDR, GIO_CORE_SYSTEM_STATE_SIZE, true },
+	{ GIO_CORE_LAST_ERROR_ADDR, GIO_CORE_LAST_ERROR_SIZE, true },
+
+	/* BATT */
+	{ GIO_BATT_IF_VERSION_ADDR, GIO_BATT_IF_VERSION_SIZE, false },
+	{ GIO_BATT_ALERT_STATUS_ADDR, GIO_BATT_ALERT_STATUS_SIZE, false },
+	{ GIO_BATT_LAST_ALERT_ADDR, GIO_BATT_LAST_ALERT_SIZE, false },
+	{ GIO_BATT_BPRT_ADDR, GIO_BATT_BPRT_SIZE, false },
+	{ GIO_BATT_BCTI_ADDR, GIO_BATT_BCTI_SIZE, true },
+	{ GIO_BATT_BCT_ADDR, GIO_BATT_BCT_SIZE, false },
+	{ GIO_BATT_BIX_ADDR, GIO_BATT_BIX_SIZE, false },
+	{ GIO_BATT_BPC_ADDR, GIO_BATT_BPC_SIZE, false },
+	{ GIO_BATT_BPS_ADDR, GIO_BATT_BPS_SIZE, false },
+	{ GIO_BATT_BPT_ADDR, GIO_BATT_BPT_SIZE, true },
+	{ GIO_BATT_BPTS_ADDR, GIO_BATT_BPTS_SIZE, false },
+	{ GIO_BATT_BST_ADDR, GIO_BATT_BST_SIZE, false },
+	{ GIO_BATT_BATTERY_PRESENT_OCV_ADDR, GIO_BATT_BATTERY_PRESENT_OCV_SIZE, false },
+	{ GIO_BATT_BTH_ADDR, GIO_BATT_BTH_SIZE, true },
+	{ GIO_BATT_BTMI_ADDR, GIO_BATT_BTMI_SIZE, true },
+	{ GIO_BATT_BTM_ADDR, GIO_BATT_BTM_SIZE, false },
+	{ GIO_BATT_BTP_ADDR, GIO_BATT_BTP_SIZE, true },
+	{ GIO_BATT_PSR_ADDR, GIO_BATT_PSR_SIZE, false },
+	{ GIO_BATT_PIF_ADDR, GIO_BATT_PIF_SIZE, false },
+	{ GIO_BATT_SHIP_MODE_EN_ADDR, GIO_BATT_SHIP_MODE_EN_SIZE, true },
+	{ GIO_BATT_PCS_ADDR, GIO_BATT_PCS_SIZE, false },
+	{ GIO_BATT_MNO_ADDR, GIO_BATT_MNO_SIZE, false },
+	{ GIO_BATT_SNO_ADDR, GIO_BATT_SNO_SIZE, false },
+	{ GIO_BATT_OTY_ADDR, GIO_BATT_OTY_SIZE, false },
+	{ GIO_BATT_OIF_ADDR, GIO_BATT_OIF_SIZE, false },
+	{ GIO_BATT_PORTABLE_BATTERY_INFO_ADDR, GIO_BATT_PORTABLE_BATTERY_INFO_SIZE, false },
+	{ GIO_BATT_BMD_ADDR, GIO_BATT_BMD_SIZE, false },
+	{ GIO_BATT_BMC_ADDR, GIO_BATT_BMC_SIZE, true },
+	{ GIO_BATT_MISC_CONFIG_ADDR, GIO_BATT_MISC_CONFIG_SIZE, true },
+
+	/* USBC */
+	{ GIO_USBC_IF_VERSION_ADDR, GIO_USBC_IF_VERSION_SIZE, false },
+	{ GIO_USBC_ALERT_STATUS_ADDR, GIO_USBC_ALERT_STATUS_SIZE, false },
+	{ GIO_USBC_LAST_ALERT_ADDR, GIO_USBC_LAST_ALERT_SIZE, false },
+	{ GIO_USBC_UCSI_VERSION_REG, GIO_USBC_UCSI_VERSION_SIZE, false },
+	{ GIO_USBC_UCSI_CCI_REG, GIO_USBC_UCSI_CCI_SIZE, false },
+	{ GIO_USBC_UCSI_CONTROL_REG, GIO_USBC_UCSI_CONTROL_SIZE, true },
+	{ GIO_USBC_UCSI_MESSAGE_IN_REG, GIO_USBC_UCSI_MESSAGE_IN_SIZE, false },
+	{ GIO_USBC_UCSI_MSGINX_REG, GIO_USBC_UCSI_MSGINX_SIZE, false },
+	{ GIO_USBC_UCSI_MESSAGE_OUT_REG, GIO_USBC_UCSI_MESSAGE_OUT_SIZE, false },
+	{ GIO_USBC_UCSI_MSGOUTX_ADDR, GIO_USBC_UCSI_MSGOUTX_SIZE, false },
+	{ GIO_USBC_UCSI_PAN_ADDR, GIO_USBC_UCSI_PAN_SIZE, false },
+	{ GIO_USBC_PAN_CMD_ADDR, GIO_USBC_PAN_CMD_SIZE, true },
+	{ GIO_USBC_RTMR_INFO_ADDR, GIO_USBC_RTMR_INFO_SIZE, false },
+	{ GIO_USBC_RTMR_FW_UPDATE_ADDR, GIO_USBC_RTMR_FW_UPDATE_SIZE, true },
+	{ GIO_USBC_USBCFG_ADDR, GIO_USBC_USBCFG_SIZE, true },
+	{ GIO_USBC_DAM_CTRL_ADDR, GIO_USBC_DAM_CTRL_SIZE, true },
+	{ GIO_USBC_DAM_CTRL_CFG_ADDR, GIO_USBC_DAM_CTRL_CFG_SIZE, false },
+
+	/* SVC */
+	{ GIO_SVC_IF_VERSION_ADDR, GIO_SVC_IF_VERSION_SIZE, false },
+	{ GIO_SVC_ALERT_STATUS_ADDR, GIO_SVC_ALERT_STATUS_SIZE, false },
+	{ GIO_SVC_LAST_ALERT_ADDR, GIO_SVC_LAST_ALERT_SIZE, false },
+	{ GIO_SVC_UEFI_ACTION_ADDR, GIO_SVC_UEFI_ACTION_SIZE, false },
+	{ GIO_SVC_POWER_OK_ADDR, GIO_SVC_POWER_OK_SIZE, false },
+	{ GIO_SVC_OOB_REQUEST_ACTION_ADDR, GIO_SVC_OOB_REQUEST_ACTION_SIZE, true },
+	{ GIO_SVC_OOB_RTCTMR_VOTE_ADDR, GIO_SVC_OOB_RTCTMR_VOTE_SIZE, true },
+	{ GIO_SVC_CLT0_REQUEST_ACTION_ADDR, GIO_SVC_CLT0_REQUEST_ACTION_SIZE, true },
+	{ GIO_SVC_CLT0_RTCTMR_VOTE_ADDR, GIO_SVC_CLT0_RTCTMR_VOTE_SIZE, true },
+	{ GIO_SVC_SYS_SET_POWER_STATE_ADDR, GIO_SVC_SYS_SET_POWER_STATE_SIZE, true },
+	{ GIO_SVC_TAD_RT_ADDR, GIO_SVC_TAD_RT_SIZE, true },
+	{ GIO_SVC_TAD_GWS_ADDR, GIO_SVC_TAD_GWS_SIZE, false },
+	{ GIO_SVC_TAD_CWS_ADDR, GIO_SVC_TAD_CWS_SIZE, true },
+	{ GIO_SVC_TAD_TPA_ADDR, GIO_SVC_TAD_TPA_SIZE, true },
+	{ GIO_SVC_TAD_TPD_ADDR, GIO_SVC_TAD_TPD_SIZE, true },
+	{ GIO_SVC_TAD_TVA_ADDR, GIO_SVC_TAD_TVA_SIZE, true },
+	{ GIO_SVC_TAD_TVD_ADDR, GIO_SVC_TAD_TVD_SIZE, true },
+	{ GIO_SVC_LID_SETTING_ADDR, GIO_SVC_LID_SETTING_SIZE, true },
+
+	/* SETTING */
+	{ GIO_SETTING_IF_VERSION_ADDR, GIO_SETTING_IF_VERSION_SIZE, false },
+	{ GIO_SETTING_ALERT_STATUS_ADDR, GIO_SETTING_ALERT_STATUS_SIZE, false },
+	{ GIO_SETTING_LAST_ALERT_ADDR, GIO_SETTING_LAST_ALERT_SIZE, false },
+	{ GIO_SETTING_SYS_PWR_CFG_ADDR, GIO_SETTING_SYS_PWR_CFG_SIZE, true },
+	{ GIO_SETTING_BATT_AUTH_ADDR, GIO_SETTING_BATT_AUTH_SIZE, true },
+	{ GIO_SETTING_CHG_LIMIT_ADDR, GIO_SETTING_CHG_LIMIT_SIZE, true },
+	{ GIO_SETTING_BOOT_ADDR, GIO_SETTING_BOOT_SIZE, true },
+	{ GIO_SETTING_USBC_ADDR, GIO_SETTING_USBC_SIZE, true },
+	{ GIO_SETTING_HOST_MAC_ADDR_ADDR, GIO_SETTING_HOST_MAC_ADDR_SIZE, true },
+	{ GIO_SETTING_AOU_ADDR, GIO_SETTING_AOU_SIZE, true },
+	{ GIO_SETTING_PSM_ADDR, GIO_SETTING_PSM_SIZE, true },
+	{ GIO_SETTING_USB_WAKE_ON_LAN_ADDR, GIO_SETTING_USB_WAKE_ON_LAN_SIZE, true },
+	{ GIO_SETTING_EPM_USBC_ADDR, GIO_SETTING_EPM_USBC_SIZE, true },
+	{ GIO_SETTING_OFFMODE_SHIPMODE_ADDR, GIO_SETTING_OFFMODE_SHIPMODE_SIZE, true },
+	{ GIO_SETTING_RTC_TIMER_FOR_EOC_ADDR, GIO_SETTING_RTC_TIMER_FOR_EOC_SIZE, true },
+	{ GIO_SETTING_INTL_CHG_ADDR, GIO_SETTING_INTL_CHG_SIZE, true },
+	{ GIO_SETTING_CMD_ADDR, GIO_SETTING_CMD_SIZE, true },
+
+	/* DBG */
+	{ GIO_DBG_IF_VERSION_ADDR, GIO_DBG_IF_VERSION_SIZE, false },
+	{ GIO_DBG_ALERT_STATUS_ADDR, GIO_DBG_ALERT_STATUS_SIZE, false },
+	{ GIO_DBG_LAST_ALERT_ADDR, GIO_DBG_LAST_ALERT_SIZE, false },
+	{ GIO_DBG_BCLIBAT_ADDR, GIO_DBG_BCLIBAT_SIZE, true },
+	{ GIO_DBG_BCLVBAT_ADDR, GIO_DBG_BCLVBAT_SIZE, true },
+	{ GIO_DBG_BCLIBAT_DGL_TIMER_ADDR, GIO_DBG_BCLIBAT_DGL_TIMER_SIZE, true },
+	{ GIO_DBG_BCLVBAT_DGL_TIMER_ADDR, GIO_DBG_BCLVBAT_DGL_TIMER_SIZE, true },
+	{ GIO_DBG_BCL_CLR_TIMER_ADDR, GIO_DBG_BCL_CLR_TIMER_SIZE, true },
+	{ GIO_DBG_CHG_DIE_TEMP_TH_ADDR, GIO_DBG_CHG_DIE_TEMP_TH_SIZE, true },
+	{ GIO_DBG_CHG_CONN_TEMP_TH_ADDR, GIO_DBG_CHG_CONN_TEMP_TH_SIZE, true },
+	{ GIO_DBG_SPMI_WRITE_ADDR, GIO_DBG_SPMI_WRITE_SIZE, true },
+	{ GIO_DBG_LOG_LEVEL_ADDR, GIO_DBG_LOG_LEVEL_SIZE, true },
+	{ GIO_DBG_LOG_CAT_ADDR, GIO_DBG_LOG_CAT_SIZE, true },
+
+	/* OEM */
+	{ GIO_OEM_IF_VERSION_ADDR, GIO_OEM_IF_VERSION_SIZE, false },
+	{ GIO_OEM_ALERT_STATUS_ADDR, GIO_OEM_ALERT_STATUS_SIZE, false },
+	{ GIO_OEM_LAST_ALERT_ADDR, GIO_OEM_LAST_ALERT_SIZE, false },
+	{ GIO_OEM_OP_RW_ADDR, GIO_OEM_OP_RW_SIZE, true },
+	{ GIO_OEM_I2C_PROXY_CONFIG_ADDR, GIO_OEM_I2C_PROXY_CONFIG_SIZE, true },
+	{ GIO_OEM_I2C_PROXY_DATA_ADDR, GIO_OEM_I2C_PROXY_DATA_SIZE, true },
+};
+
+static const struct pmic_gio_reg_block *pmic_gio_find_reg_block(unsigned int reg)
+{
+	const struct pmic_gio_reg_block *block;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(pmic_gio_reg_blocks); i++) {
+		block = &pmic_gio_reg_blocks[i];
+		if (reg == block->base)
+			return block;
+	}
+
+	return NULL;
+}
+
+static const struct pmic_gio_reg_block *pmic_gio_validate_access(unsigned int reg,
+								 size_t len)
+{
+	const struct pmic_gio_reg_block *block = pmic_gio_find_reg_block(reg);
+
+	if (!block || !len)
+		return NULL;
+
+	if (reg != block->base || len != block->size)
+		return NULL;
+
+	return block;
+}
+
+static bool pmic_gio_readable_reg(struct device *dev, unsigned int reg)
+{
+	const struct pmic_gio_reg_block *block = pmic_gio_find_reg_block(reg);
+
+	return block ? true : false;
+}
+
+static bool pmic_gio_writeable_reg(struct device *dev, unsigned int reg)
+{
+	const struct pmic_gio_reg_block *block = pmic_gio_find_reg_block(reg);
+
+	return block && block->writeable;
+}
+
+/*
+ * For registers in a block whose size exceeds 4 bytes, mark them as
+ * precious registers to prevent regmap debugfs from accessing a torn
+ * register one at a time.
+ */
+static bool pmic_gio_precious_reg(struct device *dev, unsigned int reg)
+{
+	const struct pmic_gio_reg_block *block = pmic_gio_find_reg_block(reg);
+
+	return block && block->size > PMIC_GIO_REG_GRANULARITY;
+}
+
+static const struct regmap_config pmic_gio_default_regmap_config = {
+	.reg_bits	= 32,
+	.val_bits	= PMIC_GIO_REG_GRANULARITY * 8,
+	.reg_stride	= PMIC_GIO_REG_GRANULARITY,
+	.max_register	= GIO_MAX_REG_ADDR,
+	.readable_reg	= pmic_gio_readable_reg,
+	.writeable_reg	= pmic_gio_writeable_reg,
+	.precious_reg	= pmic_gio_precious_reg,
+	.name		= "pmic-gio",
+};
+
+static const struct pmic_gio_irq pmic_gio_default_irqs[] = {
+	{ GIO_BATT_PERIPHERAL_ID, GIO_ALERT_BATT_STATUS_CHANGE },
+	{ GIO_BATT_PERIPHERAL_ID, GIO_ALERT_BATT_INFO_CHANGE },
+	{ GIO_BATT_PERIPHERAL_ID, GIO_ALERT_BATT_MAINTENANCE_STATUS_CHANGE },
+	{ GIO_BATT_PERIPHERAL_ID, GIO_ALERT_BATT_POWER_STATE_CHANGE },
+	{ GIO_BATT_PERIPHERAL_ID, GIO_ALERT_BATT_POWER_SOURCE_INFO_CHANGE },
+	{ GIO_USBC_PERIPHERAL_ID, GIO_ALERT_USBC_UCSI_CCI },
+	{ GIO_USBC_PERIPHERAL_ID, GIO_ALERT_USBC_PAN },
+	{ GIO_SVC_PERIPHERAL_ID, GIO_ALERT_SVC_POWER_OK_CHANGED },
+	{ GIO_SVC_PERIPHERAL_ID, GIO_ALERT_SVC_SHUTDOWN },
+	{ GIO_SVC_PERIPHERAL_ID, GIO_ALERT_SVC_RESET },
+	{ GIO_OEM_PERIPHERAL_ID, GIO_ALERT_OEM_OP_NOTIFY },
+};
+
+static const struct pmic_gio_irq_chip pmic_gio_default_irq_chip = {
+	.irqs = pmic_gio_default_irqs,
+	.num_irqs = ARRAY_SIZE(pmic_gio_default_irqs),
+};
+
+static int pmic_gio_regmap_bus_read(void *context, const void *reg, size_t reg_size,
+				    void *val, size_t val_size)
+{
+	const struct pmic_gio_reg_block *block;
+	struct pmic_gio_device *gio = context;
+	u32 addr;
+
+	if (!READ_ONCE(gio->online))
+		return -ENOTCONN;
+
+	if (reg_size != PMIC_GIO_REG_ADDR_BYTES)
+		return -EINVAL;
+
+	addr = get_unaligned_le32(reg);
+	block = pmic_gio_validate_access(addr, val_size);
+	if (!block)
+		return -EINVAL;
+
+	return gio->ops->read(gio, addr, val, val_size);
+}
+
+static int pmic_gio_regmap_bus_gather_write(void *context, const void *reg, size_t reg_size,
+					    const void *val, size_t val_size)
+{
+	const struct pmic_gio_reg_block *block;
+	struct pmic_gio_device *gio = context;
+	u32 addr;
+
+	if (!READ_ONCE(gio->online))
+		return -ENOTCONN;
+
+	if (reg_size != PMIC_GIO_REG_ADDR_BYTES)
+		return -EINVAL;
+
+	addr = get_unaligned_le32(reg);
+	block = pmic_gio_validate_access(addr, val_size);
+	if (!block || !block->writeable)
+		return -EINVAL;
+
+	return gio->ops->write(gio, addr, val, val_size);
+}
+
+static int pmic_gio_regmap_bus_write(void *context, const void *data, size_t count)
+{
+	const u8 *val;
+
+	if (count < PMIC_GIO_REG_ADDR_BYTES)
+		return -EINVAL;
+
+	val = (const u8 *)data + PMIC_GIO_REG_ADDR_BYTES;
+
+	return pmic_gio_regmap_bus_gather_write(context, data, PMIC_GIO_REG_ADDR_BYTES,
+						val, count - PMIC_GIO_REG_ADDR_BYTES);
+}
+
+static const struct regmap_bus pmic_gio_regmap_bus = {
+	.read = pmic_gio_regmap_bus_read,
+	.write = pmic_gio_regmap_bus_write,
+	.gather_write = pmic_gio_regmap_bus_gather_write,
+	.reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+	.val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+	.max_raw_read = PMIC_GIO_MAX_XFER_SIZE,
+	.max_raw_write = PMIC_GIO_MAX_XFER_SIZE,
+};
+
+struct pmic_gio_alert_data {
+	struct mutex dispatch_lock; /* Serialize alert publication and dispatch */
+	struct mutex data_lock; /* Protect alert data payload */
+	u8 payload[PMIC_GIO_ALERT_PAYLOAD_SIZE];
+};
+
+struct pmic_gio_irq_chip_data {
+	const struct pmic_gio_irq_chip	*chip;
+	struct fwnode_handle *fwnode;
+	struct irq_domain *domain;
+	unsigned int *virqs;
+	struct pmic_gio_alert_data *alerts;
+};
+
+struct pmic_gio_notifier {
+	struct pmic_gio_device *gio;
+	struct notifier_block *nb;
+};
+
+/* The alert IRQ couldn't be masked/unmasked separately */
+static void pmic_gio_irq_dummy(struct irq_data *data)
+{
+}
+
+static const struct irq_chip pmic_gio_irq_chip_ops = {
+	.name = "pmic-gio",
+	.irq_mask = pmic_gio_irq_dummy,
+	.irq_unmask = pmic_gio_irq_dummy,
+};
+
+static int pmic_gio_irq_domain_map(struct irq_domain *d, unsigned int virq,
+				   irq_hw_number_t hwirq)
+{
+	irq_set_chip_data(virq, d->host_data);
+	irq_set_chip_and_handler(virq, &pmic_gio_irq_chip_ops, handle_simple_irq);
+	irq_set_nested_thread(virq, true);
+	irq_set_noprobe(virq);
+
+	return 0;
+}
+
+static int pmic_gio_irq_to_hwirq(const struct pmic_gio_irq_chip *chip, u8 peripheral_id,
+				 u8 alert_id, irq_hw_number_t *hwirq)
+{
+	int i;
+
+	for (i = 0; i < chip->num_irqs; i++) {
+		if (chip->irqs[i].peripheral_id == peripheral_id &&
+		    chip->irqs[i].alert_id == alert_id) {
+			*hwirq = i;
+			return 0;
+		}
+	}
+
+	return -ENOENT;
+}
+
+static int pmic_gio_irq_domain_xlate(struct irq_domain *d, struct device_node *node,
+				     const u32 *intspec, unsigned int intsize,
+				     unsigned long *out_hwirq, unsigned int *out_type)
+{
+	struct pmic_gio_irq_chip_data *chip_data = d->host_data;
+
+	if (intsize != 2 || intspec[0] > 0xff || intspec[1] > 0xff)
+		return -EINVAL;
+
+	*out_type = IRQ_TYPE_NONE;
+
+	return pmic_gio_irq_to_hwirq(chip_data->chip, intspec[0], intspec[1], out_hwirq);
+}
+
+static const struct irq_domain_ops pmic_gio_irq_domain_ops = {
+	.map = pmic_gio_irq_domain_map,
+	.xlate = pmic_gio_irq_domain_xlate,
+};
+
+static int pmic_gio_add_irq_chip(struct pmic_gio_device *gio,
+				 struct pmic_gio_irq_chip_data **data)
+{
+	const struct pmic_gio_irq_chip *chip = &pmic_gio_default_irq_chip;
+	int virq;
+
+	struct pmic_gio_irq_chip_data *chip_data __free(kfree) =
+				kzalloc_obj(*chip_data, GFP_KERNEL);
+	if (!chip_data)
+		return -ENOMEM;
+
+	unsigned int *virqs __free(kfree) =
+		kcalloc(chip->num_irqs, sizeof(*virqs), GFP_KERNEL);
+	if (!virqs)
+		return -ENOMEM;
+
+	struct pmic_gio_alert_data *alerts __free(kfree) =
+		kcalloc(chip->num_irqs, sizeof(*alerts), GFP_KERNEL);
+	if (!alerts)
+		return -ENOMEM;
+
+	chip_data->chip = chip;
+
+	chip_data->fwnode = dev_fwnode(gio->dev);
+	if (!chip_data->fwnode)
+		return -ENODEV;
+
+	chip_data->domain = irq_domain_create_linear(chip_data->fwnode, chip->num_irqs,
+						     &pmic_gio_irq_domain_ops, chip_data);
+	if (!chip_data->domain)
+		return -ENOMEM;
+
+	for (int i = 0; i < chip->num_irqs; i++) {
+		mutex_init(&alerts[i].dispatch_lock);
+		mutex_init(&alerts[i].data_lock);
+
+		virq = irq_create_mapping(chip_data->domain, i);
+		if (!virq) {
+			while (i--)
+				irq_dispose_mapping(virqs[i]);
+			irq_domain_remove(chip_data->domain);
+			return -EINVAL;
+		}
+		virqs[i] = virq;
+	}
+
+	chip_data->virqs = no_free_ptr(virqs);
+	chip_data->alerts = no_free_ptr(alerts);
+	*data = chip_data;
+	gio->chip_data = no_free_ptr(chip_data);
+
+	return 0;
+}
+
+static void pmic_gio_irq_chip_devm_release(void *data)
+{
+	struct pmic_gio_irq_chip_data *chip_data = data;
+
+	for (int i = 0; i < chip_data->chip->num_irqs; i++)
+		irq_dispose_mapping(chip_data->virqs[i]);
+
+	irq_domain_remove(chip_data->domain);
+	kfree(chip_data->alerts);
+	kfree(chip_data->virqs);
+	kfree(chip_data);
+}
+
+static void pmic_gio_aux_release(struct device *dev)
+{
+	of_node_put(dev->of_node);
+	dev->of_node = NULL;
+}
+
+static void pmic_gio_del_aux_device(void *data)
+{
+	struct auxiliary_device *aux = data;
+
+	auxiliary_device_delete(aux);
+	auxiliary_device_uninit(aux);
+}
+
+struct pmic_gio_device *devm_pmic_gio_register(struct device *dev,
+					       const struct pmic_gio_device_ops *ops,
+					       void *drvdata)
+{
+	struct pmic_gio_irq_chip_data *chip_data;
+	struct pmic_gio_device *gio;
+	int ret;
+
+	if (!ops || !ops->read || !ops->write)
+		return ERR_PTR(-EINVAL);
+
+	gio = devm_kzalloc(dev, sizeof(*gio), GFP_KERNEL);
+	if (!gio)
+		return ERR_PTR(-ENOMEM);
+
+	gio->dev = dev;
+	gio->ops = ops;
+	gio->drvdata = drvdata;
+	ATOMIC_INIT_NOTIFIER_HEAD(&gio->state_notifiers);
+
+	gio->regmap = devm_regmap_init(gio->dev, &pmic_gio_regmap_bus, gio,
+				       &pmic_gio_default_regmap_config);
+	if (IS_ERR(gio->regmap))
+		return ERR_CAST(gio->regmap);
+
+	ret = pmic_gio_add_irq_chip(gio, &chip_data);
+	if (ret)
+		return ERR_PTR(ret);
+
+	ret = devm_add_action_or_reset(dev, pmic_gio_irq_chip_devm_release, chip_data);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return gio;
+}
+EXPORT_SYMBOL_GPL(devm_pmic_gio_register);
+
+void pmic_gio_set_state(struct pmic_gio_device *gio, enum pmic_gio_state state)
+{
+	bool online;
+
+	if (!gio)
+		return;
+
+	online = state == PMIC_GIO_STATE_UP;
+	if (READ_ONCE(gio->online) == online)
+		return;
+
+	WRITE_ONCE(gio->online, online);
+	atomic_notifier_call_chain(&gio->state_notifiers, state, gio);
+}
+EXPORT_SYMBOL_GPL(pmic_gio_set_state);
+
+bool pmic_gio_is_online(struct device *dev)
+{
+	struct pmic_gio_device *gio;
+
+	if (!dev)
+		return false;
+
+	gio = dev_get_platdata(dev);
+
+	return gio && READ_ONCE(gio->online);
+}
+EXPORT_SYMBOL_GPL(pmic_gio_is_online);
+
+static void pmic_gio_unregister_notifier(void *data)
+{
+	struct pmic_gio_notifier *notifier = data;
+
+	atomic_notifier_chain_unregister(&notifier->gio->state_notifiers, notifier->nb);
+}
+
+/**
+ * devm_pmic_gio_register_notifier - register a GIO state notifier
+ * @dev: GIO consumer device
+ * @nb: notifier block to register
+ *
+ * State notifier callbacks may run in atomic context and must not sleep.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int devm_pmic_gio_register_notifier(struct device *dev, struct notifier_block *nb)
+{
+	struct pmic_gio_notifier *notifier;
+	struct pmic_gio_device *gio;
+	int ret;
+
+	if (!dev || !nb)
+		return -EINVAL;
+
+	gio = dev_get_platdata(dev);
+	if (!gio)
+		return -ENODEV;
+
+	notifier = devm_kzalloc(dev, sizeof(*notifier), GFP_KERNEL);
+	if (!notifier)
+		return -ENOMEM;
+
+	notifier->gio = gio;
+	notifier->nb = nb;
+
+	ret = atomic_notifier_chain_register(&gio->state_notifiers, nb);
+	if (ret)
+		return ret;
+
+	return devm_add_action_or_reset(dev, pmic_gio_unregister_notifier, notifier);
+}
+EXPORT_SYMBOL_GPL(devm_pmic_gio_register_notifier);
+
+/**
+ * pmic_gio_irq_find - map a GIO alert to a Linux IRQ
+ * @dev: GIO consumer device
+ * @peripheral_id: GIO peripheral identifier
+ * @alert_id: alert identifier within the peripheral
+ *
+ * Maps a given (@peripheral_id, @alert_id) pair to the Linux IRQ associated
+ * with the consumer's GIO auxiliary parent, for example:
+ *
+ *	virq = pmic_gio_irq_find(dev, GIO_USBC_PERIPHERAL_ID,
+ *				 GIO_ALERT_USBC_UCSI_CCI);
+ *
+ * Return: A Linux IRQ number on success or a negative error code on failure.
+ */
+int pmic_gio_irq_find(struct device *dev, u8 peripheral_id, u8 alert_id)
+{
+	struct irq_fwspec fwspec = {};
+	int virq;
+
+	if (!dev || !dev->parent)
+		return -EINVAL;
+
+	fwspec.fwnode = dev_fwnode(dev->parent);
+	if (!fwspec.fwnode)
+		return -ENODEV;
+
+	fwspec.param_count = 2;
+	fwspec.param[0] = peripheral_id;
+	fwspec.param[1] = alert_id;
+
+	virq = irq_create_fwspec_mapping(&fwspec);
+
+	return virq ?: -EINVAL;
+}
+EXPORT_SYMBOL_GPL(pmic_gio_irq_find);
+
+int pmic_gio_irq_get_payload(unsigned int irq, void *payload, size_t payload_size)
+{
+	struct irq_data *irq_data = irq_get_irq_data(irq);
+	struct pmic_gio_irq_chip_data *data;
+	irq_hw_number_t hwirq;
+
+	if (!irq_data || irq_data->chip != &pmic_gio_irq_chip_ops || !payload)
+		return -EINVAL;
+
+	if (payload_size < PMIC_GIO_ALERT_PAYLOAD_SIZE)
+		return -EMSGSIZE;
+
+	data = irq_data->chip_data;
+	hwirq = irqd_to_hwirq(irq_data);
+	if (hwirq >= data->chip->num_irqs)
+		return -EINVAL;
+
+	guard(mutex)(&data->alerts[hwirq].data_lock);
+	memcpy(payload, data->alerts[hwirq].payload, PMIC_GIO_ALERT_PAYLOAD_SIZE);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pmic_gio_irq_get_payload);
+
+int devm_pmic_gio_add_consumer(struct pmic_gio_device *gio, struct auxiliary_device *aux,
+			       const char *name)
+{
+	int ret;
+
+	aux->name = name;
+	aux->dev.parent = gio->dev;
+	aux->dev.release = pmic_gio_aux_release;
+	aux->dev.platform_data = gio;
+	device_set_of_node_from_dev(&aux->dev, gio->dev);
+	ret = auxiliary_device_init(aux);
+	if (ret) {
+		of_node_put(aux->dev.of_node);
+		aux->dev.of_node = NULL;
+		return ret;
+	}
+
+	ret = auxiliary_device_add(aux);
+	if (ret) {
+		auxiliary_device_uninit(aux);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(gio->dev, pmic_gio_del_aux_device, aux);
+}
+EXPORT_SYMBOL_GPL(devm_pmic_gio_add_consumer);
+
+/**
+ * pmic_gio_handle_alert - dispatch a GIO alert to its IRQ handler
+ * @gio: GIO device receiving the alert
+ * @peripheral_id: GIO peripheral identifier
+ * @alert_id: alert identifier within the peripheral
+ * @payload: alert payload
+ *
+ * The transport layer calls this function when a GIO alert arrives. This
+ * function may sleep and must be called from sleepable context.
+ */
+void pmic_gio_handle_alert(struct pmic_gio_device *gio,
+			   u8 peripheral_id, u8 alert_id,
+			   const void *payload)
+{
+	struct pmic_gio_irq_chip_data *data = gio->chip_data;
+	struct pmic_gio_alert_data *alert;
+	irq_hw_number_t hwirq;
+	int virq;
+
+	if (!payload || !READ_ONCE(gio->online))
+		return;
+
+	if (gio->ops->handle_alert &&
+	    gio->ops->handle_alert(gio, peripheral_id, alert_id, payload))
+		return;
+
+	if (pmic_gio_irq_to_hwirq(data->chip, peripheral_id, alert_id, &hwirq))
+		return;
+
+	virq = irq_find_mapping(data->domain, hwirq);
+	if (!virq)
+		return;
+
+	alert = &data->alerts[hwirq];
+	guard(mutex)(&alert->dispatch_lock);
+
+	scoped_guard(mutex, &alert->data_lock)
+		memcpy(alert->payload, payload, PMIC_GIO_ALERT_PAYLOAD_SIZE);
+
+	handle_nested_irq(virq);
+}
+EXPORT_SYMBOL_GPL(pmic_gio_handle_alert);
+
+MODULE_DESCRIPTION("Qualcomm PMIC GIO common driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/soc/qcom/pmic_gio.h b/include/linux/soc/qcom/pmic_gio.h
new file mode 100644
index 000000000000..6eaf71ef84c1
--- /dev/null
+++ b/include/linux/soc/qcom/pmic_gio.h
@@ -0,0 +1,154 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#ifndef __SOC_QCOM_PMIC_GIO_H__
+#define __SOC_QCOM_PMIC_GIO_H__
+
+#include <linux/auxiliary_bus.h>
+#include <linux/err.h>
+#include <linux/kconfig.h>
+#include <linux/notifier.h>
+#include <linux/types.h>
+
+/* Native access granularity of the GIO regmap: bytes per register. */
+#define PMIC_GIO_REG_GRANULARITY	4
+#define PMIC_GIO_ALERT_PAYLOAD_SIZE	16
+
+/* GIO peripherals id */
+#define GIO_CORE_PERIPHERAL_ID	0x00
+#define GIO_BATT_PERIPHERAL_ID	0x01
+#define GIO_USBC_PERIPHERAL_ID	0x02
+#define GIO_SVC_PERIPHERAL_ID	0x03
+#define GIO_OEM_PERIPHERAL_ID	0x0C
+
+/* GIO Alert IDs in each peripherals */
+#define GIO_ALERT_BATT_STATUS_CHANGE			0x80
+#define GIO_ALERT_BATT_INFO_CHANGE			0x81
+#define GIO_ALERT_BATT_MAINTENANCE_STATUS_CHANGE	0x82
+#define GIO_ALERT_BATT_POWER_STATE_CHANGE		0x83
+#define GIO_ALERT_BATT_POWER_SOURCE_INFO_CHANGE		0x90
+
+#define GIO_ALERT_USBC_UCSI_CCI				0x80
+#define GIO_ALERT_USBC_PAN				0x40
+
+#define GIO_ALERT_SVC_POWER_OK_CHANGED			0x01
+#define GIO_ALERT_SVC_SHUTDOWN				0x02
+#define GIO_ALERT_SVC_RESET				0x03
+
+#define GIO_ALERT_OEM_OP_NOTIFY				0x80
+
+struct device;
+struct regmap;
+
+struct pmic_gio_device;
+
+enum pmic_gio_state {
+	PMIC_GIO_STATE_DOWN,
+	PMIC_GIO_STATE_UP,
+};
+
+/*
+ * struct pmic_gio_device_ops - transport callbacks for a GIO device
+ * @read: read @val_size bytes starting at @reg
+ * @write: write @val_size bytes starting at @reg
+ * @handle_alert: optional hook letting the transport intercept an alert
+ *	before it is dispatched through the irqchip, with direct access to
+ *	the transport-defined alert @payload. Returns true if the alert was
+ *	fully handled and no further (irqchip) dispatch is wanted.
+ */
+struct pmic_gio_device_ops {
+	int (*read)(struct pmic_gio_device *gio, unsigned int reg, void *val, size_t val_size);
+	int (*write)(struct pmic_gio_device *gio, unsigned int reg, const void *val,
+		     size_t val_size);
+	bool (*handle_alert)(struct pmic_gio_device *gio, u8 peripheral_id, u8 alert_id,
+			     const void *payload);
+};
+
+struct pmic_gio_irq_chip_data;
+
+struct pmic_gio_device {
+	struct device *dev;
+	const struct pmic_gio_device_ops *ops;
+	void *drvdata;
+
+	struct regmap *regmap;
+	struct pmic_gio_irq_chip_data *chip_data;
+	struct atomic_notifier_head state_notifiers;
+	bool online;
+};
+
+struct pmic_gio_irq {
+	u8 peripheral_id;
+	u8 alert_id;
+};
+
+struct pmic_gio_irq_chip {
+	const struct pmic_gio_irq *irqs;
+	int num_irqs;
+};
+
+#if IS_REACHABLE(CONFIG_QCOM_PMIC_GIO)
+struct pmic_gio_device *devm_pmic_gio_register(struct device *dev,
+					       const struct pmic_gio_device_ops *ops,
+					       void *drvdata);
+int devm_pmic_gio_add_consumer(struct pmic_gio_device *gio, struct auxiliary_device *aux,
+			       const char *name);
+void pmic_gio_set_state(struct pmic_gio_device *gio, enum pmic_gio_state state);
+bool pmic_gio_is_online(struct device *dev);
+int devm_pmic_gio_register_notifier(struct device *dev, struct notifier_block *nb);
+int pmic_gio_irq_find(struct device *dev, u8 peripheral_id, u8 alert_id);
+void pmic_gio_handle_alert(struct pmic_gio_device *gio, u8 peripheral_id, u8 alert_id,
+			   const void *payload);
+int pmic_gio_irq_get_payload(unsigned int irq, void *payload,
+			     size_t payload_size);
+#else
+static inline struct pmic_gio_device *devm_pmic_gio_register(struct device *dev,
+							     const struct pmic_gio_device_ops *ops,
+							     void *drvdata)
+{
+	return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline int devm_pmic_gio_add_consumer(struct pmic_gio_device *gio,
+					     struct auxiliary_device *aux,
+					     const char *name)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pmic_gio_set_state(struct pmic_gio_device *gio,
+				      enum pmic_gio_state state)
+{
+}
+
+static inline bool pmic_gio_is_online(struct device *dev)
+{
+	return false;
+}
+
+static inline int devm_pmic_gio_register_notifier(struct device *dev,
+						  struct notifier_block *nb)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int pmic_gio_irq_find(struct device *dev, u8 peripheral_id,
+				    u8 alert_id)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int pmic_gio_irq_get_payload(unsigned int irq, void *payload,
+					   size_t payload_size)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void pmic_gio_handle_alert(struct pmic_gio_device *gio, u8 peripheral_id,
+					 u8 alert_id, const void *payload)
+{
+}
+#endif
+
+#endif
diff --git a/include/linux/soc/qcom/pmic_gio_regs.h b/include/linux/soc/qcom/pmic_gio_regs.h
new file mode 100644
index 000000000000..7081d5a5abda
--- /dev/null
+++ b/include/linux/soc/qcom/pmic_gio_regs.h
@@ -0,0 +1,239 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#ifndef __SOC_QCOM_PMIC_GIO_REGS_H__
+#define __SOC_QCOM_PMIC_GIO_REGS_H__
+
+/* Register block base addresses and sizes for the GIO PMIC peripherals. */
+/* CORE */
+#define GIO_CORE_IF_VERSION_ADDR		0x00000
+#define GIO_CORE_IF_VERSION_SIZE		4
+#define GIO_CORE_ALERT_STATUS_ADDR		0x00010
+#define GIO_CORE_ALERT_STATUS_SIZE		4
+#define GIO_CORE_LAST_ALERT_ADDR		0x00014
+#define GIO_CORE_LAST_ALERT_SIZE		20
+#define GIO_CORE_GLOBAL_ALERT_CTRL_ADDR		0x00100
+#define GIO_CORE_GLOBAL_ALERT_CTRL_SIZE		4
+#define GIO_CORE_HOST_STATE_ADDR		0x00180
+#define GIO_CORE_HOST_STATE_SIZE		4
+#define GIO_CORE_SYSTEM_STATE_ADDR		0x00184
+#define GIO_CORE_SYSTEM_STATE_SIZE		4
+#define GIO_CORE_LAST_ERROR_ADDR		0x00200
+#define GIO_CORE_LAST_ERROR_SIZE		4
+
+/* BATT */
+#define GIO_BATT_IF_VERSION_ADDR		0x10000
+#define GIO_BATT_IF_VERSION_SIZE		4
+#define GIO_BATT_ALERT_STATUS_ADDR		0x10010
+#define GIO_BATT_ALERT_STATUS_SIZE		4
+#define GIO_BATT_LAST_ALERT_ADDR		0x10014
+#define GIO_BATT_LAST_ALERT_SIZE		20
+#define GIO_BATT_BPRT_ADDR			0x10100
+#define GIO_BATT_BPRT_SIZE			4
+#define GIO_BATT_BCTI_ADDR			0x10104
+#define GIO_BATT_BCTI_SIZE			4
+#define GIO_BATT_BCT_ADDR			0x10108
+#define GIO_BATT_BCT_SIZE			4
+#define GIO_BATT_BIX_ADDR			0x10110
+#define GIO_BATT_BIX_SIZE			68
+#define GIO_BATT_BPC_ADDR			0x10160
+#define GIO_BATT_BPC_SIZE			16
+#define GIO_BATT_BPS_ADDR			0x10178
+#define GIO_BATT_BPS_SIZE			20
+#define GIO_BATT_BPT_ADDR			0x10190
+#define GIO_BATT_BPT_SIZE			12
+#define GIO_BATT_BPTS_ADDR			0x1019c
+#define GIO_BATT_BPTS_SIZE			4
+#define GIO_BATT_BST_ADDR			0x101a8
+#define GIO_BATT_BST_SIZE			16
+#define GIO_BATT_BATTERY_PRESENT_OCV_ADDR	0x101b8
+#define GIO_BATT_BATTERY_PRESENT_OCV_SIZE	4
+#define GIO_BATT_BTH_ADDR			0x101c0
+#define GIO_BATT_BTH_SIZE			4
+#define GIO_BATT_BTMI_ADDR			0x101c8
+#define GIO_BATT_BTMI_SIZE			4
+#define GIO_BATT_BTM_ADDR			0x101cc
+#define GIO_BATT_BTM_SIZE			4
+#define GIO_BATT_BTP_ADDR			0x101d0
+#define GIO_BATT_BTP_SIZE			4
+#define GIO_BATT_PSR_ADDR			0x101d8
+#define GIO_BATT_PSR_SIZE			4
+#define GIO_BATT_PIF_ADDR			0x101dc
+#define GIO_BATT_PIF_SIZE			12
+#define GIO_BATT_SHIP_MODE_EN_ADDR		0x101e8
+#define GIO_BATT_SHIP_MODE_EN_SIZE		4
+#define GIO_BATT_PCS_ADDR			0x101ec
+#define GIO_BATT_PCS_SIZE			12
+#define GIO_BATT_MNO_ADDR			0x10200
+#define GIO_BATT_MNO_SIZE			128
+#define GIO_BATT_SNO_ADDR			0x10280
+#define GIO_BATT_SNO_SIZE			128
+#define GIO_BATT_OTY_ADDR			0x10300
+#define GIO_BATT_OTY_SIZE			128
+#define GIO_BATT_OIF_ADDR			0x10380
+#define GIO_BATT_OIF_SIZE			128
+#define GIO_BATT_PORTABLE_BATTERY_INFO_ADDR	0x10400
+#define GIO_BATT_PORTABLE_BATTERY_INFO_SIZE	128
+#define GIO_BATT_BMD_ADDR			0x10480
+#define GIO_BATT_BMD_SIZE			20
+#define GIO_BATT_BMC_ADDR			0x10494
+#define GIO_BATT_BMC_SIZE			4
+#define GIO_BATT_MISC_CONFIG_ADDR		0x104a0
+#define GIO_BATT_MISC_CONFIG_SIZE		4
+
+/* USBC */
+#define GIO_USBC_IF_VERSION_ADDR		0x20000
+#define GIO_USBC_IF_VERSION_SIZE		4
+#define GIO_USBC_ALERT_STATUS_ADDR		0x20010
+#define GIO_USBC_ALERT_STATUS_SIZE		4
+#define GIO_USBC_LAST_ALERT_ADDR		0x20014
+#define GIO_USBC_LAST_ALERT_SIZE		20
+#define GIO_USBC_UCSI_VERSION_REG		0x20100
+#define GIO_USBC_UCSI_VERSION_SIZE		4
+#define GIO_USBC_UCSI_CCI_REG			0x20104
+#define GIO_USBC_UCSI_CCI_SIZE			4
+#define GIO_USBC_UCSI_CONTROL_REG		0x20108
+#define GIO_USBC_UCSI_CONTROL_SIZE		8
+#define GIO_USBC_UCSI_MESSAGE_IN_REG		0x20200
+#define GIO_USBC_UCSI_MESSAGE_IN_SIZE		64
+#define GIO_USBC_UCSI_MSGINX_REG		0x20240
+#define GIO_USBC_UCSI_MSGINX_SIZE		192
+#define GIO_USBC_UCSI_MESSAGE_OUT_REG		0x20300
+#define GIO_USBC_UCSI_MESSAGE_OUT_SIZE		64
+#define GIO_USBC_UCSI_MSGOUTX_ADDR		0x20340
+#define GIO_USBC_UCSI_MSGOUTX_SIZE		192
+#define GIO_USBC_UCSI_PAN_ADDR			0x20400
+#define GIO_USBC_UCSI_PAN_SIZE			64
+#define GIO_USBC_PAN_CMD_ADDR			0x20440
+#define GIO_USBC_PAN_CMD_SIZE			0x08
+#define GIO_USBC_RTMR_INFO_ADDR			0x20600
+#define GIO_USBC_RTMR_INFO_SIZE			64
+#define GIO_USBC_RTMR_FW_UPDATE_ADDR		0x20640
+#define GIO_USBC_RTMR_FW_UPDATE_SIZE		4
+#define GIO_USBC_USBCFG_ADDR			0x20700
+#define GIO_USBC_USBCFG_SIZE			4
+#define GIO_USBC_DAM_CTRL_ADDR			0x20708
+#define GIO_USBC_DAM_CTRL_SIZE			4
+#define GIO_USBC_DAM_CTRL_CFG_ADDR		0x2070c
+#define GIO_USBC_DAM_CTRL_CFG_SIZE		4
+
+/* SVC */
+#define GIO_SVC_IF_VERSION_ADDR			0x30000
+#define GIO_SVC_IF_VERSION_SIZE			4
+#define GIO_SVC_ALERT_STATUS_ADDR		0x30010
+#define GIO_SVC_ALERT_STATUS_SIZE		4
+#define GIO_SVC_LAST_ALERT_ADDR			0x30014
+#define GIO_SVC_LAST_ALERT_SIZE			20
+#define GIO_SVC_UEFI_ACTION_ADDR		0x30100
+#define GIO_SVC_UEFI_ACTION_SIZE		4
+#define GIO_SVC_POWER_OK_ADDR			0x30104
+#define GIO_SVC_POWER_OK_SIZE			4
+#define GIO_SVC_OOB_REQUEST_ACTION_ADDR		0x30110
+#define GIO_SVC_OOB_REQUEST_ACTION_SIZE		4
+#define GIO_SVC_OOB_RTCTMR_VOTE_ADDR		0x30114
+#define GIO_SVC_OOB_RTCTMR_VOTE_SIZE		8
+#define GIO_SVC_CLT0_REQUEST_ACTION_ADDR	0x30120
+#define GIO_SVC_CLT0_REQUEST_ACTION_SIZE	4
+#define GIO_SVC_CLT0_RTCTMR_VOTE_ADDR		0x30124
+#define GIO_SVC_CLT0_RTCTMR_VOTE_SIZE		8
+#define GIO_SVC_SYS_SET_POWER_STATE_ADDR	0x30150
+#define GIO_SVC_SYS_SET_POWER_STATE_SIZE	4
+#define GIO_SVC_TAD_RT_ADDR			0x30200
+#define GIO_SVC_TAD_RT_SIZE			16
+#define GIO_SVC_TAD_GWS_ADDR			0x30210
+#define GIO_SVC_TAD_GWS_SIZE			8
+#define GIO_SVC_TAD_CWS_ADDR			0x30218
+#define GIO_SVC_TAD_CWS_SIZE			4
+#define GIO_SVC_TAD_TPA_ADDR			0x3021c
+#define GIO_SVC_TAD_TPA_SIZE			4
+#define GIO_SVC_TAD_TPD_ADDR			0x30220
+#define GIO_SVC_TAD_TPD_SIZE			4
+#define GIO_SVC_TAD_TVA_ADDR			0x30224
+#define GIO_SVC_TAD_TVA_SIZE			4
+#define GIO_SVC_TAD_TVD_ADDR			0x30228
+#define GIO_SVC_TAD_TVD_SIZE			4
+#define GIO_SVC_LID_SETTING_ADDR		0x3022c
+#define GIO_SVC_LID_SETTING_SIZE		4
+
+/* SETTING */
+#define GIO_SETTING_IF_VERSION_ADDR		0x40000
+#define GIO_SETTING_IF_VERSION_SIZE		4
+#define GIO_SETTING_ALERT_STATUS_ADDR		0x40010
+#define GIO_SETTING_ALERT_STATUS_SIZE		4
+#define GIO_SETTING_LAST_ALERT_ADDR		0x40014
+#define GIO_SETTING_LAST_ALERT_SIZE		20
+#define GIO_SETTING_SYS_PWR_CFG_ADDR		0x40100
+#define GIO_SETTING_SYS_PWR_CFG_SIZE		4
+#define GIO_SETTING_BATT_AUTH_ADDR		0x40104
+#define GIO_SETTING_BATT_AUTH_SIZE		4
+#define GIO_SETTING_CHG_LIMIT_ADDR		0x40108
+#define GIO_SETTING_CHG_LIMIT_SIZE		4
+#define GIO_SETTING_BOOT_ADDR			0x4010c
+#define GIO_SETTING_BOOT_SIZE			4
+#define GIO_SETTING_USBC_ADDR			0x40110
+#define GIO_SETTING_USBC_SIZE			4
+#define GIO_SETTING_HOST_MAC_ADDR_ADDR		0x40114
+#define GIO_SETTING_HOST_MAC_ADDR_SIZE		8
+#define GIO_SETTING_AOU_ADDR			0x4011c
+#define GIO_SETTING_AOU_SIZE			4
+#define GIO_SETTING_PSM_ADDR			0x40120
+#define GIO_SETTING_PSM_SIZE			4
+#define GIO_SETTING_USB_WAKE_ON_LAN_ADDR	0x40124
+#define GIO_SETTING_USB_WAKE_ON_LAN_SIZE	4
+#define GIO_SETTING_EPM_USBC_ADDR		0x40128
+#define GIO_SETTING_EPM_USBC_SIZE		4
+#define GIO_SETTING_OFFMODE_SHIPMODE_ADDR	0x4012c
+#define GIO_SETTING_OFFMODE_SHIPMODE_SIZE	4
+#define GIO_SETTING_RTC_TIMER_FOR_EOC_ADDR	0x40130
+#define GIO_SETTING_RTC_TIMER_FOR_EOC_SIZE	4
+#define GIO_SETTING_INTL_CHG_ADDR		0x40134
+#define GIO_SETTING_INTL_CHG_SIZE		32
+#define GIO_SETTING_CMD_ADDR			0x40800
+#define GIO_SETTING_CMD_SIZE			4
+
+/* DBG */
+#define GIO_DBG_IF_VERSION_ADDR			0x70000
+#define GIO_DBG_IF_VERSION_SIZE			4
+#define GIO_DBG_ALERT_STATUS_ADDR		0x70010
+#define GIO_DBG_ALERT_STATUS_SIZE		4
+#define GIO_DBG_LAST_ALERT_ADDR			0x70014
+#define GIO_DBG_LAST_ALERT_SIZE			20
+#define GIO_DBG_BCLIBAT_ADDR			0x70100
+#define GIO_DBG_BCLIBAT_SIZE			8
+#define GIO_DBG_BCLVBAT_ADDR			0x70110
+#define GIO_DBG_BCLVBAT_SIZE			12
+#define GIO_DBG_BCLIBAT_DGL_TIMER_ADDR		0x70120
+#define GIO_DBG_BCLIBAT_DGL_TIMER_SIZE		12
+#define GIO_DBG_BCLVBAT_DGL_TIMER_ADDR		0x70130
+#define GIO_DBG_BCLVBAT_DGL_TIMER_SIZE		12
+#define GIO_DBG_BCL_CLR_TIMER_ADDR		0x70140
+#define GIO_DBG_BCL_CLR_TIMER_SIZE		12
+#define GIO_DBG_CHG_DIE_TEMP_TH_ADDR		0x70150
+#define GIO_DBG_CHG_DIE_TEMP_TH_SIZE		4
+#define GIO_DBG_CHG_CONN_TEMP_TH_ADDR		0x70154
+#define GIO_DBG_CHG_CONN_TEMP_TH_SIZE		4
+#define GIO_DBG_SPMI_WRITE_ADDR			0x70160
+#define GIO_DBG_SPMI_WRITE_SIZE			8
+#define GIO_DBG_LOG_LEVEL_ADDR			0x70170
+#define GIO_DBG_LOG_LEVEL_SIZE			4
+#define GIO_DBG_LOG_CAT_ADDR			0x70174
+#define GIO_DBG_LOG_CAT_SIZE			8
+
+/* OEM */
+#define GIO_OEM_IF_VERSION_ADDR			0xc0000
+#define GIO_OEM_IF_VERSION_SIZE			4
+#define GIO_OEM_ALERT_STATUS_ADDR		0xc0010
+#define GIO_OEM_ALERT_STATUS_SIZE		4
+#define GIO_OEM_LAST_ALERT_ADDR			0xc0014
+#define GIO_OEM_LAST_ALERT_SIZE			20
+#define GIO_OEM_OP_RW_ADDR			0xc0100
+#define GIO_OEM_OP_RW_SIZE			64
+#define GIO_OEM_I2C_PROXY_CONFIG_ADDR		0xc0200
+#define GIO_OEM_I2C_PROXY_CONFIG_SIZE		4
+#define GIO_OEM_I2C_PROXY_DATA_ADDR		0xc0204
+#define GIO_OEM_I2C_PROXY_DATA_SIZE		72
+
+#define GIO_MAX_REG_ADDR			0xcffff
+
+#endif

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance
  2026-11-11  3:28 [PATCH 0/3] soc: qcom: Add Qualcomm PMIC GIO framework and RTC driver using it Fenglin Wu
  2026-11-11  3:28 ` [PATCH 1/3] soc: qcom: Add pmic-gio GIO framework driver Fenglin Wu
@ 2026-11-11  3:28 ` Fenglin Wu
  2026-11-11 13:14   ` Jeff Roach
  2026-11-11  3:28 ` [PATCH 3/3] rtc: Add Qualcomm GIO RTC driver Fenglin Wu
  2 siblings, 1 reply; 5+ messages in thread
From: Fenglin Wu @ 2026-11-11  3:28 UTC (permalink / raw)
  To: linux-arm-msm, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
	Alexandre Belloni
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa,
	linux-kernel, linux-rtc, Fenglin Wu, Tingguo Cheng

Add a PMIC GLINK transport instance for GIO (General I/O) which is a
new communication model used by the SoCCP battery-management firmware
on some of the Qualcomm platforms (e.g. Glymur): it turns the
GIO_OPCODE_BLOCK_READ/_WRITE requests and the GIO_OPCODE_ALERT_IND
notification exchanged over the pmic-glink rpmsg channel into a regmap
and per-alert IRQ delivery. It registers as a "pmic_glink.gio"
auxiliary driver and bound to a new "gio" auxiliary device that's created
on GIO-capable platforms.

Also, update the client_mask to additionally request the GIO client on
Glymur, which is the 1st platform to support GIO. Even through the legacy
clients (battmgr, ucsi, altmode) can also be supported using the new GIO
interface, their per-client bits are still kept with the functionalities
continue to be provided by the existing pmic_glink.* auxiliary devices
using the pmic_glink interfaces, to minimize the risks associated with
transitioning all of them to use the new GIO interface.

Co-developed-by: Tingguo Cheng <tingguo.cheng@oss.qualcomm.com>
Signed-off-by: Tingguo Cheng <tingguo.cheng@oss.qualcomm.com>
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 drivers/soc/qcom/Kconfig            |  13 +
 drivers/soc/qcom/Makefile           |   1 +
 drivers/soc/qcom/pmic_glink.c       |  26 +-
 drivers/soc/qcom/pmic_glink_gio.c   | 535 ++++++++++++++++++++++++++++++++++++
 include/linux/soc/qcom/pmic_glink.h |   1 +
 5 files changed, 574 insertions(+), 2 deletions(-)

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 266a1babc108..64f21fe6911e 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -137,6 +137,19 @@ config QCOM_PMIC_GIO
 	  Say yes here to enable the GIO interface on a SoCCP based battery
 	  management firmware platform.
 
+config QCOM_PMIC_GLINK_GIO
+	tristate "Qualcomm PMIC GLINK GIO driver"
+	depends on QCOM_PMIC_GLINK && QCOM_PMIC_GIO
+	default QCOM_PMIC_GLINK && QCOM_PMIC_GIO
+	help
+	  The Qualcomm PMIC GLINK GIO driver provides access, over GLINK, to
+	  the General I/O (GIO) register space and alerts exposed by the
+	  SoCCP coprocessor on some modern Qualcomm platforms.
+
+	  Say yes here to support USB-C and battery functions through GIO
+	  interface with the PMIC Glink transport exposed by SoCCP coprocessor
+	  on a Qualcomm platform.
+
 config QCOM_RAMP_CTRL
 	tristate "Qualcomm Ramp Controller driver"
 	help
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index c3170757b012..5a0167e8fa55 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_QCOM_PDR_MSG)	+= qcom_pdr_msg.o
 obj-$(CONFIG_QCOM_PMIC_GIO)	+= pmic_gio.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink.o
 obj-$(CONFIG_QCOM_PMIC_GLINK)	+= pmic_glink_altmode.o
+obj-$(CONFIG_QCOM_PMIC_GLINK_GIO)	+= pmic_glink_gio.o
 obj-$(CONFIG_QCOM_PMIC_PDCHARGER_ULOG)	+= pmic_pdcharger_ulog.o
 CFLAGS_pmic_pdcharger_ulog.o	:=  -I$(src)
 obj-$(CONFIG_QCOM_QMI_TMD) += qmi_tmd.o
diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c
index 84fd0c82c0c4..e8e804a9ab0e 100644
--- a/drivers/soc/qcom/pmic_glink.c
+++ b/drivers/soc/qcom/pmic_glink.c
@@ -2,8 +2,11 @@
 /*
  * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved.
  * Copyright (c) 2022, Linaro Ltd
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
  */
+
 #include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
 #include <linux/cleanup.h>
 #include <linux/delay.h>
 #include <linux/module.h>
@@ -21,6 +24,7 @@ enum {
 	PMIC_GLINK_CLIENT_BATT = 0,
 	PMIC_GLINK_CLIENT_ALTMODE,
 	PMIC_GLINK_CLIENT_UCSI,
+	PMIC_GLINK_CLIENT_GIO,
 };
 
 struct pmic_glink_data {
@@ -40,6 +44,7 @@ struct pmic_glink {
 	struct auxiliary_device altmode_aux;
 	struct auxiliary_device ps_aux;
 	struct auxiliary_device ucsi_aux;
+	struct auxiliary_device gio_aux;
 
 	/* serializing client_state and pdr_state updates */
 	struct mutex state_lock;
@@ -326,10 +331,15 @@ static int pmic_glink_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_GIO)) {
+		ret = pmic_glink_add_aux_device(pg, &pg->gio_aux, "gio");
+		if (ret)
+			goto out_release_pdr_handle;
+	}
 	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI)) {
 		ret = pmic_glink_add_aux_device(pg, &pg->ucsi_aux, "ucsi");
 		if (ret)
-			goto out_release_pdr_handle;
+			goto out_release_gio_aux;
 	}
 	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_ALTMODE)) {
 		ret = pmic_glink_add_aux_device(pg, &pg->altmode_aux, "altmode");
@@ -367,6 +377,9 @@ static int pmic_glink_probe(struct platform_device *pdev)
 out_release_ucsi_aux:
 	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI))
 		pmic_glink_del_aux_device(pg, &pg->ucsi_aux);
+out_release_gio_aux:
+	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_GIO))
+		pmic_glink_del_aux_device(pg, &pg->gio_aux);
 out_release_pdr_handle:
 	pdr_handle_release(pg->pdr);
 
@@ -385,6 +398,8 @@ static void pmic_glink_remove(struct platform_device *pdev)
 		pmic_glink_del_aux_device(pg, &pg->altmode_aux);
 	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI))
 		pmic_glink_del_aux_device(pg, &pg->ucsi_aux);
+	if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_GIO))
+		pmic_glink_del_aux_device(pg, &pg->gio_aux);
 
 	guard(mutex)(&__pmic_glink_lock);
 	__pmic_glink = NULL;
@@ -404,8 +419,15 @@ static const struct pmic_glink_data pmic_glink_soccp_data = {
 		       BIT(PMIC_GLINK_CLIENT_UCSI),
 };
 
+static const struct pmic_glink_data pmic_glink_glymur_data = {
+	.client_mask = BIT(PMIC_GLINK_CLIENT_GIO) |
+		       BIT(PMIC_GLINK_CLIENT_BATT) |
+		       BIT(PMIC_GLINK_CLIENT_ALTMODE) |
+		       BIT(PMIC_GLINK_CLIENT_UCSI),
+};
+
 static const struct of_device_id pmic_glink_of_match[] = {
-	{ .compatible = "qcom,glymur-pmic-glink", .data = &pmic_glink_soccp_data },
+	{ .compatible = "qcom,glymur-pmic-glink", .data = &pmic_glink_glymur_data },
 	{ .compatible = "qcom,kaanapali-pmic-glink", .data = &pmic_glink_soccp_data },
 	{ .compatible = "qcom,pmic-glink", .data = &pmic_glink_adsp_data },
 	{}
diff --git a/drivers/soc/qcom/pmic_glink_gio.c b/drivers/soc/qcom/pmic_glink_gio.c
new file mode 100644
index 000000000000..f222c0d59220
--- /dev/null
+++ b/drivers/soc/qcom/pmic_glink_gio.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/atomic.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/pdr.h>
+#include <linux/soc/qcom/pmic_glink.h>
+#include <linux/soc/qcom/pmic_gio.h>
+#include <linux/soc/qcom/pmic_gio_regs.h>
+#include <linux/spinlock.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+
+#define PMIC_GLINK_SEND_TIMEOUT		(5 * HZ)
+
+#define GIO_CORE_GLOBAL_ALERT_MODE_IBI	0x00
+#define GIO_CORE_GLOBAL_ALERT_EN_BIT	BIT(0)
+
+/* GIO (Generic I/O) pmic_glink transaction opcode and mask */
+#define GIO_OPCODE_BLOCK_READ		0x90
+#define GIO_OPCODE_BLOCK_WRITE		0x91
+#define GIO_OPCODE_ALERT_IND		0x92
+#define GIO_OPCODE_MASK			GENMASK(15, 0)
+
+#define GIO_RW_BUFFER_SIZE		1024
+#define GIO_ALERT_MAX_PENDING		32
+
+struct gio_alert_hdr {
+	u8	revision;
+	u8	reserved_1;
+	__le16	serial_no;
+} __packed;
+
+struct gio_alert_info {
+	u8	peripheral_id;
+	u8	reserved_1;
+	u8	alert_id;
+	u8	reserved_2;
+} __packed;
+
+struct gio_alert_data {
+	struct gio_alert_hdr	hdr;
+	struct gio_alert_info	info;
+	u8			payload[PMIC_GIO_ALERT_PAYLOAD_SIZE];
+} __packed;
+
+struct gio_alert_msg {
+	struct pmic_glink_hdr	hdr;
+	__le32			reserved;
+	struct gio_alert_data	alt_data;
+} __packed;
+
+struct gio_read_req {
+	struct pmic_glink_hdr	hdr;
+	__le32			reserved;
+	__le32			offset;
+	__le32			length;
+} __packed;
+
+struct gio_read_resp {
+	struct pmic_glink_hdr	hdr;
+	u8			data[GIO_RW_BUFFER_SIZE];
+	__le32			ret_code;
+} __packed;
+
+struct gio_write_req {
+	struct pmic_glink_hdr	hdr;
+	__le32			reserved;
+	__le32			offset;
+	__le32			length;
+	u8			data[GIO_RW_BUFFER_SIZE];
+} __packed;
+
+struct gio_write_resp {
+	struct pmic_glink_hdr	hdr;
+	__le32			ret_code;
+} __packed;
+
+struct gio_alert_work {
+	struct work_struct	work;
+	struct pmic_glink_gio	*pgio;
+	u8			peripheral_id;
+	u8			alert_id;
+	u8			payload[PMIC_GIO_ALERT_PAYLOAD_SIZE];
+};
+
+struct pmic_glink_gio {
+	struct device *dev;
+
+	struct pmic_glink_client *client;
+	struct pmic_gio_device *gio;
+
+	struct workqueue_struct *wq;
+	struct work_struct init_work;
+	struct completion ack;
+	atomic_t alert_pending;
+
+	spinlock_t lock;	/* protects the transaction state and flags */
+	bool pending;
+	u16 pending_opcode;
+	int ack_error;
+	unsigned int buf_size;
+	u8 *buf;
+	bool removing;
+	bool service_up;
+
+	struct gio_write_req write_req;
+};
+
+/* Must be called with pgio->lock held. */
+static void pmic_glink_gio_reset_transaction(struct pmic_glink_gio *pgio)
+{
+	pgio->pending = false;
+	pgio->pending_opcode = 0;
+	pgio->ack_error = 0;
+	pgio->buf = NULL;
+	pgio->buf_size = 0;
+}
+
+static int pmic_glink_gio_wait_ack(struct pmic_glink_gio *pgio)
+{
+	unsigned long left;
+	int ret;
+
+	left = wait_for_completion_timeout(&pgio->ack, PMIC_GLINK_SEND_TIMEOUT);
+
+	scoped_guard(spinlock_irqsave, &pgio->lock) {
+		ret = pgio->ack_error;
+		pmic_glink_gio_reset_transaction(pgio);
+	}
+
+	/*
+	 * If a BLOCK_READ/BLOCK_WRITE request times out, its response may still
+	 * arrive later and be mistaken for a newer request with the same opcode:
+	 * neither gio_read_resp nor gio_write_resp carries any offset/length/
+	 * sequence identifier, so a late response cannot be distinguished from
+	 * the response to whichever transaction is pending when it arrives.
+	 * Fixing this would need a per-transaction generation counter/sequence
+	 * number threaded through the request and checked against the response
+	 * before trusting it. However, this has not been implemented at the
+	 * firmware side, so just print an error message and abort the current
+	 * request instead.
+	 */
+	if (!left) {
+		dev_err(pgio->dev,
+			"GIO transaction timed out, response may still be in flight\n");
+		return -ETIMEDOUT;
+	}
+
+	return ret;
+}
+
+static int pmic_glink_gio_write_sync(struct pmic_glink_gio *pgio, u32 addr,
+				     const u8 *data, size_t length)
+{
+	struct gio_write_req *req = &pgio->write_req;
+	int ret;
+
+	if (!data || !length)
+		return -EFAULT;
+
+	if (length > GIO_RW_BUFFER_SIZE) {
+		dev_err(pgio->dev, "Write length %zu exceeds maximum buffer size %d\n",
+			length, GIO_RW_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	reinit_completion(&pgio->ack);
+
+	req->hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_GIO);
+	req->hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP);
+	req->hdr.opcode = cpu_to_le32(GIO_OPCODE_BLOCK_WRITE);
+	req->offset = cpu_to_le32(addr);
+	req->length = cpu_to_le32(length);
+	memcpy(req->data, data, length);
+
+	scoped_guard(spinlock_irqsave, &pgio->lock) {
+		pgio->pending = true;
+		pgio->pending_opcode = GIO_OPCODE_BLOCK_WRITE;
+	}
+
+	ret = pmic_glink_send(pgio->client, req, offsetof(struct gio_write_req, data) + length);
+	if (ret) {
+		dev_err(pgio->dev, "Failed to send GIO write request to 0x%05x: %d\n", addr, ret);
+
+		scoped_guard(spinlock_irqsave, &pgio->lock)
+			pmic_glink_gio_reset_transaction(pgio);
+
+		return ret;
+	}
+
+	ret = pmic_glink_gio_wait_ack(pgio);
+	if (ret)
+		dev_err(pgio->dev, "GIO write to 0x%05x failed: %d\n", addr, ret);
+
+	return ret;
+}
+
+static int pmic_glink_gio_read(struct pmic_gio_device *gio, unsigned int reg,
+			       void *val, size_t val_size)
+{
+	struct pmic_glink_gio *pgio = gio->drvdata;
+	struct gio_read_req req = {};
+	int ret;
+
+	if (!val || !val_size)
+		return -EFAULT;
+
+	if (val_size > GIO_RW_BUFFER_SIZE) {
+		dev_err(pgio->dev, "Read length %zu exceeds maximum buffer size %d\n",
+			val_size, GIO_RW_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	reinit_completion(&pgio->ack);
+
+	req.hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_GIO);
+	req.hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP);
+	req.hdr.opcode = cpu_to_le32(GIO_OPCODE_BLOCK_READ);
+	req.offset = cpu_to_le32(reg);
+	req.length = cpu_to_le32(val_size);
+
+	scoped_guard(spinlock_irqsave, &pgio->lock) {
+		pgio->pending = true;
+		pgio->pending_opcode = GIO_OPCODE_BLOCK_READ;
+		pgio->buf_size = val_size;
+		pgio->buf = val;
+	}
+
+	ret = pmic_glink_send(pgio->client, &req, sizeof(req));
+	if (ret) {
+		dev_err(pgio->dev, "Failed to send GIO read request to 0x%05x: %d\n", reg, ret);
+
+		scoped_guard(spinlock_irqsave, &pgio->lock) {
+			pmic_glink_gio_reset_transaction(pgio);
+		}
+
+		return ret;
+	}
+
+	ret = pmic_glink_gio_wait_ack(pgio);
+	if (ret)
+		dev_err(pgio->dev, "GIO read from 0x%05x failed: %d\n", reg, ret);
+
+	return ret;
+}
+
+static int pmic_glink_gio_write(struct pmic_gio_device *gio, unsigned int reg,
+				const void *val, size_t val_size)
+{
+	struct pmic_glink_gio *pgio = gio->drvdata;
+
+	return pmic_glink_gio_write_sync(pgio, reg, val, val_size);
+}
+
+static bool pmic_glink_gio_handle_alert(struct pmic_gio_device *gio,
+					u8 peripheral_id, u8 alert_id,
+					const void *payload)
+{
+	dev_dbg(gio->dev,
+		"GIO alert: peripheral_id=0x%02x alert_id=0x%02x payload=%*ph\n",
+		peripheral_id, alert_id, PMIC_GIO_ALERT_PAYLOAD_SIZE, payload);
+
+	return false;
+}
+
+static const struct pmic_gio_device_ops pmic_glink_gio_ops = {
+	.read = pmic_glink_gio_read,
+	.write = pmic_glink_gio_write,
+	.handle_alert = pmic_glink_gio_handle_alert,
+};
+
+static void pmic_glink_gio_pdr_notify(void *priv, int state)
+{
+	struct pmic_glink_gio *pgio = priv;
+	bool service_up;
+
+	service_up = state == SERVREG_SERVICE_STATE_UP;
+
+	guard(spinlock_irqsave)(&pgio->lock);
+	if (pgio->removing)
+		return;
+
+	WRITE_ONCE(pgio->service_up, service_up);
+	if (!service_up && pgio->pending) {
+		pgio->pending = false;
+		pgio->buf = NULL;
+		pgio->buf_size = 0;
+		pgio->ack_error = -ENETRESET;
+		complete(&pgio->ack);
+	}
+
+	pmic_gio_set_state(pgio->gio, service_up ? PMIC_GIO_STATE_UP : PMIC_GIO_STATE_DOWN);
+	if (service_up)
+		queue_work(pgio->wq, &pgio->init_work);
+}
+
+static void pmic_glink_gio_enable_alerts(struct pmic_glink_gio *pgio)
+{
+	__le32 data;
+	int ret;
+
+	data = cpu_to_le32(GIO_CORE_GLOBAL_ALERT_MODE_IBI | GIO_CORE_GLOBAL_ALERT_EN_BIT);
+
+	ret = regmap_raw_write(pgio->gio->regmap, GIO_CORE_GLOBAL_ALERT_CTRL_ADDR, &data, 4);
+	if (ret)
+		dev_err(pgio->dev, "GIO turn on global alert failed: %d\n", ret);
+}
+
+static void pmic_glink_gio_init_work(struct work_struct *work)
+{
+	struct pmic_glink_gio *pgio = container_of(work, struct pmic_glink_gio, init_work);
+	int ret;
+
+	if (!READ_ONCE(pgio->service_up))
+		return;
+
+	pmic_glink_gio_enable_alerts(pgio);
+}
+
+static void pmic_glink_gio_alert_work(struct work_struct *work)
+{
+	struct gio_alert_work *alert_work = container_of(work, struct gio_alert_work, work);
+
+	pmic_gio_handle_alert(alert_work->pgio->gio, alert_work->peripheral_id,
+			      alert_work->alert_id, alert_work->payload);
+	atomic_dec(&alert_work->pgio->alert_pending);
+	kfree(alert_work);
+}
+
+static void pmic_glink_gio_callback(const void *data, size_t len, void *priv)
+{
+	const struct pmic_glink_hdr *hdr = data;
+	const struct gio_alert_msg *alert_msg;
+	const struct gio_write_resp *tx_resp;
+	const struct gio_read_resp *rx_resp;
+	struct pmic_glink_gio *pgio = priv;
+	size_t copy_size;
+	u16 opcode;
+
+	if (!data || len < sizeof(*hdr)) {
+		dev_warn(pgio->dev, "Invalid packet received (len: %zu)\n", len);
+		return;
+	}
+
+	opcode = FIELD_GET(GIO_OPCODE_MASK, le32_to_cpu(hdr->opcode));
+
+	switch (opcode) {
+	case GIO_OPCODE_BLOCK_READ:
+		if (len != sizeof(struct gio_read_resp)) {
+			dev_warn(pgio->dev, "Invalid read response message, length: %zu\n", len);
+			return;
+		}
+
+		rx_resp = data;
+
+		scoped_guard(spinlock_irqsave, &pgio->lock) {
+			if (!pgio->pending || pgio->pending_opcode != GIO_OPCODE_BLOCK_READ) {
+				dev_warn(pgio->dev, "Unexpected GIO read response, dropping\n");
+				return;
+			}
+
+			if (le32_to_cpu(rx_resp->ret_code) != 0) {
+				pgio->ack_error = -EREMOTEIO;
+				dev_warn(pgio->dev, "GIO read response failed (%u)\n",
+					 le32_to_cpu(rx_resp->ret_code));
+			} else if (pgio->buf) {
+				copy_size = min_t(size_t, pgio->buf_size, GIO_RW_BUFFER_SIZE);
+				memcpy(pgio->buf, rx_resp->data, copy_size);
+			}
+			complete(&pgio->ack);
+		}
+		break;
+
+	case GIO_OPCODE_BLOCK_WRITE:
+		if (len != sizeof(struct gio_write_resp)) {
+			dev_warn(pgio->dev, "Invalid write response message, length: %zu\n", len);
+			return;
+		}
+
+		tx_resp = data;
+
+		scoped_guard(spinlock_irqsave, &pgio->lock) {
+			if (!pgio->pending || pgio->pending_opcode != GIO_OPCODE_BLOCK_WRITE) {
+				dev_warn(pgio->dev, "Unexpected GIO write response, dropping\n");
+				return;
+			}
+
+			if (le32_to_cpu(tx_resp->ret_code) != 0) {
+				pgio->ack_error = -EREMOTEIO;
+				dev_warn(pgio->dev, "GIO write response failed (%u)\n",
+					 le32_to_cpu(tx_resp->ret_code));
+			}
+			complete(&pgio->ack);
+		}
+		break;
+
+	case GIO_OPCODE_ALERT_IND:
+		if (len != sizeof(struct gio_alert_msg)) {
+			dev_warn(pgio->dev, "Invalid alert message, length: %zu\n", len);
+			return;
+		}
+
+		if (atomic_read(&pgio->alert_pending) >= GIO_ALERT_MAX_PENDING) {
+			dev_warn_ratelimited(pgio->dev,
+					     "Too many pending GIO alerts, dropping\n");
+			return;
+		}
+
+		struct gio_alert_work *alert_work __free(kfree) =
+				kmalloc_obj(*alert_work, GFP_ATOMIC);
+		if (!alert_work)
+			return;
+
+		alert_msg = data;
+
+		INIT_WORK(&alert_work->work, pmic_glink_gio_alert_work);
+		alert_work->pgio = pgio;
+		alert_work->peripheral_id = alert_msg->alt_data.info.peripheral_id;
+		alert_work->alert_id = alert_msg->alt_data.info.alert_id;
+		memcpy(alert_work->payload,
+		       alert_msg->alt_data.payload,
+		       PMIC_GIO_ALERT_PAYLOAD_SIZE);
+
+		scoped_guard(spinlock_irqsave, &pgio->lock) {
+			if (pgio->removing || !pgio->service_up)
+				return;
+
+			atomic_inc(&pgio->alert_pending);
+			if (queue_work(pgio->wq, &alert_work->work))
+				retain_and_null_ptr(alert_work);
+			else
+				atomic_dec(&pgio->alert_pending);
+		}
+		break;
+	}
+}
+
+static void pmic_glink_gio_destroy_wq(void *data)
+{
+	struct workqueue_struct *wq = data;
+
+	destroy_workqueue(wq);
+}
+
+static int pmic_glink_gio_probe(struct auxiliary_device *adev,
+				const struct auxiliary_device_id *id)
+{
+	struct pmic_glink_gio *pgio;
+	struct device *dev = &adev->dev;
+	int ret;
+
+	pgio = devm_kzalloc(dev, sizeof(*pgio), GFP_KERNEL);
+	if (!pgio)
+		return -ENOMEM;
+
+	pgio->dev = dev;
+	spin_lock_init(&pgio->lock);
+	init_completion(&pgio->ack);
+	INIT_WORK(&pgio->init_work, pmic_glink_gio_init_work);
+	dev_set_drvdata(dev, pgio);
+
+	pgio->gio = devm_pmic_gio_register(dev, &pmic_glink_gio_ops, pgio);
+	if (IS_ERR(pgio->gio))
+		return dev_err_probe(dev, PTR_ERR(pgio->gio), "failed to register GIO device\n");
+
+	pgio->wq = alloc_ordered_workqueue("pmic_glink_gio", 0);
+	if (!pgio->wq)
+		return -ENOMEM;
+
+	ret = devm_add_action_or_reset(dev, pmic_glink_gio_destroy_wq, pgio->wq);
+	if (ret)
+		return ret;
+
+	pgio->client = devm_pmic_glink_client_alloc(dev, PMIC_GLINK_OWNER_GIO,
+						    pmic_glink_gio_callback,
+						    pmic_glink_gio_pdr_notify,
+						    pgio);
+	if (IS_ERR(pgio->client))
+		return PTR_ERR(pgio->client);
+
+	pmic_glink_client_register(pgio->client);
+
+	return 0;
+}
+
+static void pmic_glink_gio_remove(struct auxiliary_device *adev)
+{
+	struct pmic_glink_gio *pgio = dev_get_drvdata(&adev->dev);
+
+	scoped_guard(spinlock_irqsave, &pgio->lock) {
+		pgio->removing = true;
+		WRITE_ONCE(pgio->service_up, false);
+		if (pgio->pending) {
+			pgio->pending = false;
+			pgio->buf = NULL;
+			pgio->buf_size = 0;
+			pgio->ack_error = -ENODEV;
+			complete(&pgio->ack);
+		}
+		pmic_gio_set_state(pgio->gio, PMIC_GIO_STATE_DOWN);
+	}
+
+	cancel_work_sync(&pgio->init_work);
+	flush_workqueue(pgio->wq);
+}
+
+static const struct auxiliary_device_id pmic_glink_gio_id_table[] = {
+	{ .name = "pmic_glink.gio", },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, pmic_glink_gio_id_table);
+
+static struct auxiliary_driver pmic_glink_gio_driver = {
+	.name = "pmic_glink_gio",
+	.probe = pmic_glink_gio_probe,
+	.remove = pmic_glink_gio_remove,
+	.id_table = pmic_glink_gio_id_table,
+};
+
+module_auxiliary_driver(pmic_glink_gio_driver);
+
+MODULE_DESCRIPTION("Qualcomm PMIC GLINK GIO driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h
index 7cddf1027752..6283495a2396 100644
--- a/include/linux/soc/qcom/pmic_glink.h
+++ b/include/linux/soc/qcom/pmic_glink.h
@@ -11,6 +11,7 @@ struct pmic_glink_client;
 #define PMIC_GLINK_OWNER_BATTMGR	32778
 #define PMIC_GLINK_OWNER_USBC		32779
 #define PMIC_GLINK_OWNER_USBC_PAN	32780
+#define PMIC_GLINK_OWNER_GIO		32785
 
 #define PMIC_GLINK_REQ_RESP		1
 #define PMIC_GLINK_NOTIFY		2

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/3] rtc: Add Qualcomm GIO RTC driver
  2026-11-11  3:28 [PATCH 0/3] soc: qcom: Add Qualcomm PMIC GIO framework and RTC driver using it Fenglin Wu
  2026-11-11  3:28 ` [PATCH 1/3] soc: qcom: Add pmic-gio GIO framework driver Fenglin Wu
  2026-11-11  3:28 ` [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance Fenglin Wu
@ 2026-11-11  3:28 ` Fenglin Wu
  2 siblings, 0 replies; 5+ messages in thread
From: Fenglin Wu @ 2026-11-11  3:28 UTC (permalink / raw)
  To: linux-arm-msm, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
	Alexandre Belloni
  Cc: David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa,
	linux-kernel, linux-rtc, Fenglin Wu, Konrad Dybcio

On the Glymur platform, the GIO (General I/O) interface communicates
with the SoCCP battery-management firmware and provides a set of TAD
(Time-Alarm-Device) registers for reading and writing RTC time. The
platform also includes registers for configuring an alarm timer and
initiating device boot-up into different power stages, such as:
charging only or fully powered-on system, based on the applied power
source (AC/DC). However, repurposing this alarm timer has very limited
value for an RTC driver because it does not expose an interrupt when
the timer expires.

Add an auxiliary RTC driver that corresponds to the auxiliary device
created by the GIO driver, "pmic_gio.rtc", which manages the RTC timer
by accessing to the TAD real-time register block through the regmap
interface exposed by the GIO device.

Tested-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com> # Zenbook A16
Signed-off-by: Fenglin Wu <fenglin.wu@oss.qualcomm.com>
---
 drivers/rtc/Kconfig               |  11 +++
 drivers/rtc/Makefile              |   1 +
 drivers/rtc/rtc-qcom-gio.c        | 153 ++++++++++++++++++++++++++++++++++++++
 drivers/soc/qcom/pmic_glink_gio.c |  36 +++++++++
 4 files changed, 201 insertions(+)

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 73ec9230edfd..a298f7ae68f9 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1863,6 +1863,17 @@ config RTC_DRV_PM8XXX
 	  To compile this driver as a module, choose M here: the
 	  module will be called rtc-pm8xxx.
 
+config RTC_DRV_QCOM_GIO
+	tristate "Qualcomm GIO RTC driver"
+	depends on QCOM_PMIC_GLINK_GIO
+	help
+	  Say y here to support the real-time clock exposed in Qualcomm
+	  SoCCP battery management firmware through the GIO (General I/O)
+	  interface.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called rtc-qcom-gio.
+
 config RTC_DRV_TEGRA
 	tristate "NVIDIA Tegra Internal RTC driver"
 	depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 46f1a0fc2416..9cb76aac7897 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -142,6 +142,7 @@ obj-$(CONFIG_RTC_DRV_PM8XXX)	+= rtc-pm8xxx.o
 obj-$(CONFIG_RTC_DRV_POLARFIRE_SOC)	+= rtc-mpfs.o
 obj-$(CONFIG_RTC_DRV_PS3)	+= rtc-ps3.o
 obj-$(CONFIG_RTC_DRV_PXA)	+= rtc-pxa.o
+obj-$(CONFIG_RTC_DRV_QCOM_GIO)	+= rtc-qcom-gio.o
 obj-$(CONFIG_RTC_DRV_R7301)	+= rtc-r7301.o
 obj-$(CONFIG_RTC_DRV_R9701)	+= rtc-r9701.o
 obj-$(CONFIG_RTC_DRV_RPI)	+= rtc-raspberrypi.o
diff --git a/drivers/rtc/rtc-qcom-gio.c b/drivers/rtc/rtc-qcom-gio.c
new file mode 100644
index 000000000000..679b24f2a190
--- /dev/null
+++ b/drivers/rtc/rtc-qcom-gio.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+#include <linux/soc/qcom/pmic_gio.h>
+#include <linux/soc/qcom/pmic_gio_regs.h>
+
+struct tad_rt {
+	u32 ymd;
+#define RT_YMD_DAY		GENMASK(31, 24)
+#define RT_YMD_MONTH		GENMASK(23, 16)
+#define RT_YMD_YEAR		GENMASK(15, 0)
+
+	u32 hms;
+#define RT_HMS_VALID		GENMASK(31, 24)
+#define RT_HMS_SEC		GENMASK(23, 16)
+#define RT_HMS_MINUTE		GENMASK(15, 8)
+#define RT_HMS_HOUR		GENMASK(7, 0)
+
+	u32 ms_tz;
+	u32 daylight;
+} __packed;
+
+static_assert(sizeof(struct tad_rt) == GIO_SVC_TAD_RT_SIZE);
+
+struct rtc_qcom_gio {
+	struct regmap *regmap;
+	struct rtc_device *rtc;
+};
+
+static int rtc_qcom_gio_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rtc_qcom_gio *rtc = dev_get_drvdata(dev);
+	struct tad_rt rt;
+	int ret;
+
+	ret = regmap_bulk_read(rtc->regmap, GIO_SVC_TAD_RT_ADDR, &rt,
+			       sizeof(rt) / PMIC_GIO_REG_GRANULARITY);
+	if (ret)
+		return ret;
+
+	/*
+	 * The firmware may return valid RTC values even when VALID is not set.
+	 * Only print out register values for potential debug if it's needed.
+	 */
+	if (!FIELD_GET(RT_HMS_VALID, rt.hms))
+		dev_dbg(dev, "RTC registers: ymd=%#010x hms=%#010x\n", rt.ymd, rt.hms);
+
+	tm->tm_year = FIELD_GET(RT_YMD_YEAR, rt.ymd) - 1900;
+	tm->tm_mon = FIELD_GET(RT_YMD_MONTH, rt.ymd) - 1;
+	tm->tm_mday = FIELD_GET(RT_YMD_DAY, rt.ymd);
+	tm->tm_hour = FIELD_GET(RT_HMS_HOUR, rt.hms);
+	tm->tm_min = FIELD_GET(RT_HMS_MINUTE, rt.hms);
+	tm->tm_sec = FIELD_GET(RT_HMS_SEC, rt.hms);
+
+	return 0;
+}
+
+static int rtc_qcom_gio_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rtc_qcom_gio *rtc = dev_get_drvdata(dev);
+	struct tad_rt rt;
+	int ret;
+
+	/*
+	 * The firmware requires the whole TAD_RT block to be written in a
+	 * single transaction, so ms_tz/daylight (which this driver never
+	 * changes) have to be read back and carried through unmodified
+	 * rather than writing just the ymd/hms words we care about. This
+	 * leaves a small window where a concurrent firmware-side update to
+	 * ms_tz/daylight between the read and the write below is clobbered
+	 * with a stale value; accepted given the firmware doesn't support
+	 * partial-block writes.
+	 */
+	ret = regmap_bulk_read(rtc->regmap, GIO_SVC_TAD_RT_ADDR, &rt,
+			       sizeof(rt) / PMIC_GIO_REG_GRANULARITY);
+	if (ret)
+		return ret;
+
+	rt.ymd = FIELD_PREP(RT_YMD_DAY, tm->tm_mday) |
+		 FIELD_PREP(RT_YMD_MONTH, tm->tm_mon + 1) |
+		 FIELD_PREP(RT_YMD_YEAR, tm->tm_year + 1900);
+
+	rt.hms = FIELD_PREP(RT_HMS_VALID, 1) |
+		 FIELD_PREP(RT_HMS_SEC, tm->tm_sec) |
+		 FIELD_PREP(RT_HMS_MINUTE, tm->tm_min) |
+		 FIELD_PREP(RT_HMS_HOUR, tm->tm_hour);
+
+	return regmap_bulk_write(rtc->regmap, GIO_SVC_TAD_RT_ADDR, &rt,
+				 sizeof(rt) / PMIC_GIO_REG_GRANULARITY);
+}
+
+static const struct rtc_class_ops rtc_qcom_gio_ops = {
+	.read_time = rtc_qcom_gio_read_time,
+	.set_time = rtc_qcom_gio_set_time,
+};
+
+static int rtc_qcom_gio_probe(struct auxiliary_device *adev,
+			      const struct auxiliary_device_id *id)
+{
+	struct device *dev = &adev->dev;
+	struct rtc_qcom_gio *rtc;
+	int ret;
+
+	rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	rtc->regmap = dev_get_regmap(dev->parent, NULL);
+	if (!rtc->regmap)
+		return -ENODEV;
+
+	dev_set_drvdata(dev, rtc);
+
+	rtc->rtc = devm_rtc_allocate_device(dev);
+	if (IS_ERR(rtc->rtc))
+		return PTR_ERR(rtc->rtc);
+
+	rtc->rtc->ops = &rtc_qcom_gio_ops;
+	rtc->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+	rtc->rtc->range_max = RTC_TIMESTAMP_END_2099;
+
+	ret = devm_rtc_register_device(rtc->rtc);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct auxiliary_device_id rtc_qcom_gio_id_table[] = {
+	{ .name = "pmic_gio.rtc", },
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, rtc_qcom_gio_id_table);
+
+static struct auxiliary_driver rtc_qcom_gio_driver = {
+	.name = "rtc_qcom_gio",
+	.probe = rtc_qcom_gio_probe,
+	.id_table = rtc_qcom_gio_id_table,
+};
+
+module_auxiliary_driver(rtc_qcom_gio_driver);
+
+MODULE_DESCRIPTION("Qualcomm GIO RTC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/pmic_glink_gio.c b/drivers/soc/qcom/pmic_glink_gio.c
index f222c0d59220..83656a5758b8 100644
--- a/drivers/soc/qcom/pmic_glink_gio.c
+++ b/drivers/soc/qcom/pmic_glink_gio.c
@@ -116,6 +116,8 @@ struct pmic_glink_gio {
 	bool service_up;
 
 	struct gio_write_req write_req;
+
+	struct auxiliary_device *rtc_aux;
 };
 
 /* Must be called with pgio->lock held. */
@@ -318,6 +320,28 @@ static void pmic_glink_gio_enable_alerts(struct pmic_glink_gio *pgio)
 		dev_err(pgio->dev, "GIO turn on global alert failed: %d\n", ret);
 }
 
+static int pmic_glink_gio_add_consumer(struct pmic_glink_gio *pgio,
+				       struct auxiliary_device **aux,
+				       const char *name)
+{
+	struct auxiliary_device *new_aux;
+	int ret;
+
+	new_aux = devm_kzalloc(pgio->dev, sizeof(*new_aux), GFP_KERNEL);
+	if (!new_aux)
+		return -ENOMEM;
+
+	ret = devm_pmic_gio_add_consumer(pgio->gio, new_aux, name);
+	if (ret) {
+		devm_kfree(pgio->dev, new_aux);
+		return ret;
+	}
+
+	*aux = new_aux;
+
+	return 0;
+}
+
 static void pmic_glink_gio_init_work(struct work_struct *work)
 {
 	struct pmic_glink_gio *pgio = container_of(work, struct pmic_glink_gio, init_work);
@@ -326,6 +350,18 @@ static void pmic_glink_gio_init_work(struct work_struct *work)
 	if (!READ_ONCE(pgio->service_up))
 		return;
 
+	/*
+	 * Create GIO consumers after PDR to ensure the transport
+	 * is up before consumers use the GIO regmap device.
+	 */
+	if (!pgio->rtc_aux) {
+		ret = pmic_glink_gio_add_consumer(pgio, &pgio->rtc_aux, "rtc");
+		if (ret) {
+			dev_err(pgio->dev, "failed to add RTC consumer: %d\n", ret);
+			return;
+		}
+	}
+
 	pmic_glink_gio_enable_alerts(pgio);
 }
 

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance
  2026-11-11  3:28 ` [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance Fenglin Wu
@ 2026-11-11 13:14   ` Jeff Roach
  0 siblings, 0 replies; 5+ messages in thread
From: Jeff Roach @ 2026-11-11 13:14 UTC (permalink / raw)
  To: Fenglin Wu
  Cc: Bjorn Andersson, Konrad Dybcio, Abel Vesa, Alexandre Belloni,
	David Collins, Subbaraman Narayanamurthy, Kamal Wadhwa,
	Tingguo Cheng, linux-arm-msm, linux-rtc, linux-kernel

Hi Fenglin,

I tried this series on an HP OmniBook X 16 (X2 Elite "Mahua", DT boot,
qcom,glymur-pmic-glink, 7.3-based kernel, firmware F.04). The RTC part
works: rtc-qcom-gio reads the correct UTC time.

But UCSI breaks when pmic_glink_gio is bound, although the commit
message says ucsi stays on the legacy interface:

- pmic_glink_gio loaded at boot: ucsi_glink fails with
  "error -ETIMEDOUT: PPM init failed", no typec ports.
- pmic_glink_gio blacklisted (patched pmic_glink still loaded): typec
  ports and partners come up, plug/unplug is seen.
- Same boot, then modprobe pmic_glink_gio: the RTC works and the ports
  stay, but UCSI no longer sees any plug or unplug (old partners stay).
  USB data and charging still work.

Happy to test a fix.

Jeff

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-11 13:14 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-11  3:28 [PATCH 0/3] soc: qcom: Add Qualcomm PMIC GIO framework and RTC driver using it Fenglin Wu
2026-11-11  3:28 ` [PATCH 1/3] soc: qcom: Add pmic-gio GIO framework driver Fenglin Wu
2026-11-11  3:28 ` [PATCH 2/3] soc: qcom: pmic_glink: Add GIO pmic_glink instance Fenglin Wu
2026-11-11 13:14   ` Jeff Roach
2026-11-11  3:28 ` [PATCH 3/3] rtc: Add Qualcomm GIO RTC driver Fenglin Wu

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®