* [PATCH v2 1/2] soc: qcom: pmic_glink: Add RTC client support
2026-11-02 22:42 [PATCH v2 0/2] rtc: Fix broken RTC alarm support on Qualcomm Glymur and Hamoa/X1E80100 boards Kamal Wadhwa
@ 2026-11-02 22:42 ` Kamal Wadhwa
2026-11-02 22:48 ` sashiko-bot
2026-11-03 9:12 ` Abel Vesa
2026-11-02 22:42 ` [PATCH v2 2/2] rtc: qcom: rtc-glink: Add new driver to support alarms on Glymur & Hamoa boards Kamal Wadhwa
1 sibling, 2 replies; 6+ messages in thread
From: Kamal Wadhwa @ 2026-11-02 22:42 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Abel Vesa, Alexandre Belloni
Cc: Kamal Wadhwa, linux-arm-msm, linux-kernel, linux-rtc, Raj Aryan
On some Qualcomm targets like Hamoa and Glymur, PMIC RTC is only reachable over
the GLINK interface, so add a new RTC auxiliary device ("rtc-glink") to let the
new RTC glink driver bind against it, mirroring the existing
altmode/UCSI/power-supply clients.
Also add pmic_glink_client_unregister() so the RTC aux client can detach from the
clients list before unregistering the RTC in order to avoid use-after-free issue
in a scenario when an alarm IRQ is received during teardown.
Enable the RTC client on Glymur and Hamoa/X1E80100 variants, as only those
currently support this RTC client from the firmware side.
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>
---
drivers/soc/qcom/pmic_glink.c | 43 ++++++++++++++++++++++++++++++++++++-
include/linux/soc/qcom/pmic_glink.h | 1 +
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c
index 84fd0c82c0c4..8af6f4a65e61 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;
@@ -118,6 +120,17 @@ void pmic_glink_client_register(struct pmic_glink_client *client)
}
EXPORT_SYMBOL_GPL(pmic_glink_client_register);
+void pmic_glink_client_unregister(struct pmic_glink_client *client)
+{
+ struct pmic_glink *pg = client->pg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pg->client_lock, flags);
+ list_del_init(&client->node);
+ spin_unlock_irqrestore(&pg->client_lock, flags);
+}
+EXPORT_SYMBOL_GPL(pmic_glink_client_unregister);
+
int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len)
{
struct pmic_glink *pg = client->pg;
@@ -342,6 +355,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);
@@ -359,6 +378,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:
@@ -385,11 +407,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,10 +435,20 @@ static const struct pmic_glink_data pmic_glink_soccp_data = {
BIT(PMIC_GLINK_CLIENT_UCSI),
};
+static const struct pmic_glink_data pmic_glink_x1e80100_data = {
+ .client_mask = BIT(PMIC_GLINK_CLIENT_BATT) |
+ BIT(PMIC_GLINK_CLIENT_ALTMODE) |
+ BIT(PMIC_GLINK_CLIENT_UCSI) |
+ BIT(PMIC_GLINK_CLIENT_RTC),
+ .charger_pdr_service_name = "tms/servreg",
+ .charger_pdr_service_path = "msm/adsp/charger_pd",
+};
+
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 },
+ { .compatible = "qcom,x1e80100-pmic-glink", .data = &pmic_glink_x1e80100_data },
{}
};
MODULE_DEVICE_TABLE(of, pmic_glink_of_match);
diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h
index 7cddf1027752..c78dc7a8c99e 100644
--- a/include/linux/soc/qcom/pmic_glink.h
+++ b/include/linux/soc/qcom/pmic_glink.h
@@ -29,5 +29,6 @@ struct pmic_glink_client *devm_pmic_glink_client_alloc(struct device *dev,
void (*pdr)(void *, int),
void *priv);
void pmic_glink_client_register(struct pmic_glink_client *client);
+void pmic_glink_client_unregister(struct pmic_glink_client *client);
#endif
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v2 2/2] rtc: qcom: rtc-glink: Add new driver to support alarms on Glymur & Hamoa boards
2026-11-02 22:42 [PATCH v2 0/2] rtc: Fix broken RTC alarm support on Qualcomm Glymur and Hamoa/X1E80100 boards Kamal Wadhwa
2026-11-02 22:42 ` [PATCH v2 1/2] soc: qcom: pmic_glink: Add RTC client support Kamal Wadhwa
@ 2026-11-02 22:42 ` Kamal Wadhwa
2026-11-02 23:06 ` sashiko-bot
1 sibling, 1 reply; 6+ messages in thread
From: Kamal Wadhwa @ 2026-11-02 22:42 UTC (permalink / raw)
To: Bjorn Andersson, Konrad Dybcio, Abel Vesa, Alexandre Belloni
Cc: Kamal Wadhwa, linux-arm-msm, linux-kernel, linux-rtc, Raj Aryan
On some Qualcomm targets like Glymur & Hamoa, the SPMI based rtc-pm8xxx.c
driver is unable to receive alarm IRQs. This happens because the SPMI IRQ
permission for receiving the RTC alarms is routed to remote firmware that
implements the TAD feature [1].
TAD feature is a standard feature on Microsoft Windows OS based devices.
Since Glymur and Hamoa were originally launched on Windows OS, unlike the other
Qualcomm targets which were launched with Android/Linux OS, rtc-pm8xxx.c can
not provide alarm support on these targets due to the design change in the
RTC's SPMI IRQ routing.
In order to make the RTC alarm IRQ available on the Linux kernel, the PMIC RTC
Alarm IRQ is also relayed to APPS by the remote firmware via Glink interface.
To receive the relayed alarms on APPS and also to support normal time
read/write operations for RTC, add a new auxiliary driver `rtc-qcom-glink.c`
under the existing `pmic_glink` base driver, which will register an RTC device
and under the hood will handle the communication with the remote firmware for
RTC related operations via the pmic_glink interface driver.
Note - On the Qualcomm targets, pmic_glink driver is currently used for
communicating with the remote firmware by 3 clients i.e. qcom_battmgr,
ucsi_glink, altmode_glink aux client drivers. RTC glink will be the 4th aux
client to this pmic_glink driver.
[1] See "Control method time and alarm device" in the specification:
Link: https://learn.microsoft.com/en-us/windows-hardware/drivers/bringup/acpi-defined-devices
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 | 590 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 608 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index aaf2825ac945..b8f500581da2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23152,6 +23152,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 73ec9230edfd..41ace94550ed 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1863,6 +1863,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 46f1a0fc2416..a01803a69ad7 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..b1394b08e28d
--- /dev/null
+++ b/drivers/rtc/rtc-qcom-glink.c
@@ -0,0 +1,590 @@
+// 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/completion.h>
+#include <linux/device.h>
+#include <linux/module.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 PMIC_GLINK_OWNER_RTC 32784
+
+#define RTC_GLINK_SET_TIMER_VALUE 0x63
+#define RTC_GLINK_GET_TIMER_VALUE 0x65
+#define RTC_GLINK_GET_REAL_TIME 0x68
+#define RTC_GLINK_SET_REAL_TIME 0x69
+#define RTC_GLINK_ALARM_EXPIRED 0x6A
+
+#define QCOM_RTC_GLINK_TIME 0
+#define QCOM_RTC_GLINK_ALARM_TIME 1
+#define QCOM_RTC_GLINK_ALARM_ENABLE 2
+
+#define RTC_GLINK_W0_YEAR GENMASK(15, 0)
+#define RTC_GLINK_W0_MONTH GENMASK(23, 16)
+#define RTC_GLINK_W0_DAY GENMASK(31, 24)
+#define RTC_GLINK_W1_HOUR GENMASK(7, 0)
+#define RTC_GLINK_W1_MIN GENMASK(15, 8)
+#define RTC_GLINK_W1_SEC GENMASK(23, 16)
+#define RTC_GLINK_W1_VALID GENMASK(31, 24)
+
+struct qcom_rtc_glink_get_timer_req {
+ struct pmic_glink_hdr hdr;
+ __le32 timer_id;
+};
+
+struct qcom_rtc_glink_set_timer_req {
+ struct pmic_glink_hdr hdr;
+ __le32 timer_id;
+ __le32 timer_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;
+};
+
+/*
+ * Shared by SET_TIMER_VALUE (value = pass/fail status) and
+ * GET_TIMER_VALUE (value = the timer's current value) responses --
+ * firmware never echoes the request's timer_id back.
+ */
+struct qcom_rtc_glink_timer_resp {
+ struct pmic_glink_hdr hdr;
+ __le32 value;
+};
+
+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;
+
+ struct completion ack;
+ struct work_struct alarm_work;
+
+ spinlock_t state_lock;
+ bool service_up;
+ bool request_pending;
+ bool timed_out;
+
+ int error;
+ u32 resp_value;
+ struct rtc_time resp_tm;
+ bool resp_valid;
+};
+
+static int qcom_rtc_glink_request(struct qcom_rtc_glink *rtc_glink,
+ void *data, size_t len)
+{
+ unsigned long flags;
+ bool timed_out = false;
+ int ret;
+
+ /*
+ * Reinit the completion before request_pending becomes visible to
+ * qcom_rtc_glink_pdr_notify(), 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 || rtc_glink->timed_out) {
+ spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
+ return !rtc_glink->service_up ? -ECONNRESET : -EAGAIN;
+ }
+ rtc_glink->request_pending = true;
+ spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
+
+ ret = pmic_glink_send(rtc_glink->client, data, len);
+ if (!ret && !wait_for_completion_timeout(&rtc_glink->ack, HZ)) {
+ ret = -ETIMEDOUT;
+ timed_out = true;
+ }
+
+ spin_lock_irqsave(&rtc_glink->state_lock, flags);
+ rtc_glink->request_pending = false;
+ rtc_glink->timed_out |= timed_out;
+ spin_unlock_irqrestore(&rtc_glink->state_lock, flags);
+
+ return ret < 0 ? ret : rtc_glink->error;
+}
+
+static int qcom_rtc_glink_set_timer_value(struct qcom_rtc_glink *rtc_glink,
+ u32 timer_id, u32 timer_value)
+{
+ struct qcom_rtc_glink_set_timer_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_SET_TIMER_VALUE),
+ .timer_id = cpu_to_le32(timer_id),
+ .timer_value = cpu_to_le32(timer_value),
+ };
+
+ dev_dbg(rtc_glink->dev,
+ "TX opcode=0x%x timer_id=%u timer_value=%u\n",
+ RTC_GLINK_SET_TIMER_VALUE, timer_id, timer_value);
+ return qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
+}
+
+static int qcom_rtc_glink_get_timer_value(struct qcom_rtc_glink *rtc_glink,
+ u32 timer_id, u32 *timer_value)
+{
+ struct qcom_rtc_glink_get_timer_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_TIMER_VALUE),
+ .timer_id = cpu_to_le32(timer_id),
+ };
+ int ret;
+
+ dev_dbg(rtc_glink->dev,
+ "TX opcode=0x%x timer_id=%u\n",
+ RTC_GLINK_GET_TIMER_VALUE, timer_id);
+ ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ if (!rtc_glink->resp_valid)
+ return -EIO;
+
+ *timer_value = rtc_glink->resp_value;
+ return 0;
+}
+
+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);
+ ret = qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
+ if (ret)
+ return ret;
+ if (!rtc_glink->resp_valid)
+ return -EIO;
+
+ *tm = rtc_glink->resp_tm;
+ return 0;
+}
+
+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;
+
+ w0 = FIELD_PREP(RTC_GLINK_W0_YEAR, tm->tm_year + 1900) |
+ FIELD_PREP(RTC_GLINK_W0_MONTH, tm->tm_mon + 1) |
+ FIELD_PREP(RTC_GLINK_W0_DAY, tm->tm_mday);
+ w1 = FIELD_PREP(RTC_GLINK_W1_HOUR, tm->tm_hour) |
+ FIELD_PREP(RTC_GLINK_W1_MIN, tm->tm_min) |
+ FIELD_PREP(RTC_GLINK_W1_SEC, tm->tm_sec) |
+ FIELD_PREP(RTC_GLINK_W1_VALID, 1);
+
+ 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);
+ return qcom_rtc_glink_request(rtc_glink, &msg, sizeof(msg));
+}
+
+static int qcom_rtc_glink_get_time(struct qcom_rtc_glink *rtc_glink,
+ struct rtc_time *time)
+{
+ return qcom_rtc_glink_get_real_time(rtc_glink, time);
+}
+
+static int qcom_rtc_glink_set_time(struct qcom_rtc_glink *rtc_glink,
+ struct rtc_time *time)
+{
+ return qcom_rtc_glink_set_real_time(rtc_glink, time);
+}
+
+static int qcom_rtc_glink_set_alarm_en(struct qcom_rtc_glink *rtc_glink,
+ int enabled)
+{
+ return qcom_rtc_glink_set_timer_value(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_timer_value(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_timer_value(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_timer_value(rtc_glink, QCOM_RTC_GLINK_ALARM_TIME, &fw_alarm_time);
+ if (ret)
+ return ret;
+
+ ret = qcom_rtc_glink_get_timer_value(rtc_glink, QCOM_RTC_GLINK_TIME, &fw_current);
+ if (ret)
+ return ret;
+
+ ret = qcom_rtc_glink_get_timer_value(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 = (u32)(fw_alarm_time - 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);
+
+ /*
+ * The glink client is registered, and can receive an unsolicited
+ * ALARM_EXPIRED, before the RTC device exists - e.g. if the board
+ * booted because of a previously armed alarm.
+ */
+ if (!rtc_glink->rtc)
+ return;
+
+ rtc_lock(rtc_glink->rtc);
+ rtc_update_irq(rtc_glink->rtc, 1, RTC_IRQF | RTC_AF);
+ rtc_unlock(rtc_glink->rtc);
+}
+
+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) {
+ 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) {
+ if (rtc_glink->timed_out)
+ rtc_glink->timed_out = false;
+ 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_TIMER_VALUE:
+ fallthrough;
+ case RTC_GLINK_SET_TIMER_VALUE: {
+ const struct qcom_rtc_glink_timer_resp *resp = data;
+
+ if (len < sizeof(*resp)) {
+ rtc_glink->error = -EINVAL;
+ break;
+ }
+ rtc_glink->resp_value = le32_to_cpu(resp->value);
+ 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_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 = FIELD_GET(RTC_GLINK_W0_YEAR, w0) - 1900;
+ rtc_glink->resp_tm.tm_mon = FIELD_GET(RTC_GLINK_W0_MONTH, w0) - 1;
+ rtc_glink->resp_tm.tm_mday = FIELD_GET(RTC_GLINK_W0_DAY, w0);
+ rtc_glink->resp_tm.tm_hour = FIELD_GET(RTC_GLINK_W1_HOUR, w1);
+ rtc_glink->resp_tm.tm_min = FIELD_GET(RTC_GLINK_W1_MIN, w1);
+ rtc_glink->resp_tm.tm_sec = FIELD_GET(RTC_GLINK_W1_SEC, w1);
+ 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);
+ 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);
+ }
+ if (!up)
+ rtc_glink->timed_out = false;
+ 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 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;
+ struct rtc_time tm;
+ int ret;
+
+ rtc_glink = devm_kzalloc(dev, sizeof(*rtc_glink), GFP_KERNEL);
+ if (!rtc_glink)
+ return -ENOMEM;
+
+ rtc_glink->dev = dev;
+
+ 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);
+
+ ret = qcom_rtc_glink_get_time(rtc_glink, &tm);
+ if (ret) {
+ pmic_glink_client_unregister(rtc_glink->client);
+ if (ret == -ECONNRESET)
+ return -EPROBE_DEFER;
+ return dev_err_probe(dev, -ENODEV,
+ "RTC GLINK firmware is not available\n");
+ }
+
+ device_init_wakeup(dev, true);
+
+ rtc_glink->rtc = devm_rtc_allocate_device(dev);
+ if (IS_ERR(rtc_glink->rtc)) {
+ pmic_glink_client_unregister(rtc_glink->client);
+ return PTR_ERR(rtc_glink->rtc);
+ }
+
+ 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);
+
+ pmic_glink_client_unregister(rtc_glink->client);
+ 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");
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread