From: Alexandre Belloni <alexandre.belloni@bootlin.com>
To: Kamal Wadhwa <kamal.wadhwa@oss.qualcomm.com>
Cc: Bjorn Andersson <andersson@kernel.org>,
Konrad Dybcio <konradybcio@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
linux-kernel@vger.kernel.org, linux-rtc@vger.kernel.org,
linux-arm-msm@vger.kernel.org, devicetree@vger.kernel.org,
Raj Aryan <raryan@qti.qualcomm.com>
Subject: Re: [PATCH 2/2] rtc: qcom: rtc-glink: Add PMIC GLINK RTC driver and extend pmic_glink
Date: Fri, 7 Aug 2026 22:58:44 +0200 [thread overview]
Message-ID: <2026080720584407a4d82d@mail.local> (raw)
In-Reply-To: <20260807-rtc-glink-cleanup-v1-2-7801e4d69c4f@oss.qualcomm.com>
On 07/08/2026 02:26:31+0530, Kamal Wadhwa wrote:
> Add a new RTC driver that communicates with the PMIC firmware over
> the GLINK transport layer. The driver supports reading time and
> alarm functionality via GET_PROPERTY and SET_PROPERTY opcodes.
>
> Time is read via GET_PROPERTY (opcode 0x62) using QCOM_RTC_GLINK_TIME
> property (0x00). Alarm set and enable are handled via SET_PROPERTY
> (opcode 0x63) using QCOM_RTC_GLINK_ALARM and QCOM_RTC_GLINK_ALARM_ENABLE
> properties respectively.
>
> GET_PROPERTY and SET_PROPERTY responses share the same firmware message
> layout, with the firmware echoing back the property value. Both opcodes
> are handled by a common callback path accordingly.
>
> Also extend pmic_glink to register the RTC as a PMIC GLINK client and
> add PMIC GLINK RTC client support for glymur.
>
> Assisted-by: Claude:claude-sonnet-5
> Signed-off-by: Raj Aryan <raryan@qti.qualcomm.com>
> Signed-off-by: Kamal Wadhwa <kamal.wadhwa@oss.qualcomm.com>
> ---
> MAINTAINERS | 7 +
> drivers/rtc/Kconfig | 10 +
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-qcom-glink.c | 690 ++++++++++++++++++++++++++++++++++++
> drivers/soc/qcom/pmic_glink.c | 22 +-
> include/linux/soc/qcom/pmic_glink.h | 1 +
> 6 files changed, 730 insertions(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5f178a5ff026..752cf667b8ad 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22505,6 +22505,13 @@ S: Maintained
> F: Documentation/devicetree/bindings/power/supply/qcom,pmi8998-charger.yaml
> F: drivers/power/supply/qcom_smbx.c
>
> +QUALCOMM PMIC GLINK RTC DRIVER
> +M: Kamal Wadhwa <kamal.wadhwa@oss.qualcomm.com>
> +L: linux-rtc@vger.kernel.org
> +L: linux-arm-msm@vger.kernel.org
> +S: Maintained
> +F: drivers/rtc/rtc-qcom-glink.c
> +
> QUALCOMM PPE DRIVER
> M: Luo Jie <jie.luo@oss.qualcomm.com>
> L: netdev@vger.kernel.org
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 01def8231873..3ac39704b2e1 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1836,6 +1836,16 @@ 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_GLINK
> + tristate "Qualcomm PMIC GLINK RTC"
> + depends on QCOM_PMIC_GLINK
> + help
> + Say Y here to enable support for the RTC exposed by the PMIC
> + over the GLINK transport on Qualcomm platforms.
> +
> + This driver can also be built as a module. If so, the module
> + will be called rtc-qcom-glink.
> +
> 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 0347645b021f..8215a1fc29fb 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -73,6 +73,7 @@ obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
> obj-$(CONFIG_RTC_DRV_FSL_FTM_ALARM) += rtc-fsl-ftm-alarm.o
> obj-$(CONFIG_RTC_DRV_FTRTC010) += rtc-ftrtc010.o
> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
> +obj-$(CONFIG_RTC_DRV_QCOM_GLINK) += rtc-qcom-glink.o
> obj-$(CONFIG_RTC_DRV_GOLDFISH) += rtc-goldfish.o
> obj-$(CONFIG_RTC_DRV_HID_SENSOR_TIME) += rtc-hid-sensor-time.o
> obj-$(CONFIG_RTC_DRV_HYM8563) += rtc-hym8563.o
> diff --git a/drivers/rtc/rtc-qcom-glink.c b/drivers/rtc/rtc-qcom-glink.c
> new file mode 100644
> index 000000000000..611f06a3cc5c
> --- /dev/null
> +++ b/drivers/rtc/rtc-qcom-glink.c
> @@ -0,0 +1,690 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +#include <linux/auxiliary_bus.h>
> +#include <linux/completion.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/property.h>
> +#include <linux/rtc.h>
> +#include <linux/slab.h>
> +#include <linux/soc/qcom/pdr.h>
> +#include <linux/soc/qcom/pmic_glink.h>
> +#include <linux/timekeeping.h>
> +#include <linux/workqueue.h>
> +
> +#define RTC_GLINK_SET_PROPERTY 0x63
> +#define RTC_GLINK_GET_PROPERTY 0x65
> +#define RTC_GLINK_GET_RTC_TICKS 0x67
> +#define RTC_GLINK_GET_REAL_TIME 0x68
> +#define RTC_GLINK_SET_REAL_TIME 0x69
> +#define RTC_GLINK_ALARM_EXPIRED 0x6A
> +
> +enum qcom_rtc_glink_properties {
> + QCOM_RTC_GLINK_TIME = 0,
> + QCOM_RTC_GLINK_ALARM_TIME,
> + QCOM_RTC_GLINK_ALARM_ENABLE,
> +};
> +
> +struct qcom_rtc_glink_msg {
> + struct pmic_glink_hdr hdr;
> + __le32 property;
> + __le32 value;
> +};
> +
> +struct qcom_rtc_glink_generic_req {
> + struct pmic_glink_hdr hdr;
> +};
> +
> +struct qcom_rtc_glink_status_resp {
> + struct pmic_glink_hdr hdr;
> + __le32 return_status;
> +};
> +
> +struct qcom_rtc_glink_ticks_resp {
> + struct pmic_glink_hdr hdr;
> + __le32 return_status;
> + __le32 rtc_ticks;
> +};
> +
> +struct qcom_rtc_glink_real_time_resp {
> + struct pmic_glink_hdr hdr;
> + __le32 return_status;
> + __le32 real_time_data[4];
> +};
> +
> +struct qcom_rtc_glink_set_real_time_req {
> + struct pmic_glink_hdr hdr;
> + __le32 real_time_data[4];
> +};
> +
> +struct qcom_rtc_glink {
> + struct device *dev;
> + struct pmic_glink_client *client;
> + struct rtc_device *rtc;
> +
> + /* Serializes requests: only one may be in flight at a time */
> + struct mutex lock;
This mutex is useless as all the RTC operations are already serialized.
Simply rtc_lock/rtc_unlock in qcom_rtc_glink_alarm_work.
> + struct completion ack;
> + struct work_struct alarm_work;
> +
> + /* Protects service_up and request_pending across callback contexts */
> + spinlock_t state_lock;
> + bool service_up;
> + bool request_pending;
> +
> + int error;
> + u32 resp_value;
> + u32 offset;
> + struct rtc_time resp_tm;
> + bool resp_valid;
> + bool allow_set_time;
> +};
> +
> +static int qcom_rtc_glink_request(struct qcom_rtc_glink *rtc_glink,
> + void *data, size_t len)
> +{
> + unsigned long flags;
> + unsigned long left;
> + int ret;
> +
> + /*
> + * Reinit the completion before request_pending becomes visible to
> + * qcom_rtc_glink_pdr_tify(), so a concurrent SSR down-transition
> + * can't complete() a stale completion that gets wiped out by
> + * reinit_completion() right after.
> + */
> + reinit_completion(&rtc_glink->ack);
> + rtc_glink->error = 0;
> + rtc_glink->resp_valid = false;
> +
> + spin_lock_irqsave(&rtc_glink->state_lock, flags);
> + if (!rtc_glink->service_up) {
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + return -ECONNRESET;
> + }
> + rtc_glink->request_pending = true;
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> +
> + ret = pmic_glink_send(rtc_glink->client, data, len);
> + if (ret < 0) {
> + spin_lock_irqsave(&rtc_glink->state_lock, flags);
> + rtc_glink->request_pending = false;
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + return ret;
> + }
> +
> + left = wait_for_completion_timeout(&rtc_glink->ack, HZ);
> + spin_lock_irqsave(&rtc_glink->state_lock, flags);
> + rtc_glink->request_pending = false;
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + if (!left)
> + return -ETIMEDOUT;
> +
> + return rtc_glink->error;
> +}
> +
> +static int qcom_rtc_glink_set_property(struct qcom_rtc_glink *rtc_glink,
> + u32 property, u32 value)
> +{
> + struct qcom_rtc_glink_msg msg = {
> + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_RTC),
> + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
> + .hdr.opcode = cpu_to_le32(RTC_GLINK_SET_PROPERTY),
> + .property = cpu_to_le32(property),
> + .value = cpu_to_le32(value),
> + };
> + int ret;
> +
> + dev_dbg(rtc_glink->dev,
> + "TX opcode=0x%x property=%u value=%u\n",
> + RTC_GLINK_SET_PROPERTY, property, value);
> + mutex_lock(&rtc_glink->lock);
> + ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
> + mutex_unlock(&rtc_glink->lock);
> + return ret;
> +}
> +
> +static int qcom_rtc_glink_get_property(struct qcom_rtc_glink *rtc_glink,
> + u32 property, u32 *value)
> +{
> + struct qcom_rtc_glink_msg msg = {
> + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_RTC),
> + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
> + .hdr.opcode = cpu_to_le32(RTC_GLINK_GET_PROPERTY),
> + .property = cpu_to_le32(property),
> + };
> + int ret;
> +
> + dev_dbg(rtc_glink->dev,
> + "TX opcode=0x%x property=%u\n",
> + RTC_GLINK_GET_PROPERTY, property);
> + mutex_lock(&rtc_glink->lock);
> + ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
> + if (!ret) {
> + if (!rtc_glink->resp_valid)
> + ret = -EIO;
> + else
> + *value = rtc_glink->resp_value;
> + }
> + mutex_unlock(&rtc_glink->lock);
> + return ret;
> +}
> +
> +static int qcom_rtc_glink_get_ticks(struct qcom_rtc_glink *rtc_glink,
> + u32 *ticks)
> +{
> + struct qcom_rtc_glink_generic_req msg = {
> + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_RTC),
> + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
> + .hdr.opcode = cpu_to_le32(RTC_GLINK_GET_RTC_TICKS),
> + };
> + int ret;
> +
> + dev_dbg(rtc_glink->dev, "TX opcode=0x%x\n", RTC_GLINK_GET_RTC_TICKS);
> + mutex_lock(&rtc_glink->lock);
> + ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
> + if (!ret) {
> + if (!rtc_glink->resp_valid)
> + ret = -EIO;
> + else
> + *ticks = rtc_glink->resp_value;
> + }
> + mutex_unlock(&rtc_glink->lock);
> + return ret;
> +}
> +
> +static int qcom_rtc_glink_get_real_time(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_time *tm)
> +{
> + struct qcom_rtc_glink_generic_req msg = {
> + .hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_RTC),
> + .hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP),
> + .hdr.opcode = cpu_to_le32(RTC_GLINK_GET_REAL_TIME),
> + };
> + int ret;
> +
> + dev_dbg(rtc_glink->dev, "TX opcode=0x%x\n", RTC_GLINK_GET_REAL_TIME);
> + mutex_lock(&rtc_glink->lock);
> + ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
> + if (!ret) {
> + if (!rtc_glink->resp_valid)
> + ret = -EIO;
> + else
> + *tm = rtc_glink->resp_tm;
> + }
> + mutex_unlock(&rtc_glink->lock);
> + return ret;
> +}
> +
> +static int qcom_rtc_glink_set_real_time(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_time *tm)
> +{
> + struct qcom_rtc_glink_set_real_time_req msg = {};
> + u32 w0, w1;
> + int ret;
> +
> + w0 = ((tm->tm_year + 1900) & 0xffff) |
> + (((tm->tm_mon + 1) & 0xff) << 16) |
> + ((tm->tm_mday & 0xff) << 24);
> + w1 = (tm->tm_hour & 0xff) |
> + ((tm->tm_min & 0xff) << 8) |
> + ((tm->tm_sec & 0xff) << 16) |
> + (1U << 24);
> + msg.hdr.owner = cpu_to_le32(PMIC_GLINK_OWNER_RTC);
> + msg.hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP);
> + msg.hdr.opcode = cpu_to_le32(RTC_GLINK_SET_REAL_TIME);
> + msg.real_time_data[0] = cpu_to_le32(w0);
> + msg.real_time_data[1] = cpu_to_le32(w1);
> + msg.real_time_data[2] = 0;
> + msg.real_time_data[3] = 0;
> + dev_dbg(rtc_glink->dev,
> + "TX opcode=0x%x data=%08x %08x\n",
> + RTC_GLINK_SET_REAL_TIME, w0, w1);
> + mutex_lock(&rtc_glink->lock);
> + ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
> + mutex_unlock(&rtc_glink->lock);
> + return ret;
> +}
> +
> +static int qcom_rtc_glink_get_time(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_time *time)
> +{
> + u32 ticks;
> + int ret;
> +
> + ret = qcom_rtc_glink_get_real_time(rtc_glink, time);
> + if (!ret)
> + return 0;
> + dev_warn(rtc_glink->dev, "0x68 failed (%d), falling back\n", ret);
This message is useless, either fail or fallback silently
> +
> + ret = qcom_rtc_glink_get_ticks(rtc_glink, &ticks);
> + if (!ret) {
> + rtc_time64_to_tm((time64_t)ticks + rtc_glink->offset, time);
> + return 0;
> + }
> +
> + ret = qcom_rtc_glink_get_property(rtc_glink, QCOM_RTC_GLINK_TIME,
> + &ticks);
> + if (!ret && ticks != U32_MAX) {
> + rtc_time64_to_tm((time64_t)ticks + rtc_glink->offset, time);
> + return 0;
> + }
> +
> + dev_err(rtc_glink->dev, "all time sources failed\n");
Ditto
> + return -EIO;
> +}
> +
> +static int qcom_rtc_glink_set_time(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_time *time)
> +{
> + time64_t t = rtc_tm_to_time64(time);
> + u32 ticks;
> + int ret;
> +
> + if (!rtc_glink->allow_set_time)
> + return -EOPNOTSUPP;
> +
> + ret = qcom_rtc_glink_set_real_time(rtc_glink, time);
> + if (ret)
> + dev_warn(rtc_glink->dev, "0x69 failed (%d)\n", ret);
Ditto
> +
> + ret = qcom_rtc_glink_set_property(rtc_glink, QCOM_RTC_GLINK_TIME, (u32)t);
> + if (ret)
> + return ret;
> +
> + ret = qcom_rtc_glink_get_ticks(rtc_glink, &ticks);
> + if (!ret && ticks != U32_MAX) {
> + rtc_glink->offset = (u32)t - ticks;
> + } else {
> + dev_warn(rtc_glink->dev, "no tick source for offset\n");
This is also probably useless. No one is going to read those messages or
do anything about them.
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int qcom_rtc_glink_set_alarm_en(struct qcom_rtc_glink *rtc_glink,
> + int enabled)
> +{
> + return qcom_rtc_glink_set_property(rtc_glink,
> + QCOM_RTC_GLINK_ALARM_ENABLE,
> + enabled);
> +}
> +
> +static int qcom_rtc_glink_set_alarm(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_wkalrm *alarm)
> +{
> + time64_t alarm_t = rtc_tm_to_time64(&alarm->time);
> + struct rtc_time now_tm;
> + time64_t now_real, secs_until_alarm;
> + u32 fw_current, fw_alarm;
> + int ret;
> +
> + ret = qcom_rtc_glink_get_time(rtc_glink, &now_tm);
> + if (ret)
> + return ret;
> + now_real = rtc_tm_to_time64(&now_tm);
> + secs_until_alarm = alarm_t - now_real;
> + dev_dbg(rtc_glink->dev,
> + "set_alarm: now_real=%lld alarm_t=%lld delta=%lld\n",
> + now_real, alarm_t, secs_until_alarm);
> + if (secs_until_alarm < 0)
> + return -EINVAL;
> + if (secs_until_alarm > U32_MAX)
> + return -ERANGE;
> +
> + ret = qcom_rtc_glink_get_property(rtc_glink, QCOM_RTC_GLINK_TIME,
> + &fw_current);
> + if (ret)
> + return ret;
> + if (fw_current == U32_MAX)
> + fw_current = 0;
> + fw_alarm = fw_current + (u32)secs_until_alarm;
> + dev_dbg(rtc_glink->dev,
> + "set_alarm: fw_current=%u fw_alarm=%u\n",
> + fw_current, fw_alarm);
> +
> + ret = qcom_rtc_glink_set_alarm_en(rtc_glink, 0);
> + if (ret)
> + return ret;
> +
> + ret = qcom_rtc_glink_set_property(rtc_glink,
> + QCOM_RTC_GLINK_ALARM_TIME, fw_alarm);
> + if (ret)
> + return ret;
> +
> + return qcom_rtc_glink_set_alarm_en(rtc_glink, alarm->enabled);
> +}
> +
> +static int qcom_rtc_glink_get_alarm(struct qcom_rtc_glink *rtc_glink,
> + struct rtc_wkalrm *alarm)
> +{
> + u32 fw_alarm_time, fw_current, alarm_en;
> + struct rtc_time now_tm;
> + time64_t now_real, secs_until_alarm, alarm_real;
> + int ret;
> +
> + ret = qcom_rtc_glink_get_property(rtc_glink,
> + QCOM_RTC_GLINK_ALARM_TIME,
> + &fw_alarm_time);
> + if (ret)
> + return ret;
> +
> + ret = qcom_rtc_glink_get_property(rtc_glink, QCOM_RTC_GLINK_TIME,
> + &fw_current);
> + if (ret)
> + return ret;
> +
> + ret = qcom_rtc_glink_get_property(rtc_glink,
> + QCOM_RTC_GLINK_ALARM_ENABLE,
> + &alarm_en);
> + if (ret)
> + return ret;
> + if (fw_current == U32_MAX)
> + fw_current = 0;
> +
> + ret = qcom_rtc_glink_get_time(rtc_glink, &now_tm);
> + if (ret)
> + return ret;
> + now_real = rtc_tm_to_time64(&now_tm);
> + secs_until_alarm = (time64_t)fw_alarm_time - (time64_t)fw_current;
> + alarm_real = now_real + secs_until_alarm;
> + dev_dbg(rtc_glink->dev,
> + "get_alarm: now_real=%lld fw_current=%u fw_alarm=%u delta=%lld\n",
> + now_real, fw_current, fw_alarm_time, secs_until_alarm);
> + rtc_time64_to_tm(alarm_real, &alarm->time);
> + alarm->enabled = !!alarm_en;
> + return 0;
> +}
> +
> +static void qcom_rtc_glink_alarm_work(struct work_struct *work)
> +{
> + struct qcom_rtc_glink *rtc_glink =
> + container_of(work, struct qcom_rtc_glink, alarm_work);
> + int ret;
> +
> + ret = qcom_rtc_glink_set_alarm_en(rtc_glink, 0);
> + if (ret)
> + dev_err(rtc_glink->dev,
> + "failed to disable alarm after expiry (%d)\n", ret);
So what is the user supposed to do with this message? Also, do you
actually have to disable the alarm? I guess it is not going to fire again.
> + rtc_update_irq(rtc_glink->rtc, 1, RTC_IRQF | RTC_AF);
> +}
> +
> +static void qcom_rtc_glink_callback(const void *data, size_t len, void *priv)
> +{
> + struct qcom_rtc_glink *rtc_glink = priv;
> + const struct pmic_glink_hdr *hdr = data;
> + unsigned long flags;
> + bool pending;
> +
> + if (len < sizeof(*hdr))
> + return;
> +
> + dev_dbg(rtc_glink->dev,
> + "RX opcode=0x%x type=0x%x owner=0x%x len=%zu\n",
> + le32_to_cpu(hdr->opcode), le32_to_cpu(hdr->type),
> + le32_to_cpu(hdr->owner), len);
> +
> + if (le32_to_cpu(hdr->opcode) == RTC_GLINK_ALARM_EXPIRED) {
> + dev_info(rtc_glink->dev, "alarm expired\n");
This is just noise
> + schedule_work(&rtc_glink->alarm_work);
> + return;
> + }
> +
> + /*
> + * Hold state_lock across the pending check and the response fields
> + * below: qcom_rtc_glink_pdr_notify() writes the same error/resp_*
> + * fields under this lock when it force-completes a request on an
> + * SSR down-transition, and the two must not race. Dropping the
> + * response here if !pending also covers requests that already
> + * timed out or were aborted; qcom_rtc_glink_request() will call
> + * reinit_completion() again before this response could be mistaken
> + * for a later request's.
> + */
> + spin_lock_irqsave(&rtc_glink->state_lock, flags);
> + pending = rtc_glink->request_pending;
> +
> + if (!pending) {
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + dev_dbg(rtc_glink->dev,
> + "dropping stale response opcode=0x%x\n",
> + le32_to_cpu(hdr->opcode));
> + return;
> + }
> +
> + switch (le32_to_cpu(hdr->opcode)) {
> + case RTC_GLINK_GET_PROPERTY:
> + fallthrough;
> + case RTC_GLINK_SET_PROPERTY: {
> + const struct qcom_rtc_glink_msg *msg = data;
> +
> + if (len < sizeof(struct pmic_glink_hdr) + sizeof(__le32)) {
> + rtc_glink->error = -EINVAL;
> + break;
> + }
> + rtc_glink->resp_value = le32_to_cpu(msg->property);
> + rtc_glink->resp_valid = true;
> + rtc_glink->error = 0;
> + dev_dbg(rtc_glink->dev, "ACK opcode=0x%x resp_value=%u\n",
> + le32_to_cpu(hdr->opcode), rtc_glink->resp_value);
> + break;
> + }
> +
> + case RTC_GLINK_GET_RTC_TICKS: {
> + const struct qcom_rtc_glink_ticks_resp *resp = data;
> +
> + if (len < sizeof(*resp)) {
> + rtc_glink->error = -EINVAL;
> + break;
> + }
> + if (le32_to_cpu(resp->return_status)) {
> + rtc_glink->error = -EIO;
> + break;
> + }
> + rtc_glink->resp_value = le32_to_cpu(resp->rtc_ticks);
> + rtc_glink->resp_valid = true;
> + rtc_glink->error = 0;
> + dev_dbg(rtc_glink->dev, "ACK opcode=0x%x ticks=%u\n",
> + RTC_GLINK_GET_RTC_TICKS, rtc_glink->resp_value);
> + break;
> + }
> +
> + case RTC_GLINK_GET_REAL_TIME: {
> + const struct qcom_rtc_glink_real_time_resp *resp = data;
> + u32 w0, w1;
> +
> + if (len < sizeof(*resp)) {
> + rtc_glink->error = -EINVAL;
> + break;
> + }
> + if (le32_to_cpu(resp->return_status)) {
> + rtc_glink->error = -EIO;
> + break;
> + }
> + w0 = le32_to_cpu(resp->real_time_data[0]);
> + w1 = le32_to_cpu(resp->real_time_data[1]);
> + rtc_glink->resp_tm.tm_year = (w0 & 0xffff) - 1900;
> + rtc_glink->resp_tm.tm_mon = ((w0 >> 16) & 0xff) - 1;
> + rtc_glink->resp_tm.tm_mday = (w0 >> 24) & 0xff;
> + rtc_glink->resp_tm.tm_hour = w1 & 0xff;
> + rtc_glink->resp_tm.tm_min = (w1 >> 8) & 0xff;
> + rtc_glink->resp_tm.tm_sec = (w1 >> 16) & 0xff;
> + rtc_glink->resp_valid = true;
> + rtc_glink->error = 0;
> + dev_dbg(rtc_glink->dev,
> + "ACK opcode=0x%x %04d-%02d-%02d %02d:%02d:%02d\n",
> + RTC_GLINK_GET_REAL_TIME,
> + rtc_glink->resp_tm.tm_year + 1900,
> + rtc_glink->resp_tm.tm_mon + 1,
> + rtc_glink->resp_tm.tm_mday,
> + rtc_glink->resp_tm.tm_hour,
> + rtc_glink->resp_tm.tm_min,
> + rtc_glink->resp_tm.tm_sec);
> + break;
> + }
> +
> + case RTC_GLINK_SET_REAL_TIME: {
> + const struct qcom_rtc_glink_status_resp *resp = data;
> +
> + if (len < sizeof(*resp)) {
> + rtc_glink->error = -EINVAL;
> + break;
> + }
> + rtc_glink->error = le32_to_cpu(resp->return_status) ? -EIO : 0;
> + dev_dbg(rtc_glink->dev, "ACK opcode=0x%x status=%u\n",
> + RTC_GLINK_SET_REAL_TIME,
> + le32_to_cpu(resp->return_status));
> + break;
> + }
> +
> + default:
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + dev_warn(rtc_glink->dev,
> + "unhandled RX opcode=0x%x len=%zu\n",
> + le32_to_cpu(hdr->opcode), len);
Useless message
> + return;
> + }
> +
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> + complete(&rtc_glink->ack);
> +}
> +
> +static void qcom_rtc_glink_pdr_notify(void *priv, int state)
> +{
> + struct qcom_rtc_glink *rtc_glink = priv;
> + unsigned long flags;
> + bool up = (state == SERVREG_SERVICE_STATE_UP);
> +
> + spin_lock_irqsave(&rtc_glink->state_lock, flags);
> + rtc_glink->service_up = up;
> + if (!up && rtc_glink->request_pending) {
> + rtc_glink->error = -ECONNRESET;
> + rtc_glink->resp_valid = false;
> + complete(&rtc_glink->ack);
> + }
> + spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
> +}
> +
> +static int qcom_rtc_glink_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + return qcom_rtc_glink_get_time(dev_get_drvdata(dev), tm);
> +}
> +
> +static int qcom_rtc_glink_set_time_dev(struct device *dev, struct rtc_time *tm)
> +{
> + return qcom_rtc_glink_set_time(dev_get_drvdata(dev), tm);
> +}
> +
> +static int qcom_rtc_glink_read_alarm(struct device *dev,
> + struct rtc_wkalrm *alrm)
> +{
> + return qcom_rtc_glink_get_alarm(dev_get_drvdata(dev), alrm);
> +}
> +
> +static int qcom_rtc_glink_set_alarm_dev(struct device *dev,
> + struct rtc_wkalrm *alrm)
> +{
> + return qcom_rtc_glink_set_alarm(dev_get_drvdata(dev), alrm);
> +}
> +
> +static int qcom_rtc_glink_alarm_irq_enable(struct device *dev,
> + unsigned int enabled)
> +{
> + return qcom_rtc_glink_set_alarm_en(dev_get_drvdata(dev), enabled);
> +}
> +
> +static const struct rtc_class_ops qcom_rtc_glink_rtc_ops = {
> + .read_time = qcom_rtc_glink_read_time,
> + .set_time = qcom_rtc_glink_set_time_dev,
> + .read_alarm = qcom_rtc_glink_read_alarm,
> + .set_alarm = qcom_rtc_glink_set_alarm_dev,
> + .alarm_irq_enable = qcom_rtc_glink_alarm_irq_enable,
> +};
> +
> +static const struct of_device_id qcom_rtc_glink_of_variants[] = {
> + { .compatible = "qcom,glymur-pmic-glink" },
> + {}
> +};
> +
> +static int qcom_rtc_glink_probe(struct auxiliary_device *adev,
> + const struct auxiliary_device_id *id)
> +{
> + struct device *dev = &adev->dev;
> + struct qcom_rtc_glink *rtc_glink;
> +
> + if (!of_match_device(qcom_rtc_glink_of_variants, dev->parent))
> + return -ENXIO;
> +
> + rtc_glink = devm_kzalloc(dev, sizeof(*rtc_glink), GFP_KERNEL);
> + if (!rtc_glink)
> + return -ENOMEM;
> +
> + rtc_glink->dev = dev;
> + rtc_glink->allow_set_time = device_property_read_bool(dev->parent,
> + "allow-set-time");
> +
> + mutex_init(&rtc_glink->lock);
> + spin_lock_init(&rtc_glink->state_lock);
> + init_completion(&rtc_glink->ack);
> + INIT_WORK(&rtc_glink->alarm_work, qcom_rtc_glink_alarm_work);
> + dev_set_drvdata(dev, rtc_glink);
> +
> + rtc_glink->client = devm_pmic_glink_client_alloc(dev,
> + PMIC_GLINK_OWNER_RTC,
> + qcom_rtc_glink_callback,
> + qcom_rtc_glink_pdr_notify,
> + rtc_glink);
> + if (IS_ERR(rtc_glink->client))
> + return dev_err_probe(dev, PTR_ERR(rtc_glink->client),
> + "failed to allocate glink client\n");
> +
> + pmic_glink_client_register(rtc_glink->client);
> +
> + device_init_wakeup(dev, true);
> +
> + rtc_glink->rtc = devm_rtc_allocate_device(dev);
> + if (IS_ERR(rtc_glink->rtc))
> + return dev_err_probe(dev, PTR_ERR(rtc_glink->rtc),
> + "failed to allocate RTC device\n");
> +
Useless message, there is no failure path without a message in
devm_rtc_allocate_device.
> + rtc_glink->rtc->ops = &qcom_rtc_glink_rtc_ops;
> + rtc_glink->rtc->range_min = 0;
> + rtc_glink->rtc->range_max = U32_MAX;
> +
> + return devm_rtc_register_device(rtc_glink->rtc);
> +}
> +
> +static void qcom_rtc_glink_remove(struct auxiliary_device *adev)
> +{
> + struct qcom_rtc_glink *rtc_glink = dev_get_drvdata(&adev->dev);
> + /*
> + * Nothing left to arm alarm_work after this returns: the glink
> + * client (and its callback/pdr_notify) is torn down by devm after
> + * .remove() returns, and rtc_update_irq() below still targets a
> + * live devm-managed rtc device.
> + */
> + cancel_work_sync(&rtc_glink->alarm_work);
> +}
> +
> +static const struct auxiliary_device_id qcom_rtc_glink_id_table[] = {
> + { .name = "pmic_glink.rtc-glink" },
> + {}
> +};
> +
> +MODULE_DEVICE_TABLE(auxiliary, qcom_rtc_glink_id_table);
> +
> +static struct auxiliary_driver qcom_rtc_glink_driver = {
> + .name = "qcom_pmic_rtc_glink",
> + .id_table = qcom_rtc_glink_id_table,
> + .probe = qcom_rtc_glink_probe,
> + .remove = qcom_rtc_glink_remove,
> +};
> +
> +module_auxiliary_driver(qcom_rtc_glink_driver);
> +MODULE_DESCRIPTION("Qualcomm PMIC GLINK RTC driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c
> index 3042261578aa..7ee70a31ba15 100644
> --- a/drivers/soc/qcom/pmic_glink.c
> +++ b/drivers/soc/qcom/pmic_glink.c
> @@ -21,6 +21,7 @@ enum {
> PMIC_GLINK_CLIENT_BATT = 0,
> PMIC_GLINK_CLIENT_ALTMODE,
> PMIC_GLINK_CLIENT_UCSI,
> + PMIC_GLINK_CLIENT_RTC,
> };
>
> struct pmic_glink_data {
> @@ -40,6 +41,7 @@ struct pmic_glink {
> struct auxiliary_device altmode_aux;
> struct auxiliary_device ps_aux;
> struct auxiliary_device ucsi_aux;
> + struct auxiliary_device rtc_aux;
>
> /* serializing client_state and pdr_state updates */
> struct mutex state_lock;
> @@ -341,6 +343,12 @@ static int pmic_glink_probe(struct platform_device *pdev)
> goto out_release_altmode_aux;
> }
>
> + if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_RTC)) {
> + ret = pmic_glink_add_aux_device(pg, &pg->rtc_aux, "rtc-glink");
> + if (ret)
> + goto out_release_ps_aux;
> + }
> +
> if (pg->data->charger_pdr_service_name && pg->data->charger_pdr_service_path) {
> service = pdr_add_lookup(pg->pdr, pg->data->charger_pdr_service_name,
> pg->data->charger_pdr_service_path);
> @@ -358,6 +366,9 @@ static int pmic_glink_probe(struct platform_device *pdev)
> return 0;
>
> out_release_aux_devices:
> + if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_RTC))
> + pmic_glink_del_aux_device(pg, &pg->rtc_aux);
> +out_release_ps_aux:
> if (pg->data->client_mask & BIT(PMIC_GLINK_CLIENT_BATT))
> pmic_glink_del_aux_device(pg, &pg->ps_aux);
> out_release_altmode_aux:
> @@ -384,11 +395,20 @@ 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_RTC))
> + pmic_glink_del_aux_device(pg, &pg->rtc_aux);
>
> guard(mutex)(&__pmic_glink_lock);
> __pmic_glink = NULL;
> }
>
> +static const struct pmic_glink_data pmic_glink_glymur_data = {
> + .client_mask = BIT(PMIC_GLINK_CLIENT_BATT) |
> + BIT(PMIC_GLINK_CLIENT_ALTMODE) |
> + BIT(PMIC_GLINK_CLIENT_UCSI) |
> + BIT(PMIC_GLINK_CLIENT_RTC),
> +};
> +
> static const struct pmic_glink_data pmic_glink_adsp_data = {
> .client_mask = BIT(PMIC_GLINK_CLIENT_BATT) |
> BIT(PMIC_GLINK_CLIENT_ALTMODE) |
> @@ -404,7 +424,7 @@ static const struct pmic_glink_data pmic_glink_soccp_data = {
> };
>
> 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/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h
> index 7cddf1027752..11031b06b810 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_RTC 32784
>
> #define PMIC_GLINK_REQ_RESP 1
> #define PMIC_GLINK_NOTIFY 2
>
> --
> 2.43.0
>
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
next prev parent reply other threads:[~2026-08-07 20:58 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 20:56 [PATCH 0/2] rtc: Add Qualcomm PMIC RTC-GLINK support for Glymur Kamal Wadhwa
2026-08-06 20:56 ` [PATCH 1/2] dt-bindings: soc: qcom: qcom,pmic-glink: Document RTC client properties Kamal Wadhwa
2026-08-06 21:02 ` sashiko-bot
2026-08-07 0:02 ` Bjorn Andersson
2026-08-06 20:56 ` [PATCH 2/2] rtc: qcom: rtc-glink: Add PMIC GLINK RTC driver and extend pmic_glink Kamal Wadhwa
2026-08-06 21:09 ` sashiko-bot
2026-08-06 23:53 ` Bjorn Andersson
2026-08-07 20:58 ` Alexandre Belloni [this message]
2026-08-11 12:10 ` [PATCH 0/2] rtc: Add Qualcomm PMIC RTC-GLINK support for Glymur Troels Vognbjerg
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=2026080720584407a4d82d@mail.local \
--to=alexandre.belloni@bootlin.com \
--cc=andersson@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=kamal.wadhwa@oss.qualcomm.com \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rtc@vger.kernel.org \
--cc=raryan@qti.qualcomm.com \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.