* [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 11:44 [PATCH] mmc: sdhci-esdhc-imx: enable 'SDHCI_QUIRK_NO_LED' quirk for S32G Ciprian Costea
@ 2024-11-26 11:44 ` Ciprian Costea
2024-11-30 20:07 ` kernel test robot
0 siblings, 1 reply; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:44 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea, Bogdan Hamciuc, Ghennadi Procopciuc
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Add a RTC driver for NXP S32G2/S32G3 SoCs.
RTC tracks clock time during system suspend. It can be a wakeup source
for the S32G2/S32G3 SoC based boards.
The RTC module from S32G2/S32G3 is not battery-powered and it is not kept
alive during system reset.
Co-developed-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
Signed-off-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
Co-developed-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-s32g.c | 554 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 566 insertions(+)
create mode 100644 drivers/rtc/rtc-s32g.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e87c3d74565c..18fc3577f6cd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -2054,4 +2054,15 @@ config RTC_DRV_SSD202D
This driver can also be built as a module, if so, the module
will be called "rtc-ssd20xd".
+config RTC_DRV_S32G
+ tristate "RTC driver for S32G2/S32G3 SoCs"
+ depends on ARCH_S32 || COMPILE_TEST
+ depends on COMMON_CLK
+ help
+ Say yes to enable RTC driver for platforms based on the
+ S32G2/S32G3 SoC family.
+
+ This RTC module can be used as a wakeup source.
+ Please note that it is not battery-powered.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 8ee79cb18322..a63d010a753c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o
obj-$(CONFIG_RTC_DRV_RX8111) += rtc-rx8111.o
obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o
obj-$(CONFIG_RTC_DRV_RZN1) += rtc-rzn1.o
+obj-$(CONFIG_RTC_DRV_S32G) += rtc-s32g.o
obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c
new file mode 100644
index 000000000000..ad78423783da
--- /dev/null
+++ b/drivers/rtc/rtc-s32g.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+
+#define RTCC_OFFSET 0x4ul
+#define RTCS_OFFSET 0x8ul
+#define RTCCNT_OFFSET 0xCul
+#define APIVAL_OFFSET 0x10ul
+#define RTCVAL_OFFSET 0x14ul
+
+/* RTCC fields */
+#define RTCC_CNTEN BIT(31)
+#define RTCC_RTCIE_SHIFT 30
+#define RTCC_RTCIE BIT(RTCC_RTCIE_SHIFT)
+#define RTCC_APIEN BIT(15)
+#define RTCC_APIIE BIT(14)
+#define RTCC_CLKSEL_OFFSET 12
+#define RTCC_CLKSEL_MASK GENMASK(13, 12)
+#define RTCC_CLKSEL(n) (((n) << 12) & RTCC_CLKSEL_MASK)
+#define RTCC_DIV512EN BIT(11)
+#define RTCC_DIV32EN BIT(10)
+
+/* RTCS fields */
+#define RTCS_RTCF BIT(29)
+#define RTCS_INV_RTC BIT(18)
+#define RTCS_APIF BIT(13)
+
+#define RTCCNT_MAX_VAL GENMASK(31, 0)
+#define RTC_SYNCH_TIMEOUT (100 * USEC_PER_MSEC)
+
+#define RTC_CLK_MUX_SIZE 4
+
+/*
+ * S32G2 and S32G3 SoCs have RTC clock source 1 reserved and
+ * should not be used.
+ */
+#define RTC_QUIRK_SRC1_RESERVED BIT(2)
+
+enum {
+ RTC_CLK_SRC0,
+ RTC_CLK_SRC1,
+ RTC_CLK_SRC2,
+ RTC_CLK_SRC3
+};
+
+enum {
+ DIV1 = 1,
+ DIV32 = 32,
+ DIV512 = 512,
+ DIV512_32 = 16384
+};
+
+static const char *rtc_clk_src[RTC_CLK_MUX_SIZE] = {
+ "source0",
+ "source1",
+ "source2",
+ "source3"
+};
+
+struct rtc_time_base {
+ s64 sec;
+ u64 cycles;
+ struct rtc_time tm;
+};
+
+struct rtc_priv {
+ struct rtc_device *rdev;
+ void __iomem *rtc_base;
+ struct clk *ipg;
+ struct clk *clk_src;
+ const struct rtc_soc_data *rtc_data;
+ struct rtc_time_base base;
+ u64 rtc_hz;
+ int dt_irq_id;
+ int clk_src_idx;
+};
+
+struct rtc_soc_data {
+ u32 clk_div;
+ u32 quirks;
+};
+
+static const struct rtc_soc_data rtc_s32g2_data = {
+ .clk_div = DIV512,
+ .quirks = RTC_QUIRK_SRC1_RESERVED,
+};
+
+static int is_src1_reserved(struct rtc_priv *priv)
+{
+ return priv->rtc_data->quirks & RTC_QUIRK_SRC1_RESERVED;
+}
+
+static u64 cycles_to_sec(u64 hz, u64 cycles)
+{
+ return div_u64(cycles, hz);
+}
+
+/**
+ * Convert a number of seconds to a value suitable for RTCVAL in our clock's
+ * current configuration.
+ * @rtcval: The value to go into RTCVAL[RTCVAL]
+ * Returns: 0 for success, -EINVAL if @seconds push the counter past the
+ * 32bit register range
+ */
+static int sec_to_rtcval(const struct rtc_priv *priv,
+ unsigned long seconds, u32 *rtcval)
+{
+ u32 delta_cnt;
+
+ if (!seconds || seconds > cycles_to_sec(priv->rtc_hz, RTCCNT_MAX_VAL))
+ return -EINVAL;
+
+ /*
+ * RTCCNT is read-only; we must return a value relative to the
+ * current value of the counter (and hope we don't linger around
+ * too much before we get to enable the interrupt)
+ */
+ delta_cnt = seconds * priv->rtc_hz;
+ *rtcval = delta_cnt + ioread32(priv->rtc_base + RTCCNT_OFFSET);
+
+ return 0;
+}
+
+static irqreturn_t s32g_rtc_handler(int irq, void *dev)
+{
+ struct rtc_priv *priv = platform_get_drvdata(dev);
+ u32 status;
+
+ status = ioread32(priv->rtc_base + RTCS_OFFSET);
+
+ if (status & RTCS_RTCF) {
+ iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
+ rtc_update_irq(priv->rdev, 1, RTC_AF);
+ }
+
+ if (status & RTCS_APIF)
+ rtc_update_irq(priv->rdev, 1, RTC_PF);
+
+ iowrite32(status, priv->rtc_base + RTCS_OFFSET);
+
+ return IRQ_HANDLED;
+}
+
+static s64 s32g_rtc_get_time_or_alrm(struct rtc_priv *priv,
+ u32 offset)
+{
+ u32 counter;
+
+ counter = ioread32(priv->rtc_base + offset);
+
+ if (counter < priv->base.cycles)
+ return -EINVAL;
+
+ counter -= priv->base.cycles;
+
+ return priv->base.sec + cycles_to_sec(priv->rtc_hz, counter);
+}
+
+static int s32g_rtc_read_time(struct device *dev,
+ struct rtc_time *tm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ s64 sec;
+
+ sec = s32g_rtc_get_time_or_alrm(priv, RTCCNT_OFFSET);
+ if (sec < 0)
+ return -EINVAL;
+
+ rtc_time64_to_tm(sec, tm);
+
+ return 0;
+}
+
+static int s32g_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 rtcc, rtccnt, rtcval;
+ s64 sec;
+
+ sec = s32g_rtc_get_time_or_alrm(priv, RTCVAL_OFFSET);
+ if (sec < 0)
+ return -EINVAL;
+
+ rtc_time64_to_tm(sec, &alrm->time);
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ alrm->enabled = sec && (rtcc & RTCC_RTCIE);
+
+ alrm->pending = 0;
+ if (alrm->enabled) {
+ rtccnt = ioread32(priv->rtc_base + RTCCNT_OFFSET);
+ rtcval = ioread32(priv->rtc_base + RTCVAL_OFFSET);
+
+ if (rtccnt < rtcval)
+ alrm->pending = 1;
+ }
+
+ return 0;
+}
+
+static int s32g_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 rtcc;
+
+ if (!priv->dt_irq_id)
+ return -EIO;
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ if (enabled)
+ rtcc |= RTCC_RTCIE;
+
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+
+ return 0;
+}
+
+static int s32g_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ struct rtc_time time_crt;
+ long long t_crt, t_alrm;
+ u32 rtcval, rtcs;
+ int ret = 0;
+
+ iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
+
+ t_alrm = rtc_tm_to_time64(&alrm->time);
+
+ /*
+ * Assuming the alarm is being set relative to the same time
+ * returned by our s32g_rtc_read_time callback
+ */
+ ret = s32g_rtc_read_time(dev, &time_crt);
+ if (ret)
+ return ret;
+
+ t_crt = rtc_tm_to_time64(&time_crt);
+ ret = sec_to_rtcval(priv, t_alrm - t_crt, &rtcval);
+ if (ret) {
+ dev_warn(dev, "Alarm is set too far in the future\n");
+ return -ERANGE;
+ }
+
+ ret = read_poll_timeout(ioread32, rtcs, !(rtcs & RTCS_INV_RTC),
+ 0, RTC_SYNCH_TIMEOUT, false, priv->rtc_base + RTCS_OFFSET);
+ if (ret)
+ return ret;
+
+ iowrite32(rtcval, priv->rtc_base + RTCVAL_OFFSET);
+
+ return 0;
+}
+
+static int s32g_rtc_set_time(struct device *dev,
+ struct rtc_time *time)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+
+ priv->base.cycles = ioread32(priv->rtc_base + RTCCNT_OFFSET);
+ priv->base.sec = rtc_tm_to_time64(time);
+
+ return 0;
+}
+
+/*
+ * Disable the 32-bit free running counter.
+ * This allows Clock Source and Divisors selection
+ * to be performed without causing synchronization issues.
+ */
+static void s32g_rtc_disable(struct rtc_priv *priv)
+{
+ u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+
+ rtcc &= ~RTCC_CNTEN;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static void s32g_rtc_enable(struct rtc_priv *priv)
+{
+ u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+
+ rtcc |= RTCC_CNTEN;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static int rtc_clk_src_setup(struct rtc_priv *priv)
+{
+ u32 rtcc = 0;
+
+ switch (priv->clk_src_idx) {
+ case RTC_CLK_SRC0:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC0);
+ break;
+ case RTC_CLK_SRC1:
+ if (is_src1_reserved(priv))
+ return -EOPNOTSUPP;
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC1);
+ break;
+ case RTC_CLK_SRC2:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC2);
+ break;
+ case RTC_CLK_SRC3:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (priv->rtc_data->clk_div) {
+ case DIV512_32:
+ rtcc |= RTCC_DIV512EN;
+ rtcc |= RTCC_DIV32EN;
+ break;
+ case DIV512:
+ rtcc |= RTCC_DIV512EN;
+ break;
+ case DIV32:
+ rtcc |= RTCC_DIV32EN;
+ break;
+ case DIV1:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rtcc |= RTCC_RTCIE;
+ /*
+ * Make sure the CNTEN is 0 before we configure
+ * the clock source and dividers.
+ */
+ s32g_rtc_disable(priv);
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+ s32g_rtc_enable(priv);
+
+ return 0;
+}
+
+static const struct rtc_class_ops rtc_ops = {
+ .read_time = s32g_rtc_read_time,
+ .set_time = s32g_rtc_set_time,
+ .read_alarm = s32g_rtc_read_alarm,
+ .set_alarm = s32g_rtc_set_alarm,
+ .alarm_irq_enable = s32g_rtc_alarm_irq_enable,
+};
+
+static int rtc_clk_dts_setup(struct rtc_priv *priv,
+ struct device *dev)
+{
+ int i;
+
+ priv->ipg = devm_clk_get_enabled(dev, "ipg");
+ if (IS_ERR(priv->ipg))
+ return dev_err_probe(dev, PTR_ERR(priv->ipg),
+ "Failed to get 'ipg' clock\n");
+
+ for (i = 0; i < RTC_CLK_MUX_SIZE; i++) {
+ priv->clk_src = devm_clk_get_enabled(dev, rtc_clk_src[i]);
+ if (!IS_ERR(priv->clk_src)) {
+ priv->clk_src_idx = i;
+ break;
+ }
+ }
+
+ if (IS_ERR(priv->clk_src))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_src),
+ "Failed to get rtc module clock source\n");
+
+ return 0;
+}
+
+static int s32g_rtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtc_priv *priv;
+ int ret = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->rtc_data = of_device_get_match_data(dev);
+ if (!priv->rtc_data)
+ return -ENODEV;
+
+ priv->rtc_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->rtc_base))
+ return PTR_ERR(priv->rtc_base);
+
+ device_init_wakeup(dev, true);
+
+ ret = rtc_clk_dts_setup(priv, dev);
+ if (ret)
+ return ret;
+
+ priv->rdev = devm_rtc_allocate_device(dev);
+ if (IS_ERR(priv->rdev))
+ return PTR_ERR(priv->rdev);
+
+ ret = rtc_clk_src_setup(priv);
+ if (ret)
+ return ret;
+
+ priv->rtc_hz = clk_get_rate(priv->clk_src);
+ if (!priv->rtc_hz)
+ return dev_err_probe(dev, -EINVAL, "Failed to get RTC frequency\n");
+
+ priv->rtc_hz /= priv->rtc_data->clk_div;
+
+ platform_set_drvdata(pdev, priv);
+ priv->rdev->ops = &rtc_ops;
+
+ priv->dt_irq_id = platform_get_irq(pdev, 0);
+ if (priv->dt_irq_id < 0)
+ return priv->dt_irq_id;
+
+ ret = devm_request_irq(dev, priv->dt_irq_id,
+ s32g_rtc_handler, 0, dev_name(dev), pdev);
+ if (ret) {
+ dev_err(dev, "Request interrupt %d failed, error: %d\n",
+ priv->dt_irq_id, ret);
+ goto disable_rtc;
+ }
+
+ ret = devm_rtc_register_device(priv->rdev);
+ if (ret)
+ goto disable_rtc;
+
+ return 0;
+
+disable_rtc:
+ s32g_rtc_disable(priv);
+ return ret;
+}
+
+static void enable_api_irq(struct device *dev, unsigned int enabled)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 api_irq = RTCC_APIEN | RTCC_APIIE;
+ u32 rtcc;
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ if (enabled)
+ rtcc |= api_irq;
+ else
+ rtcc &= ~api_irq;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static int s32g_rtc_suspend(struct device *dev)
+{
+ struct rtc_priv *init_priv = dev_get_drvdata(dev);
+ struct rtc_priv priv;
+ long long base_sec;
+ u32 rtcval, rtccnt;
+ int ret = 0;
+ u32 sec;
+
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ /* Save last known timestamp */
+ ret = s32g_rtc_read_time(dev, &init_priv->base.tm);
+ if (ret)
+ return ret;
+
+ /*
+ * Use a local copy of the RTC control block to
+ * avoid restoring it on resume path.
+ */
+ memcpy(&priv, init_priv, sizeof(priv));
+
+ rtccnt = ioread32(init_priv->rtc_base + RTCCNT_OFFSET);
+ rtcval = ioread32(init_priv->rtc_base + RTCVAL_OFFSET);
+ sec = cycles_to_sec(init_priv->rtc_hz, rtcval - rtccnt);
+
+ /* Adjust for the number of seconds we'll be asleep */
+ base_sec = rtc_tm_to_time64(&init_priv->base.tm);
+ base_sec += sec;
+ rtc_time64_to_tm(base_sec, &init_priv->base.tm);
+
+ /* Reset RTC to prevent overflow.
+ * RTCCNT (RTC Counter) cannot be individually reset
+ * since it is RO (read-only).
+ */
+ s32g_rtc_disable(&priv);
+ s32g_rtc_enable(&priv);
+
+ ret = sec_to_rtcval(&priv, sec, &rtcval);
+ if (ret) {
+ dev_warn(dev, "Alarm is too far in the future\n");
+ return -ERANGE;
+ }
+
+ enable_api_irq(dev, 1);
+ iowrite32(rtcval, priv.rtc_base + APIVAL_OFFSET);
+ iowrite32(0, priv.rtc_base + RTCVAL_OFFSET);
+
+ return ret;
+}
+
+static int s32g_rtc_resume(struct device *dev)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ /* Disable wake-up interrupts */
+ enable_api_irq(dev, 0);
+
+ ret = rtc_clk_src_setup(priv);
+ if (ret)
+ return ret;
+
+ /*
+ * Now RTCCNT has just been reset, and is out of sync with priv->base;
+ * reapply the saved time settings.
+ */
+ return s32g_rtc_set_time(dev, &priv->base.tm);
+}
+
+static const struct of_device_id rtc_dt_ids[] = {
+ { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data},
+ { /* sentinel */ },
+};
+
+static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops,
+ s32g_rtc_suspend, s32g_rtc_resume);
+
+static struct platform_driver s32g_rtc_driver = {
+ .driver = {
+ .name = "s32g-rtc",
+ .pm = pm_sleep_ptr(&s32g_rtc_pm_ops),
+ .of_match_table = rtc_dt_ids,
+ },
+ .probe = s32g_rtc_probe,
+};
+module_platform_driver(s32g_rtc_driver);
+
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("NXP RTC driver for S32G2/S32G3");
+MODULE_LICENSE("GPL");
--
2.45.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs
@ 2024-11-26 11:49 Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
` (3 more replies)
0 siblings, 4 replies; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:49 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
This patch series add support for the NXP RTC hardware module present on
S32G2/S32G3 SoCs.
RTC tracks clock time during system suspend. It is used as a time-based
wakeup source for the S32G2/S32G3 SoCs.
RTC is not battery-powered and it is not kept alive during system reset.
Following is an example of Suspend to RAM trigger on S32G2/S32G3 SoCs,
using userspace tools such as rtcwake:
# rtcwake -s 2 -m mem
# rtcwake: assuming RTC uses UTC ...
# rtcwake: wakeup from "mem" using /dev/rtc0 at Wed Feb 6 06:28:36 2036
#
Changes in V5:
- Removed rollover support.
- Removed clock switching support between Runtime and Suspend. A clock source
which is always available has been used instead.
- Enabled 512 value RTC hardware frequency divisor to achieve higher rollover
time
- Removed unneeded 'remove' callback.
- Decreased driver print verbosity on error paths.
- Provided 'clock-names' actual names in bindings documentation
- Remove __maybe_unused notations. Used the DEFINE_SIMPLE_DEV_PM_OPS() and
pm_sleep_ptr() macros to handle the .suspend/.resume callbacks.
- Fixed some alignment issues.
Changes in V4:
- Dropped 'assigned-*' clock management approach. Simplified RTC Runtime
and Suspend/Standby clock configuration.
- Simplified error paths on probe function
- Removed node label from bindings example
- Several cosmetic coding style fixes
Changes in V3:
- Removed 'nxp,s32g3-rtc' compatible string
- Change to 'remove' callback from 'remove_new'
- Used 'dev.parent' from 'struct rtc_device' instead of defining a
specific 'struct device' in driver data
- Fixed several errors reported by kernel test robot
- Removed 'assigned-clocks', 'assigned-clock-parents' and
'assigned-clock-rates' from required properties in the binding
documentation.
- Refactored S32G RTC driver such that a default clock source and
divisors configuration will be applied in case 'assigned-clocks' and
'assigned-clock-parents' properties are missing.
Changes in V2:
- Removed 'clksel' support from dts bindings. Used clock parents support
from CCF to better illustrate the RTC hardware IP from S32G2/S32G3.
- Removed frequency dividers support from dts bindings. Used assigned
clock frequencies support from CCF instead.
- Reduced the interval while RTC is voluntarily disabled to a simple
register write in order to avoid any race condition between a possbile
rollover and 'suspend' callback execution flow.
- Updated bindings documentation with respect to clocking support.
- Fixed a potential unused variable warning reported by kernel test robot.
- Updated to usage of 'devm_rtc_allocate_device' and 'devm_rtc_register_device'
instead of deprecated 'devm_rtc_device_register'.
Ciprian Marian Costea (4):
dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
rtc: s32g: add NXP S32G2/S32G3 SoC support
arm64: defconfig: add S32G RTC module support
MAINTAINERS: add NXP S32G RTC driver
.../devicetree/bindings/rtc/nxp,s32g-rtc.yaml | 69 +++
MAINTAINERS | 2 +
arch/arm64/configs/defconfig | 1 +
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-s32g.c | 554 ++++++++++++++++++
6 files changed, 638 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
create mode 100644 drivers/rtc/rtc-s32g.c
--
2.45.2
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
2024-11-26 11:49 [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs Ciprian Costea
@ 2024-11-26 11:49 ` Ciprian Costea
2024-11-26 19:08 ` Krzysztof Kozlowski
2024-11-27 14:43 ` Rob Herring
2024-11-26 11:49 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
` (2 subsequent siblings)
3 siblings, 2 replies; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:49 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea, Bogdan-Gabriel Roman, Ghennadi Procopciuc
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
RTC tracks clock time during system suspend and it is used as a wakeup
source on S32G2/S32G3 architecture.
RTC from S32G2/S32G3 is not battery-powered and it is not kept alive
during system reset.
Co-developed-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
Signed-off-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
.../devicetree/bindings/rtc/nxp,s32g-rtc.yaml | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
diff --git a/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
new file mode 100644
index 000000000000..89414a0d926c
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/rtc/nxp,s32g-rtc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP S32G2/S32G3 Real Time Clock (RTC)
+
+maintainers:
+ - Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
+ - Ciprian Marian Costea <ciprianmarian.costea@nxp.com>
+
+description:
+ RTC hardware module present on S32G2/S32G3 SoCs is used as a wakeup source.
+ It is not kept alive during system reset and it is not battery-powered.
+
+properties:
+ compatible:
+ oneOf:
+ - enum:
+ - nxp,s32g2-rtc
+ - items:
+ - const: nxp,s32g3-rtc
+ - const: nxp,s32g2-rtc
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: ipg clock drives the access to the RTC iomapped registers
+ - description: Clock source for the RTC module. Can be selected between
+ 4 different clock sources using an integrated hardware mux.
+ On S32G2/S32G3 SoCs, 'source0' is the SIRC clock (~32KHz) and it is
+ available during standby and runtime. 'source1' is reserved and cannot
+ be used. 'source2' is the FIRC clock and it is only available during
+ runtime providing a better resolution (~48MHz). 'source3' is an external
+ RTC clock source which can be additionally added in hardware.
+
+ clock-names:
+ items:
+ - const: ipg
+ - enum: [ source0, source1, source2, source3 ]
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ rtc@40060000 {
+ compatible = "nxp,s32g3-rtc",
+ "nxp,s32g2-rtc";
+ reg = <0x40060000 0x1000>;
+ interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clks 54>, <&clks 55>;
+ clock-names = "ipg", "source0";
+ };
--
2.45.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 11:49 [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
@ 2024-11-26 11:49 ` Ciprian Costea
2024-11-26 18:05 ` Frank Li
2024-11-28 21:18 ` kernel test robot
2024-11-26 11:49 ` [PATCH v5 3/4] arm64: defconfig: add S32G RTC module support Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 4/4] MAINTAINERS: add NXP S32G RTC driver Ciprian Costea
3 siblings, 2 replies; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:49 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea, Bogdan Hamciuc, Ghennadi Procopciuc
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Add a RTC driver for NXP S32G2/S32G3 SoCs.
RTC tracks clock time during system suspend. It can be a wakeup source
for the S32G2/S32G3 SoC based boards.
The RTC module from S32G2/S32G3 is not battery-powered and it is not kept
alive during system reset.
Co-developed-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
Signed-off-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
Co-developed-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
Signed-off-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
drivers/rtc/Kconfig | 11 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-s32g.c | 554 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 566 insertions(+)
create mode 100644 drivers/rtc/rtc-s32g.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index e87c3d74565c..18fc3577f6cd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -2054,4 +2054,15 @@ config RTC_DRV_SSD202D
This driver can also be built as a module, if so, the module
will be called "rtc-ssd20xd".
+config RTC_DRV_S32G
+ tristate "RTC driver for S32G2/S32G3 SoCs"
+ depends on ARCH_S32 || COMPILE_TEST
+ depends on COMMON_CLK
+ help
+ Say yes to enable RTC driver for platforms based on the
+ S32G2/S32G3 SoC family.
+
+ This RTC module can be used as a wakeup source.
+ Please note that it is not battery-powered.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 8ee79cb18322..a63d010a753c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o
obj-$(CONFIG_RTC_DRV_RX8111) += rtc-rx8111.o
obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o
obj-$(CONFIG_RTC_DRV_RZN1) += rtc-rzn1.o
+obj-$(CONFIG_RTC_DRV_S32G) += rtc-s32g.o
obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o
obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c
new file mode 100644
index 000000000000..ad78423783da
--- /dev/null
+++ b/drivers/rtc/rtc-s32g.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2024 NXP
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/iopoll.h>
+#include <linux/math64.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+
+#define RTCC_OFFSET 0x4ul
+#define RTCS_OFFSET 0x8ul
+#define RTCCNT_OFFSET 0xCul
+#define APIVAL_OFFSET 0x10ul
+#define RTCVAL_OFFSET 0x14ul
+
+/* RTCC fields */
+#define RTCC_CNTEN BIT(31)
+#define RTCC_RTCIE_SHIFT 30
+#define RTCC_RTCIE BIT(RTCC_RTCIE_SHIFT)
+#define RTCC_APIEN BIT(15)
+#define RTCC_APIIE BIT(14)
+#define RTCC_CLKSEL_OFFSET 12
+#define RTCC_CLKSEL_MASK GENMASK(13, 12)
+#define RTCC_CLKSEL(n) (((n) << 12) & RTCC_CLKSEL_MASK)
+#define RTCC_DIV512EN BIT(11)
+#define RTCC_DIV32EN BIT(10)
+
+/* RTCS fields */
+#define RTCS_RTCF BIT(29)
+#define RTCS_INV_RTC BIT(18)
+#define RTCS_APIF BIT(13)
+
+#define RTCCNT_MAX_VAL GENMASK(31, 0)
+#define RTC_SYNCH_TIMEOUT (100 * USEC_PER_MSEC)
+
+#define RTC_CLK_MUX_SIZE 4
+
+/*
+ * S32G2 and S32G3 SoCs have RTC clock source 1 reserved and
+ * should not be used.
+ */
+#define RTC_QUIRK_SRC1_RESERVED BIT(2)
+
+enum {
+ RTC_CLK_SRC0,
+ RTC_CLK_SRC1,
+ RTC_CLK_SRC2,
+ RTC_CLK_SRC3
+};
+
+enum {
+ DIV1 = 1,
+ DIV32 = 32,
+ DIV512 = 512,
+ DIV512_32 = 16384
+};
+
+static const char *rtc_clk_src[RTC_CLK_MUX_SIZE] = {
+ "source0",
+ "source1",
+ "source2",
+ "source3"
+};
+
+struct rtc_time_base {
+ s64 sec;
+ u64 cycles;
+ struct rtc_time tm;
+};
+
+struct rtc_priv {
+ struct rtc_device *rdev;
+ void __iomem *rtc_base;
+ struct clk *ipg;
+ struct clk *clk_src;
+ const struct rtc_soc_data *rtc_data;
+ struct rtc_time_base base;
+ u64 rtc_hz;
+ int dt_irq_id;
+ int clk_src_idx;
+};
+
+struct rtc_soc_data {
+ u32 clk_div;
+ u32 quirks;
+};
+
+static const struct rtc_soc_data rtc_s32g2_data = {
+ .clk_div = DIV512,
+ .quirks = RTC_QUIRK_SRC1_RESERVED,
+};
+
+static int is_src1_reserved(struct rtc_priv *priv)
+{
+ return priv->rtc_data->quirks & RTC_QUIRK_SRC1_RESERVED;
+}
+
+static u64 cycles_to_sec(u64 hz, u64 cycles)
+{
+ return div_u64(cycles, hz);
+}
+
+/**
+ * Convert a number of seconds to a value suitable for RTCVAL in our clock's
+ * current configuration.
+ * @rtcval: The value to go into RTCVAL[RTCVAL]
+ * Returns: 0 for success, -EINVAL if @seconds push the counter past the
+ * 32bit register range
+ */
+static int sec_to_rtcval(const struct rtc_priv *priv,
+ unsigned long seconds, u32 *rtcval)
+{
+ u32 delta_cnt;
+
+ if (!seconds || seconds > cycles_to_sec(priv->rtc_hz, RTCCNT_MAX_VAL))
+ return -EINVAL;
+
+ /*
+ * RTCCNT is read-only; we must return a value relative to the
+ * current value of the counter (and hope we don't linger around
+ * too much before we get to enable the interrupt)
+ */
+ delta_cnt = seconds * priv->rtc_hz;
+ *rtcval = delta_cnt + ioread32(priv->rtc_base + RTCCNT_OFFSET);
+
+ return 0;
+}
+
+static irqreturn_t s32g_rtc_handler(int irq, void *dev)
+{
+ struct rtc_priv *priv = platform_get_drvdata(dev);
+ u32 status;
+
+ status = ioread32(priv->rtc_base + RTCS_OFFSET);
+
+ if (status & RTCS_RTCF) {
+ iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
+ rtc_update_irq(priv->rdev, 1, RTC_AF);
+ }
+
+ if (status & RTCS_APIF)
+ rtc_update_irq(priv->rdev, 1, RTC_PF);
+
+ iowrite32(status, priv->rtc_base + RTCS_OFFSET);
+
+ return IRQ_HANDLED;
+}
+
+static s64 s32g_rtc_get_time_or_alrm(struct rtc_priv *priv,
+ u32 offset)
+{
+ u32 counter;
+
+ counter = ioread32(priv->rtc_base + offset);
+
+ if (counter < priv->base.cycles)
+ return -EINVAL;
+
+ counter -= priv->base.cycles;
+
+ return priv->base.sec + cycles_to_sec(priv->rtc_hz, counter);
+}
+
+static int s32g_rtc_read_time(struct device *dev,
+ struct rtc_time *tm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ s64 sec;
+
+ sec = s32g_rtc_get_time_or_alrm(priv, RTCCNT_OFFSET);
+ if (sec < 0)
+ return -EINVAL;
+
+ rtc_time64_to_tm(sec, tm);
+
+ return 0;
+}
+
+static int s32g_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 rtcc, rtccnt, rtcval;
+ s64 sec;
+
+ sec = s32g_rtc_get_time_or_alrm(priv, RTCVAL_OFFSET);
+ if (sec < 0)
+ return -EINVAL;
+
+ rtc_time64_to_tm(sec, &alrm->time);
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ alrm->enabled = sec && (rtcc & RTCC_RTCIE);
+
+ alrm->pending = 0;
+ if (alrm->enabled) {
+ rtccnt = ioread32(priv->rtc_base + RTCCNT_OFFSET);
+ rtcval = ioread32(priv->rtc_base + RTCVAL_OFFSET);
+
+ if (rtccnt < rtcval)
+ alrm->pending = 1;
+ }
+
+ return 0;
+}
+
+static int s32g_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 rtcc;
+
+ if (!priv->dt_irq_id)
+ return -EIO;
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ if (enabled)
+ rtcc |= RTCC_RTCIE;
+
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+
+ return 0;
+}
+
+static int s32g_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ struct rtc_time time_crt;
+ long long t_crt, t_alrm;
+ u32 rtcval, rtcs;
+ int ret = 0;
+
+ iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
+
+ t_alrm = rtc_tm_to_time64(&alrm->time);
+
+ /*
+ * Assuming the alarm is being set relative to the same time
+ * returned by our s32g_rtc_read_time callback
+ */
+ ret = s32g_rtc_read_time(dev, &time_crt);
+ if (ret)
+ return ret;
+
+ t_crt = rtc_tm_to_time64(&time_crt);
+ ret = sec_to_rtcval(priv, t_alrm - t_crt, &rtcval);
+ if (ret) {
+ dev_warn(dev, "Alarm is set too far in the future\n");
+ return -ERANGE;
+ }
+
+ ret = read_poll_timeout(ioread32, rtcs, !(rtcs & RTCS_INV_RTC),
+ 0, RTC_SYNCH_TIMEOUT, false, priv->rtc_base + RTCS_OFFSET);
+ if (ret)
+ return ret;
+
+ iowrite32(rtcval, priv->rtc_base + RTCVAL_OFFSET);
+
+ return 0;
+}
+
+static int s32g_rtc_set_time(struct device *dev,
+ struct rtc_time *time)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+
+ priv->base.cycles = ioread32(priv->rtc_base + RTCCNT_OFFSET);
+ priv->base.sec = rtc_tm_to_time64(time);
+
+ return 0;
+}
+
+/*
+ * Disable the 32-bit free running counter.
+ * This allows Clock Source and Divisors selection
+ * to be performed without causing synchronization issues.
+ */
+static void s32g_rtc_disable(struct rtc_priv *priv)
+{
+ u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+
+ rtcc &= ~RTCC_CNTEN;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static void s32g_rtc_enable(struct rtc_priv *priv)
+{
+ u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+
+ rtcc |= RTCC_CNTEN;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static int rtc_clk_src_setup(struct rtc_priv *priv)
+{
+ u32 rtcc = 0;
+
+ switch (priv->clk_src_idx) {
+ case RTC_CLK_SRC0:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC0);
+ break;
+ case RTC_CLK_SRC1:
+ if (is_src1_reserved(priv))
+ return -EOPNOTSUPP;
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC1);
+ break;
+ case RTC_CLK_SRC2:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC2);
+ break;
+ case RTC_CLK_SRC3:
+ rtcc |= RTCC_CLKSEL(RTC_CLK_SRC3);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (priv->rtc_data->clk_div) {
+ case DIV512_32:
+ rtcc |= RTCC_DIV512EN;
+ rtcc |= RTCC_DIV32EN;
+ break;
+ case DIV512:
+ rtcc |= RTCC_DIV512EN;
+ break;
+ case DIV32:
+ rtcc |= RTCC_DIV32EN;
+ break;
+ case DIV1:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ rtcc |= RTCC_RTCIE;
+ /*
+ * Make sure the CNTEN is 0 before we configure
+ * the clock source and dividers.
+ */
+ s32g_rtc_disable(priv);
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+ s32g_rtc_enable(priv);
+
+ return 0;
+}
+
+static const struct rtc_class_ops rtc_ops = {
+ .read_time = s32g_rtc_read_time,
+ .set_time = s32g_rtc_set_time,
+ .read_alarm = s32g_rtc_read_alarm,
+ .set_alarm = s32g_rtc_set_alarm,
+ .alarm_irq_enable = s32g_rtc_alarm_irq_enable,
+};
+
+static int rtc_clk_dts_setup(struct rtc_priv *priv,
+ struct device *dev)
+{
+ int i;
+
+ priv->ipg = devm_clk_get_enabled(dev, "ipg");
+ if (IS_ERR(priv->ipg))
+ return dev_err_probe(dev, PTR_ERR(priv->ipg),
+ "Failed to get 'ipg' clock\n");
+
+ for (i = 0; i < RTC_CLK_MUX_SIZE; i++) {
+ priv->clk_src = devm_clk_get_enabled(dev, rtc_clk_src[i]);
+ if (!IS_ERR(priv->clk_src)) {
+ priv->clk_src_idx = i;
+ break;
+ }
+ }
+
+ if (IS_ERR(priv->clk_src))
+ return dev_err_probe(dev, PTR_ERR(priv->clk_src),
+ "Failed to get rtc module clock source\n");
+
+ return 0;
+}
+
+static int s32g_rtc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtc_priv *priv;
+ int ret = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->rtc_data = of_device_get_match_data(dev);
+ if (!priv->rtc_data)
+ return -ENODEV;
+
+ priv->rtc_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->rtc_base))
+ return PTR_ERR(priv->rtc_base);
+
+ device_init_wakeup(dev, true);
+
+ ret = rtc_clk_dts_setup(priv, dev);
+ if (ret)
+ return ret;
+
+ priv->rdev = devm_rtc_allocate_device(dev);
+ if (IS_ERR(priv->rdev))
+ return PTR_ERR(priv->rdev);
+
+ ret = rtc_clk_src_setup(priv);
+ if (ret)
+ return ret;
+
+ priv->rtc_hz = clk_get_rate(priv->clk_src);
+ if (!priv->rtc_hz)
+ return dev_err_probe(dev, -EINVAL, "Failed to get RTC frequency\n");
+
+ priv->rtc_hz /= priv->rtc_data->clk_div;
+
+ platform_set_drvdata(pdev, priv);
+ priv->rdev->ops = &rtc_ops;
+
+ priv->dt_irq_id = platform_get_irq(pdev, 0);
+ if (priv->dt_irq_id < 0)
+ return priv->dt_irq_id;
+
+ ret = devm_request_irq(dev, priv->dt_irq_id,
+ s32g_rtc_handler, 0, dev_name(dev), pdev);
+ if (ret) {
+ dev_err(dev, "Request interrupt %d failed, error: %d\n",
+ priv->dt_irq_id, ret);
+ goto disable_rtc;
+ }
+
+ ret = devm_rtc_register_device(priv->rdev);
+ if (ret)
+ goto disable_rtc;
+
+ return 0;
+
+disable_rtc:
+ s32g_rtc_disable(priv);
+ return ret;
+}
+
+static void enable_api_irq(struct device *dev, unsigned int enabled)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ u32 api_irq = RTCC_APIEN | RTCC_APIIE;
+ u32 rtcc;
+
+ rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
+ if (enabled)
+ rtcc |= api_irq;
+ else
+ rtcc &= ~api_irq;
+ iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
+}
+
+static int s32g_rtc_suspend(struct device *dev)
+{
+ struct rtc_priv *init_priv = dev_get_drvdata(dev);
+ struct rtc_priv priv;
+ long long base_sec;
+ u32 rtcval, rtccnt;
+ int ret = 0;
+ u32 sec;
+
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ /* Save last known timestamp */
+ ret = s32g_rtc_read_time(dev, &init_priv->base.tm);
+ if (ret)
+ return ret;
+
+ /*
+ * Use a local copy of the RTC control block to
+ * avoid restoring it on resume path.
+ */
+ memcpy(&priv, init_priv, sizeof(priv));
+
+ rtccnt = ioread32(init_priv->rtc_base + RTCCNT_OFFSET);
+ rtcval = ioread32(init_priv->rtc_base + RTCVAL_OFFSET);
+ sec = cycles_to_sec(init_priv->rtc_hz, rtcval - rtccnt);
+
+ /* Adjust for the number of seconds we'll be asleep */
+ base_sec = rtc_tm_to_time64(&init_priv->base.tm);
+ base_sec += sec;
+ rtc_time64_to_tm(base_sec, &init_priv->base.tm);
+
+ /* Reset RTC to prevent overflow.
+ * RTCCNT (RTC Counter) cannot be individually reset
+ * since it is RO (read-only).
+ */
+ s32g_rtc_disable(&priv);
+ s32g_rtc_enable(&priv);
+
+ ret = sec_to_rtcval(&priv, sec, &rtcval);
+ if (ret) {
+ dev_warn(dev, "Alarm is too far in the future\n");
+ return -ERANGE;
+ }
+
+ enable_api_irq(dev, 1);
+ iowrite32(rtcval, priv.rtc_base + APIVAL_OFFSET);
+ iowrite32(0, priv.rtc_base + RTCVAL_OFFSET);
+
+ return ret;
+}
+
+static int s32g_rtc_resume(struct device *dev)
+{
+ struct rtc_priv *priv = dev_get_drvdata(dev);
+ int ret;
+
+ if (!device_may_wakeup(dev))
+ return 0;
+
+ /* Disable wake-up interrupts */
+ enable_api_irq(dev, 0);
+
+ ret = rtc_clk_src_setup(priv);
+ if (ret)
+ return ret;
+
+ /*
+ * Now RTCCNT has just been reset, and is out of sync with priv->base;
+ * reapply the saved time settings.
+ */
+ return s32g_rtc_set_time(dev, &priv->base.tm);
+}
+
+static const struct of_device_id rtc_dt_ids[] = {
+ { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data},
+ { /* sentinel */ },
+};
+
+static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops,
+ s32g_rtc_suspend, s32g_rtc_resume);
+
+static struct platform_driver s32g_rtc_driver = {
+ .driver = {
+ .name = "s32g-rtc",
+ .pm = pm_sleep_ptr(&s32g_rtc_pm_ops),
+ .of_match_table = rtc_dt_ids,
+ },
+ .probe = s32g_rtc_probe,
+};
+module_platform_driver(s32g_rtc_driver);
+
+MODULE_AUTHOR("NXP");
+MODULE_DESCRIPTION("NXP RTC driver for S32G2/S32G3");
+MODULE_LICENSE("GPL");
--
2.45.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 3/4] arm64: defconfig: add S32G RTC module support
2024-11-26 11:49 [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
@ 2024-11-26 11:49 ` Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 4/4] MAINTAINERS: add NXP S32G RTC driver Ciprian Costea
3 siblings, 0 replies; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:49 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
The RTC hardware module present on S32G based SoCs tracks clock time
during system suspend and it is used as a wakeup source on S32G2/S32G3
architecture.
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
arch/arm64/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 5fdbfea7a5b2..584cef78c984 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -1209,6 +1209,7 @@ CONFIG_RTC_DRV_DA9063=m
CONFIG_RTC_DRV_EFI=y
CONFIG_RTC_DRV_CROS_EC=y
CONFIG_RTC_DRV_FSL_FTM_ALARM=m
+CONFIG_RTC_DRV_S32G=m
CONFIG_RTC_DRV_S3C=y
CONFIG_RTC_DRV_PL031=y
CONFIG_RTC_DRV_SUN6I=y
--
2.45.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 4/4] MAINTAINERS: add NXP S32G RTC driver
2024-11-26 11:49 [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs Ciprian Costea
` (2 preceding siblings ...)
2024-11-26 11:49 ` [PATCH v5 3/4] arm64: defconfig: add S32G RTC module support Ciprian Costea
@ 2024-11-26 11:49 ` Ciprian Costea
3 siblings, 0 replies; 16+ messages in thread
From: Ciprian Costea @ 2024-11-26 11:49 UTC (permalink / raw)
To: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Ciprian Marian Costea
From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
Add the NXP S32G RTC driver as maintained so further patches on this
driver can be reviewed under this architecture.
Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
---
MAINTAINERS | 2 ++
1 file changed, 2 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 7bfef98226d9..991a9df6819d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2763,8 +2763,10 @@ R: Ghennadi Procopciuc <ghennadi.procopciuc@oss.nxp.com>
L: NXP S32 Linux Team <s32@nxp.com>
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
S: Maintained
+F: Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
F: arch/arm64/boot/dts/freescale/s32g*.dts*
F: drivers/pinctrl/nxp/
+F: drivers/rtc/rtc-s32g.c
ARM/Orion SoC/Technologic Systems TS-78xx platform support
M: Alexander Clouter <alex@digriz.org.uk>
--
2.45.2
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 11:49 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
@ 2024-11-26 18:05 ` Frank Li
2024-11-27 15:07 ` Ciprian Marian Costea
2024-11-28 21:18 ` kernel test robot
1 sibling, 1 reply; 16+ messages in thread
From: Frank Li @ 2024-11-26 18:05 UTC (permalink / raw)
To: Ciprian Costea
Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan Hamciuc, Ghennadi Procopciuc
On Tue, Nov 26, 2024 at 01:49:38PM +0200, Ciprian Costea wrote:
> From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>
> Add a RTC driver for NXP S32G2/S32G3 SoCs.
>
> RTC tracks clock time during system suspend. It can be a wakeup source
> for the S32G2/S32G3 SoC based boards.
>
> The RTC module from S32G2/S32G3 is not battery-powered and it is not kept
> alive during system reset.
>
> Co-developed-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
> Signed-off-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
> Co-developed-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
> Signed-off-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
> Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
> ---
> drivers/rtc/Kconfig | 11 +
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-s32g.c | 554 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 566 insertions(+)
> create mode 100644 drivers/rtc/rtc-s32g.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index e87c3d74565c..18fc3577f6cd 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -2054,4 +2054,15 @@ config RTC_DRV_SSD202D
> This driver can also be built as a module, if so, the module
> will be called "rtc-ssd20xd".
>
> +config RTC_DRV_S32G
> + tristate "RTC driver for S32G2/S32G3 SoCs"
> + depends on ARCH_S32 || COMPILE_TEST
> + depends on COMMON_CLK
> + help
> + Say yes to enable RTC driver for platforms based on the
> + S32G2/S32G3 SoC family.
> +
> + This RTC module can be used as a wakeup source.
> + Please note that it is not battery-powered.
> +
> endif # RTC_CLASS
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 8ee79cb18322..a63d010a753c 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -158,6 +158,7 @@ obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o
> obj-$(CONFIG_RTC_DRV_RX8111) += rtc-rx8111.o
> obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o
> obj-$(CONFIG_RTC_DRV_RZN1) += rtc-rzn1.o
> +obj-$(CONFIG_RTC_DRV_S32G) += rtc-s32g.o
> obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o
> obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
> obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
> diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c
> new file mode 100644
> index 000000000000..ad78423783da
> --- /dev/null
> +++ b/drivers/rtc/rtc-s32g.c
> @@ -0,0 +1,554 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright 2024 NXP
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/clk-provider.h>
> +#include <linux/err.h>
> +#include <linux/iopoll.h>
> +#include <linux/math64.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/rtc.h>
> +
> +#define RTCC_OFFSET 0x4ul
> +#define RTCS_OFFSET 0x8ul
> +#define RTCCNT_OFFSET 0xCul
> +#define APIVAL_OFFSET 0x10ul
> +#define RTCVAL_OFFSET 0x14ul
> +
> +/* RTCC fields */
> +#define RTCC_CNTEN BIT(31)
> +#define RTCC_RTCIE_SHIFT 30
> +#define RTCC_RTCIE BIT(RTCC_RTCIE_SHIFT)
Only use RTCC_RTCIE_SHIFT here, just put 30 and remove
RTCC_RTCIE_SHIFT.
> +#define RTCC_APIEN BIT(15)
> +#define RTCC_APIIE BIT(14)
> +#define RTCC_CLKSEL_OFFSET 12
No used
> +#define RTCC_CLKSEL_MASK GENMASK(13, 12)
> +#define RTCC_CLKSEL(n) (((n) << 12) & RTCC_CLKSEL_MASK)
use FIELD_GET
> +#define RTCC_DIV512EN BIT(11)
> +#define RTCC_DIV32EN BIT(10)
> +
> +/* RTCS fields */
> +#define RTCS_RTCF BIT(29)
> +#define RTCS_INV_RTC BIT(18)
> +#define RTCS_APIF BIT(13)
> +
> +#define RTCCNT_MAX_VAL GENMASK(31, 0)
> +#define RTC_SYNCH_TIMEOUT (100 * USEC_PER_MSEC)
> +
> +#define RTC_CLK_MUX_SIZE 4
> +
> +/*
> + * S32G2 and S32G3 SoCs have RTC clock source 1 reserved and
> + * should not be used.
> + */
> +#define RTC_QUIRK_SRC1_RESERVED BIT(2)
> +
> +enum {
> + RTC_CLK_SRC0,
> + RTC_CLK_SRC1,
> + RTC_CLK_SRC2,
> + RTC_CLK_SRC3
> +};
> +
> +enum {
> + DIV1 = 1,
> + DIV32 = 32,
> + DIV512 = 512,
> + DIV512_32 = 16384
> +};
> +
> +static const char *rtc_clk_src[RTC_CLK_MUX_SIZE] = {
> + "source0",
> + "source1",
> + "source2",
> + "source3"
> +};
> +
> +struct rtc_time_base {
> + s64 sec;
> + u64 cycles;
> + struct rtc_time tm;
> +};
> +
> +struct rtc_priv {
> + struct rtc_device *rdev;
> + void __iomem *rtc_base;
> + struct clk *ipg;
> + struct clk *clk_src;
> + const struct rtc_soc_data *rtc_data;
> + struct rtc_time_base base;
> + u64 rtc_hz;
> + int dt_irq_id;
> + int clk_src_idx;
> +};
> +
> +struct rtc_soc_data {
> + u32 clk_div;
> + u32 quirks;
> +};
> +
> +static const struct rtc_soc_data rtc_s32g2_data = {
> + .clk_div = DIV512,
> + .quirks = RTC_QUIRK_SRC1_RESERVED,
> +};
> +
> +static int is_src1_reserved(struct rtc_priv *priv)
> +{
> + return priv->rtc_data->quirks & RTC_QUIRK_SRC1_RESERVED;
> +}
> +
> +static u64 cycles_to_sec(u64 hz, u64 cycles)
> +{
> + return div_u64(cycles, hz);
> +}
> +
> +/**
> + * Convert a number of seconds to a value suitable for RTCVAL in our clock's
> + * current configuration.
> + * @rtcval: The value to go into RTCVAL[RTCVAL]
> + * Returns: 0 for success, -EINVAL if @seconds push the counter past the
> + * 32bit register range
> + */
> +static int sec_to_rtcval(const struct rtc_priv *priv,
> + unsigned long seconds, u32 *rtcval)
> +{
> + u32 delta_cnt;
> +
> + if (!seconds || seconds > cycles_to_sec(priv->rtc_hz, RTCCNT_MAX_VAL))
> + return -EINVAL;
> +
> + /*
> + * RTCCNT is read-only; we must return a value relative to the
> + * current value of the counter (and hope we don't linger around
> + * too much before we get to enable the interrupt)
> + */
> + delta_cnt = seconds * priv->rtc_hz;
> + *rtcval = delta_cnt + ioread32(priv->rtc_base + RTCCNT_OFFSET);
> +
> + return 0;
> +}
> +
> +static irqreturn_t s32g_rtc_handler(int irq, void *dev)
> +{
> + struct rtc_priv *priv = platform_get_drvdata(dev);
> + u32 status;
> +
> + status = ioread32(priv->rtc_base + RTCS_OFFSET);
> +
> + if (status & RTCS_RTCF) {
> + iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
> + rtc_update_irq(priv->rdev, 1, RTC_AF);
> + }
> +
> + if (status & RTCS_APIF)
> + rtc_update_irq(priv->rdev, 1, RTC_PF);
> +
> + iowrite32(status, priv->rtc_base + RTCS_OFFSET);
Generally, iowrite32(status, priv->rtc_base + RTCS_OFFSET); should
follow status = ioread32(priv->rtc_base + RTCS_OFFSET); Any specfic reason
put it after rtc_update_irq()?
> +
> + return IRQ_HANDLED;
> +}
> +
> +static s64 s32g_rtc_get_time_or_alrm(struct rtc_priv *priv,
> + u32 offset)
> +{
> + u32 counter;
> +
> + counter = ioread32(priv->rtc_base + offset);
> +
> + if (counter < priv->base.cycles)
> + return -EINVAL;
> +
> + counter -= priv->base.cycles;
> +
> + return priv->base.sec + cycles_to_sec(priv->rtc_hz, counter);
> +}
> +
> +static int s32g_rtc_read_time(struct device *dev,
> + struct rtc_time *tm)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + s64 sec;
> +
> + sec = s32g_rtc_get_time_or_alrm(priv, RTCCNT_OFFSET);
> + if (sec < 0)
> + return -EINVAL;
> +
> + rtc_time64_to_tm(sec, tm);
> +
> + return 0;
> +}
> +
> +static int s32g_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + u32 rtcc, rtccnt, rtcval;
> + s64 sec;
> +
> + sec = s32g_rtc_get_time_or_alrm(priv, RTCVAL_OFFSET);
> + if (sec < 0)
> + return -EINVAL;
> +
> + rtc_time64_to_tm(sec, &alrm->time);
> +
> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
> + alrm->enabled = sec && (rtcc & RTCC_RTCIE);
> +
> + alrm->pending = 0;
> + if (alrm->enabled) {
> + rtccnt = ioread32(priv->rtc_base + RTCCNT_OFFSET);
> + rtcval = ioread32(priv->rtc_base + RTCVAL_OFFSET);
> +
> + if (rtccnt < rtcval)
> + alrm->pending = 1;
> + }
> +
> + return 0;
> +}
> +
> +static int s32g_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + u32 rtcc;
> +
> + if (!priv->dt_irq_id)
> + return -EIO;
> +
> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
> + if (enabled)
> + rtcc |= RTCC_RTCIE;
> +
> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
> +
> + return 0;
> +}
> +
> +static int s32g_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + struct rtc_time time_crt;
> + long long t_crt, t_alrm;
> + u32 rtcval, rtcs;
> + int ret = 0;
> +
> + iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
> +
> + t_alrm = rtc_tm_to_time64(&alrm->time);
> +
> + /*
> + * Assuming the alarm is being set relative to the same time
> + * returned by our s32g_rtc_read_time callback
> + */
> + ret = s32g_rtc_read_time(dev, &time_crt);
> + if (ret)
> + return ret;
> +
> + t_crt = rtc_tm_to_time64(&time_crt);
> + ret = sec_to_rtcval(priv, t_alrm - t_crt, &rtcval);
> + if (ret) {
> + dev_warn(dev, "Alarm is set too far in the future\n");
> + return -ERANGE;
> + }
> +
> + ret = read_poll_timeout(ioread32, rtcs, !(rtcs & RTCS_INV_RTC),
> + 0, RTC_SYNCH_TIMEOUT, false, priv->rtc_base + RTCS_OFFSET);
> + if (ret)
> + return ret;
> +
> + iowrite32(rtcval, priv->rtc_base + RTCVAL_OFFSET);
> +
> + return 0;
> +}
> +
> +static int s32g_rtc_set_time(struct device *dev,
> + struct rtc_time *time)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> +
> + priv->base.cycles = ioread32(priv->rtc_base + RTCCNT_OFFSET);
> + priv->base.sec = rtc_tm_to_time64(time);
> +
> + return 0;
> +}
> +
> +/*
> + * Disable the 32-bit free running counter.
> + * This allows Clock Source and Divisors selection
> + * to be performed without causing synchronization issues.
> + */
> +static void s32g_rtc_disable(struct rtc_priv *priv)
> +{
> + u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
> +
> + rtcc &= ~RTCC_CNTEN;
> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
> +}
> +
> +static void s32g_rtc_enable(struct rtc_priv *priv)
> +{
> + u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
> +
> + rtcc |= RTCC_CNTEN;
> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
> +}
> +
> +static int rtc_clk_src_setup(struct rtc_priv *priv)
> +{
> + u32 rtcc = 0;
> +
> + switch (priv->clk_src_idx) {
> + case RTC_CLK_SRC0:
> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC0);
> + break;
> + case RTC_CLK_SRC1:
> + if (is_src1_reserved(priv))
> + return -EOPNOTSUPP;
> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC1);
> + break;
> + case RTC_CLK_SRC2:
> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC2);
> + break;
> + case RTC_CLK_SRC3:
> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC3);
> + break;
> + default:
> + return -EINVAL;
> + }
Suggest a reserve_mask at platform data
if (priv->reserve_mask & (1 << priv->clk_src_idx));
return -EOPNOTSUPP;
rtcc = RTCC_CLKSEL(priv->clk_src_idx);
> +
> + switch (priv->rtc_data->clk_div) {
> + case DIV512_32:
> + rtcc |= RTCC_DIV512EN;
> + rtcc |= RTCC_DIV32EN;
> + break;
> + case DIV512:
> + rtcc |= RTCC_DIV512EN;
> + break;
> + case DIV32:
> + rtcc |= RTCC_DIV32EN;
> + break;
> + case DIV1:
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + rtcc |= RTCC_RTCIE;
> + /*
> + * Make sure the CNTEN is 0 before we configure
> + * the clock source and dividers.
> + */
> + s32g_rtc_disable(priv);
> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
> + s32g_rtc_enable(priv);
> +
> + return 0;
> +}
> +
> +static const struct rtc_class_ops rtc_ops = {
> + .read_time = s32g_rtc_read_time,
> + .set_time = s32g_rtc_set_time,
> + .read_alarm = s32g_rtc_read_alarm,
> + .set_alarm = s32g_rtc_set_alarm,
> + .alarm_irq_enable = s32g_rtc_alarm_irq_enable,
> +};
> +
> +static int rtc_clk_dts_setup(struct rtc_priv *priv,
> + struct device *dev)
> +{
> + int i;
> +
> + priv->ipg = devm_clk_get_enabled(dev, "ipg");
> + if (IS_ERR(priv->ipg))
> + return dev_err_probe(dev, PTR_ERR(priv->ipg),
> + "Failed to get 'ipg' clock\n");
> +
> + for (i = 0; i < RTC_CLK_MUX_SIZE; i++) {
> + priv->clk_src = devm_clk_get_enabled(dev, rtc_clk_src[i]);
> + if (!IS_ERR(priv->clk_src)) {
> + priv->clk_src_idx = i;
> + break;
> + }
> + }
> +
> + if (IS_ERR(priv->clk_src))
> + return dev_err_probe(dev, PTR_ERR(priv->clk_src),
> + "Failed to get rtc module clock source\n");
> +
> + return 0;
> +}
> +
> +static int s32g_rtc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct rtc_priv *priv;
> + int ret = 0;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->rtc_data = of_device_get_match_data(dev);
> + if (!priv->rtc_data)
> + return -ENODEV;
> +
> + priv->rtc_base = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(priv->rtc_base))
> + return PTR_ERR(priv->rtc_base);
> +
> + device_init_wakeup(dev, true);
> +
> + ret = rtc_clk_dts_setup(priv, dev);
> + if (ret)
> + return ret;
> +
> + priv->rdev = devm_rtc_allocate_device(dev);
> + if (IS_ERR(priv->rdev))
> + return PTR_ERR(priv->rdev);
> +
> + ret = rtc_clk_src_setup(priv);
> + if (ret)
> + return ret;
> +
> + priv->rtc_hz = clk_get_rate(priv->clk_src);
> + if (!priv->rtc_hz)
> + return dev_err_probe(dev, -EINVAL, "Failed to get RTC frequency\n");
> +
> + priv->rtc_hz /= priv->rtc_data->clk_div;
> +
> + platform_set_drvdata(pdev, priv);
> + priv->rdev->ops = &rtc_ops;
> +
> + priv->dt_irq_id = platform_get_irq(pdev, 0);
'dt_irq_id' is too long. 'irq' should be enough.
> + if (priv->dt_irq_id < 0)
> + return priv->dt_irq_id;
> +
> + ret = devm_request_irq(dev, priv->dt_irq_id,
> + s32g_rtc_handler, 0, dev_name(dev), pdev);
> + if (ret) {
> + dev_err(dev, "Request interrupt %d failed, error: %d\n",
> + priv->dt_irq_id, ret);
> + goto disable_rtc;
Already enable rtc at rtc_clk_src_setup(), you direct return fail after
check clk_get_rate();
if you want to disable_rtc, you use devm_add_action_or_reset() to add
a disable action callback and return dev_err_probe() here directly.
Frank
> + }
> +
> + ret = devm_rtc_register_device(priv->rdev);
> + if (ret)
> + goto disable_rtc;
> +
> + return 0;
> +
> +disable_rtc:
> + s32g_rtc_disable(priv);
> + return ret;
> +}
> +
> +static void enable_api_irq(struct device *dev, unsigned int enabled)
s32_enable_api_irq()?
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + u32 api_irq = RTCC_APIEN | RTCC_APIIE;
> + u32 rtcc;
> +
> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
> + if (enabled)
> + rtcc |= api_irq;
> + else
> + rtcc &= ~api_irq;
> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
> +}
> +
> +static int s32g_rtc_suspend(struct device *dev)
> +{
> + struct rtc_priv *init_priv = dev_get_drvdata(dev);
> + struct rtc_priv priv;
> + long long base_sec;
> + u32 rtcval, rtccnt;
> + int ret = 0;
> + u32 sec;
> +
> + if (!device_may_wakeup(dev))
> + return 0;
> +
> + /* Save last known timestamp */
> + ret = s32g_rtc_read_time(dev, &init_priv->base.tm);
> + if (ret)
> + return ret;
> +
> + /*
> + * Use a local copy of the RTC control block to
> + * avoid restoring it on resume path.
> + */
> + memcpy(&priv, init_priv, sizeof(priv));
> +
> + rtccnt = ioread32(init_priv->rtc_base + RTCCNT_OFFSET);
> + rtcval = ioread32(init_priv->rtc_base + RTCVAL_OFFSET);
> + sec = cycles_to_sec(init_priv->rtc_hz, rtcval - rtccnt);
> +
> + /* Adjust for the number of seconds we'll be asleep */
> + base_sec = rtc_tm_to_time64(&init_priv->base.tm);
> + base_sec += sec;
> + rtc_time64_to_tm(base_sec, &init_priv->base.tm);
> +
> + /* Reset RTC to prevent overflow.
> + * RTCCNT (RTC Counter) cannot be individually reset
> + * since it is RO (read-only).
> + */
what's happen if overflow happen? I suppose it should go back to 0 and
continue increase?
> + s32g_rtc_disable(&priv);
> + s32g_rtc_enable(&priv);
> +
> + ret = sec_to_rtcval(&priv, sec, &rtcval);
> + if (ret) {
> + dev_warn(dev, "Alarm is too far in the future\n");
> + return -ERANGE;
> + }
> +
> + enable_api_irq(dev, 1);
> + iowrite32(rtcval, priv.rtc_base + APIVAL_OFFSET);
> + iowrite32(0, priv.rtc_base + RTCVAL_OFFSET);
> +
> + return ret;
> +}
> +
> +static int s32g_rtc_resume(struct device *dev)
> +{
> + struct rtc_priv *priv = dev_get_drvdata(dev);
> + int ret;
> +
> + if (!device_may_wakeup(dev))
> + return 0;
> +
> + /* Disable wake-up interrupts */
> + enable_api_irq(dev, 0);
> +
> + ret = rtc_clk_src_setup(priv);
> + if (ret)
> + return ret;
> +
> + /*
> + * Now RTCCNT has just been reset, and is out of sync with priv->base;
> + * reapply the saved time settings.
> + */
> + return s32g_rtc_set_time(dev, &priv->base.tm);
> +}
> +
> +static const struct of_device_id rtc_dt_ids[] = {
> + { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data},
> + { /* sentinel */ },
> +};
> +
> +static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops,
> + s32g_rtc_suspend, s32g_rtc_resume);
> +
> +static struct platform_driver s32g_rtc_driver = {
> + .driver = {
> + .name = "s32g-rtc",
> + .pm = pm_sleep_ptr(&s32g_rtc_pm_ops),
> + .of_match_table = rtc_dt_ids,
> + },
> + .probe = s32g_rtc_probe,
> +};
> +module_platform_driver(s32g_rtc_driver);
> +
> +MODULE_AUTHOR("NXP");
> +MODULE_DESCRIPTION("NXP RTC driver for S32G2/S32G3");
> +MODULE_LICENSE("GPL");
> --
> 2.45.2
>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
@ 2024-11-26 19:08 ` Krzysztof Kozlowski
2024-11-27 12:01 ` Ciprian Marian Costea
2024-11-27 14:43 ` Rob Herring
1 sibling, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2024-11-26 19:08 UTC (permalink / raw)
To: Ciprian Costea, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Bogdan-Gabriel Roman, Ghennadi Procopciuc
On 26/11/2024 12:49, Ciprian Costea wrote:
> +
> +title: NXP S32G2/S32G3 Real Time Clock (RTC)
> +
> +maintainers:
> + - Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
> + - Ciprian Marian Costea <ciprianmarian.costea@nxp.com>
> +
> +description:
> + RTC hardware module present on S32G2/S32G3 SoCs is used as a wakeup source.
> + It is not kept alive during system reset and it is not battery-powered.
Does this mean that this is not a standard RTC thus standard RTC schema
does not apply?
> +
> +properties:
> + compatible:
> + oneOf:
> + - enum:
> + - nxp,s32g2-rtc
> + - items:
> + - const: nxp,s32g3-rtc
> + - const: nxp,s32g2-rtc
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: ipg clock drives the access to the RTC iomapped registers
> + - description: Clock source for the RTC module. Can be selected between
> + 4 different clock sources using an integrated hardware mux.
> + On S32G2/S32G3 SoCs, 'source0' is the SIRC clock (~32KHz) and it is
> + available during standby and runtime. 'source1' is reserved and cannot
I am not sure what are the benefits of allowing to choose a clock which
cannot be used. I think source1 should be dropped.
> + be used. 'source2' is the FIRC clock and it is only available during
> + runtime providing a better resolution (~48MHz). 'source3' is an external
> + RTC clock source which can be additionally added in hardware.
> +
> + clock-names:
> + items:
> + - const: ipg
> + - enum: [ source0, source1, source2, source3 ]
> +
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
2024-11-26 19:08 ` Krzysztof Kozlowski
@ 2024-11-27 12:01 ` Ciprian Marian Costea
0 siblings, 0 replies; 16+ messages in thread
From: Ciprian Marian Costea @ 2024-11-27 12:01 UTC (permalink / raw)
To: Krzysztof Kozlowski, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Catalin Marinas, Will Deacon
Cc: linux-rtc, devicetree, linux-kernel, linux-arm-kernel, imx,
NXP S32 Linux, Christophe Lizzi, Alberto Ruiz, Enric Balletbo,
Bogdan-Gabriel Roman, Ghennadi Procopciuc
On 11/26/2024 9:08 PM, Krzysztof Kozlowski wrote:
> On 26/11/2024 12:49, Ciprian Costea wrote:
>> +
>> +title: NXP S32G2/S32G3 Real Time Clock (RTC)
>> +
>> +maintainers:
>> + - Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
>> + - Ciprian Marian Costea <ciprianmarian.costea@nxp.com>
>> +
>> +description:
>> + RTC hardware module present on S32G2/S32G3 SoCs is used as a wakeup source.
>> + It is not kept alive during system reset and it is not battery-powered.
>
> Does this mean that this is not a standard RTC thus standard RTC schema
> does not apply?
>
Hello Krzysztof,
I would say the standard RTC schema does apply but indeed you bring up a
valid point in the fact that I forgot to reference 'rtc.yaml' schema.
I will fix this in V6, by adding:
allOf:
- $ref: rtc.yaml#
>> +
>> +properties:
>> + compatible:
>> + oneOf:
>> + - enum:
>> + - nxp,s32g2-rtc
>> + - items:
>> + - const: nxp,s32g3-rtc
>> + - const: nxp,s32g2-rtc
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + clocks:
>> + items:
>> + - description: ipg clock drives the access to the RTC iomapped registers
>> + - description: Clock source for the RTC module. Can be selected between
>> + 4 different clock sources using an integrated hardware mux.
>> + On S32G2/S32G3 SoCs, 'source0' is the SIRC clock (~32KHz) and it is
>> + available during standby and runtime. 'source1' is reserved and cannot
>
> I am not sure what are the benefits of allowing to choose a clock which
> cannot be used. I think source1 should be dropped.
>
The current RTC support targets S32G2/S32G3 SoCs where 'source1' clock
source cannot be used. The reasoning for allowing to choose it is that
on future SoCs from S32 family the same RTC module may be integrated and
'source1' may become available.
>> + be used. 'source2' is the FIRC clock and it is only available during
>> + runtime providing a better resolution (~48MHz). 'source3' is an external
>> + RTC clock source which can be additionally added in hardware.
>> +
>> + clock-names:
>> + items:
>> + - const: ipg
>> + - enum: [ source0, source1, source2, source3 ]
>> +
> Best regards,
> Krzysztof
Best Regards,
Ciprian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
2024-11-26 19:08 ` Krzysztof Kozlowski
@ 2024-11-27 14:43 ` Rob Herring
2024-11-27 14:49 ` Ciprian Marian Costea
1 sibling, 1 reply; 16+ messages in thread
From: Rob Herring @ 2024-11-27 14:43 UTC (permalink / raw)
To: Ciprian Costea
Cc: Alexandre Belloni, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan-Gabriel Roman,
Ghennadi Procopciuc
On Tue, Nov 26, 2024 at 01:49:37PM +0200, Ciprian Costea wrote:
> From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>
> RTC tracks clock time during system suspend and it is used as a wakeup
> source on S32G2/S32G3 architecture.
>
> RTC from S32G2/S32G3 is not battery-powered and it is not kept alive
> during system reset.
>
> Co-developed-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
> Signed-off-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
> Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
> Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
> Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
> ---
> .../devicetree/bindings/rtc/nxp,s32g-rtc.yaml | 69 +++++++++++++++++++
> 1 file changed, 69 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
>
> diff --git a/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
> new file mode 100644
> index 000000000000..89414a0d926c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
> @@ -0,0 +1,69 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/rtc/nxp,s32g-rtc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: NXP S32G2/S32G3 Real Time Clock (RTC)
> +
> +maintainers:
> + - Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
> + - Ciprian Marian Costea <ciprianmarian.costea@nxp.com>
> +
> +description:
> + RTC hardware module present on S32G2/S32G3 SoCs is used as a wakeup source.
> + It is not kept alive during system reset and it is not battery-powered.
> +
> +properties:
> + compatible:
> + oneOf:
> + - enum:
> + - nxp,s32g2-rtc
> + - items:
> + - const: nxp,s32g3-rtc
> + - const: nxp,s32g2-rtc
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: ipg clock drives the access to the RTC iomapped registers
> + - description: Clock source for the RTC module. Can be selected between
> + 4 different clock sources using an integrated hardware mux.
> + On S32G2/S32G3 SoCs, 'source0' is the SIRC clock (~32KHz) and it is
> + available during standby and runtime. 'source1' is reserved and cannot
> + be used. 'source2' is the FIRC clock and it is only available during
> + runtime providing a better resolution (~48MHz). 'source3' is an external
> + RTC clock source which can be additionally added in hardware.
Is switching the clock source at run-time possible? For example, use the
48MHz at runtime and switch to 32kHz or external clock during suspend.
If so, you need to list all possible clock sources. Really, you probably
should no matter what as you need to describe what's in the h/w, not
configuration (though configuration is okay when it's fixed for the
device).
> +
> + clock-names:
> + items:
> + - const: ipg
> + - enum: [ source0, source1, source2, source3 ]
You can do:
maxItems: 5
items:
- const: ipg
additionalItems:
pattern: '^source[0-4]$'
Though I will have to relax constraints on 'additionalItems' to avoid a
warning.
Rob
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP S32G2/S32G3 SoCs
2024-11-27 14:43 ` Rob Herring
@ 2024-11-27 14:49 ` Ciprian Marian Costea
0 siblings, 0 replies; 16+ messages in thread
From: Ciprian Marian Costea @ 2024-11-27 14:49 UTC (permalink / raw)
To: Rob Herring
Cc: Alexandre Belloni, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan-Gabriel Roman,
Ghennadi Procopciuc
On 11/27/2024 4:43 PM, Rob Herring wrote:
> On Tue, Nov 26, 2024 at 01:49:37PM +0200, Ciprian Costea wrote:
>> From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>>
>> RTC tracks clock time during system suspend and it is used as a wakeup
>> source on S32G2/S32G3 architecture.
>>
>> RTC from S32G2/S32G3 is not battery-powered and it is not kept alive
>> during system reset.
>>
>> Co-developed-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
>> Signed-off-by: Bogdan-Gabriel Roman <bogdan-gabriel.roman@nxp.com>
>> Co-developed-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
>> Signed-off-by: Ghennadi Procopciuc <ghennadi.procopciuc@nxp.com>
>> Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>> ---
>> .../devicetree/bindings/rtc/nxp,s32g-rtc.yaml | 69 +++++++++++++++++++
>> 1 file changed, 69 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
>> new file mode 100644
>> index 000000000000..89414a0d926c
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/rtc/nxp,s32g-rtc.yaml
>> @@ -0,0 +1,69 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/rtc/nxp,s32g-rtc.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: NXP S32G2/S32G3 Real Time Clock (RTC)
>> +
>> +maintainers:
>> + - Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
>> + - Ciprian Marian Costea <ciprianmarian.costea@nxp.com>
>> +
>> +description:
>> + RTC hardware module present on S32G2/S32G3 SoCs is used as a wakeup source.
>> + It is not kept alive during system reset and it is not battery-powered.
>> +
>> +properties:
>> + compatible:
>> + oneOf:
>> + - enum:
>> + - nxp,s32g2-rtc
>> + - items:
>> + - const: nxp,s32g3-rtc
>> + - const: nxp,s32g2-rtc
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + interrupts:
>> + maxItems: 1
>> +
>> + clocks:
>> + items:
>> + - description: ipg clock drives the access to the RTC iomapped registers
>> + - description: Clock source for the RTC module. Can be selected between
>> + 4 different clock sources using an integrated hardware mux.
>> + On S32G2/S32G3 SoCs, 'source0' is the SIRC clock (~32KHz) and it is
>> + available during standby and runtime. 'source1' is reserved and cannot
>> + be used. 'source2' is the FIRC clock and it is only available during
>> + runtime providing a better resolution (~48MHz). 'source3' is an external
>> + RTC clock source which can be additionally added in hardware.
>
> Is switching the clock source at run-time possible? For example, use the
> 48MHz at runtime and switch to 32kHz or external clock during suspend.
> If so, you need to list all possible clock sources. Really, you probably
> should no matter what as you need to describe what's in the h/w, not
> configuration (though configuration is okay when it's fixed for the
> device).
>
Hello Rob,
Thank you for your review.
In this latest V5 of this patchset, clock source switching at
run-time/suspend support has been dropped (as agreed during the review
process). Therefore a static clock source configuration is used for a
specific S32G SoC which uses the RTC device.
>> +
>> + clock-names:
>> + items:
>> + - const: ipg
>> + - enum: [ source0, source1, source2, source3 ]
>
> You can do:
>
> maxItems: 5
> items:
> - const: ipg
> additionalItems:
> pattern: '^source[0-4]$'
>
> Though I will have to relax constraints on 'additionalItems' to avoid a
> warning.
>
> Rob
Thanks for your suggestion. I will consider it for V6.
Best Regards,
Ciprian
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 18:05 ` Frank Li
@ 2024-11-27 15:07 ` Ciprian Marian Costea
2024-11-27 15:32 ` Alexandre Belloni
0 siblings, 1 reply; 16+ messages in thread
From: Ciprian Marian Costea @ 2024-11-27 15:07 UTC (permalink / raw)
To: Frank Li
Cc: Alexandre Belloni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan Hamciuc, Ghennadi Procopciuc
On 11/26/2024 8:05 PM, Frank Li wrote:
> On Tue, Nov 26, 2024 at 01:49:38PM +0200, Ciprian Costea wrote:
>> From: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>>
>> Add a RTC driver for NXP S32G2/S32G3 SoCs.
>>
>> RTC tracks clock time during system suspend. It can be a wakeup source
>> for the S32G2/S32G3 SoC based boards.
>>
>> The RTC module from S32G2/S32G3 is not battery-powered and it is not kept
>> alive during system reset.
>>
>> Co-developed-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
>> Signed-off-by: Bogdan Hamciuc <bogdan.hamciuc@nxp.com>
>> Co-developed-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
>> Signed-off-by: Ghennadi Procopciuc <Ghennadi.Procopciuc@nxp.com>
>> Signed-off-by: Ciprian Marian Costea <ciprianmarian.costea@oss.nxp.com>
>> ---
>> drivers/rtc/Kconfig | 11 +
>> drivers/rtc/Makefile | 1 +
>> drivers/rtc/rtc-s32g.c | 554 +++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 566 insertions(+)
>> create mode 100644 drivers/rtc/rtc-s32g.c
>>
>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>> index e87c3d74565c..18fc3577f6cd 100644
>> --- a/drivers/rtc/Kconfig
>> +++ b/drivers/rtc/Kconfig
>> @@ -2054,4 +2054,15 @@ config RTC_DRV_SSD202D
>> This driver can also be built as a module, if so, the module
>> will be called "rtc-ssd20xd".
>>
>> +config RTC_DRV_S32G
>> + tristate "RTC driver for S32G2/S32G3 SoCs"
>> + depends on ARCH_S32 || COMPILE_TEST
>> + depends on COMMON_CLK
>> + help
>> + Say yes to enable RTC driver for platforms based on the
>> + S32G2/S32G3 SoC family.
>> +
>> + This RTC module can be used as a wakeup source.
>> + Please note that it is not battery-powered.
>> +
>> endif # RTC_CLASS
>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>> index 8ee79cb18322..a63d010a753c 100644
>> --- a/drivers/rtc/Makefile
>> +++ b/drivers/rtc/Makefile
>> @@ -158,6 +158,7 @@ obj-$(CONFIG_RTC_DRV_RX8025) += rtc-rx8025.o
>> obj-$(CONFIG_RTC_DRV_RX8111) += rtc-rx8111.o
>> obj-$(CONFIG_RTC_DRV_RX8581) += rtc-rx8581.o
>> obj-$(CONFIG_RTC_DRV_RZN1) += rtc-rzn1.o
>> +obj-$(CONFIG_RTC_DRV_S32G) += rtc-s32g.o
>> obj-$(CONFIG_RTC_DRV_S35390A) += rtc-s35390a.o
>> obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
>> obj-$(CONFIG_RTC_DRV_S5M) += rtc-s5m.o
>> diff --git a/drivers/rtc/rtc-s32g.c b/drivers/rtc/rtc-s32g.c
>> new file mode 100644
>> index 000000000000..ad78423783da
>> --- /dev/null
>> +++ b/drivers/rtc/rtc-s32g.c
>> @@ -0,0 +1,554 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright 2024 NXP
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/clk-provider.h>
>> +#include <linux/err.h>
>> +#include <linux/iopoll.h>
>> +#include <linux/math64.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/rtc.h>
>> +
>> +#define RTCC_OFFSET 0x4ul
>> +#define RTCS_OFFSET 0x8ul
>> +#define RTCCNT_OFFSET 0xCul
>> +#define APIVAL_OFFSET 0x10ul
>> +#define RTCVAL_OFFSET 0x14ul
>> +
>> +/* RTCC fields */
>> +#define RTCC_CNTEN BIT(31)
>> +#define RTCC_RTCIE_SHIFT 30
>> +#define RTCC_RTCIE BIT(RTCC_RTCIE_SHIFT)
>
> Only use RTCC_RTCIE_SHIFT here, just put 30 and remove
> RTCC_RTCIE_SHIFT.
>
Hello Frank,
Thanks for your review.
I will remove 'RTCC_RTCIE_SHIFT' in V6.
>> +#define RTCC_APIEN BIT(15)
>> +#define RTCC_APIIE BIT(14)
>> +#define RTCC_CLKSEL_OFFSET 12
>
> No used
>
Thanks. I will remove it in V6.
>> +#define RTCC_CLKSEL_MASK GENMASK(13, 12)
>> +#define RTCC_CLKSEL(n) (((n) << 12) & RTCC_CLKSEL_MASK)
>
> use FIELD_GET
>
Actually, I use 'RTCC_CLKSEL' in order to set the 'CLKSEL' value in the
'RTCC' register.
Therefore, I believe I can use 'FIELD_PREP' instead.
I will consider it for V6.
>> +#define RTCC_DIV512EN BIT(11)
>> +#define RTCC_DIV32EN BIT(10)
>> +
>> +/* RTCS fields */
>> +#define RTCS_RTCF BIT(29)
>> +#define RTCS_INV_RTC BIT(18)
>> +#define RTCS_APIF BIT(13)
>> +
>> +#define RTCCNT_MAX_VAL GENMASK(31, 0)
>> +#define RTC_SYNCH_TIMEOUT (100 * USEC_PER_MSEC)
>> +
>> +#define RTC_CLK_MUX_SIZE 4
>> +
>> +/*
>> + * S32G2 and S32G3 SoCs have RTC clock source 1 reserved and
>> + * should not be used.
>> + */
>> +#define RTC_QUIRK_SRC1_RESERVED BIT(2)
>> +
>> +enum {
>> + RTC_CLK_SRC0,
>> + RTC_CLK_SRC1,
>> + RTC_CLK_SRC2,
>> + RTC_CLK_SRC3
>> +};
>> +
>> +enum {
>> + DIV1 = 1,
>> + DIV32 = 32,
>> + DIV512 = 512,
>> + DIV512_32 = 16384
>> +};
>> +
>> +static const char *rtc_clk_src[RTC_CLK_MUX_SIZE] = {
>> + "source0",
>> + "source1",
>> + "source2",
>> + "source3"
>> +};
>> +
>> +struct rtc_time_base {
>> + s64 sec;
>> + u64 cycles;
>> + struct rtc_time tm;
>> +};
>> +
>> +struct rtc_priv {
>> + struct rtc_device *rdev;
>> + void __iomem *rtc_base;
>> + struct clk *ipg;
>> + struct clk *clk_src;
>> + const struct rtc_soc_data *rtc_data;
>> + struct rtc_time_base base;
>> + u64 rtc_hz;
>> + int dt_irq_id;
>> + int clk_src_idx;
>> +};
>> +
>> +struct rtc_soc_data {
>> + u32 clk_div;
>> + u32 quirks;
>> +};
>> +
>> +static const struct rtc_soc_data rtc_s32g2_data = {
>> + .clk_div = DIV512,
>> + .quirks = RTC_QUIRK_SRC1_RESERVED,
>> +};
>> +
>> +static int is_src1_reserved(struct rtc_priv *priv)
>> +{
>> + return priv->rtc_data->quirks & RTC_QUIRK_SRC1_RESERVED;
>> +}
>> +
>> +static u64 cycles_to_sec(u64 hz, u64 cycles)
>> +{
>> + return div_u64(cycles, hz);
>> +}
>> +
>> +/**
>> + * Convert a number of seconds to a value suitable for RTCVAL in our clock's
>> + * current configuration.
>> + * @rtcval: The value to go into RTCVAL[RTCVAL]
>> + * Returns: 0 for success, -EINVAL if @seconds push the counter past the
>> + * 32bit register range
>> + */
>> +static int sec_to_rtcval(const struct rtc_priv *priv,
>> + unsigned long seconds, u32 *rtcval)
>> +{
>> + u32 delta_cnt;
>> +
>> + if (!seconds || seconds > cycles_to_sec(priv->rtc_hz, RTCCNT_MAX_VAL))
>> + return -EINVAL;
>> +
>> + /*
>> + * RTCCNT is read-only; we must return a value relative to the
>> + * current value of the counter (and hope we don't linger around
>> + * too much before we get to enable the interrupt)
>> + */
>> + delta_cnt = seconds * priv->rtc_hz;
>> + *rtcval = delta_cnt + ioread32(priv->rtc_base + RTCCNT_OFFSET);
>> +
>> + return 0;
>> +}
>> +
>> +static irqreturn_t s32g_rtc_handler(int irq, void *dev)
>> +{
>> + struct rtc_priv *priv = platform_get_drvdata(dev);
>> + u32 status;
>> +
>> + status = ioread32(priv->rtc_base + RTCS_OFFSET);
>> +
>> + if (status & RTCS_RTCF) {
>> + iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
>> + rtc_update_irq(priv->rdev, 1, RTC_AF);
>> + }
>> +
>> + if (status & RTCS_APIF)
>> + rtc_update_irq(priv->rdev, 1, RTC_PF);
>> +
>> + iowrite32(status, priv->rtc_base + RTCS_OFFSET);
>
> Generally, iowrite32(status, priv->rtc_base + RTCS_OFFSET); should
> follow status = ioread32(priv->rtc_base + RTCS_OFFSET); Any specfic reason
> put it after rtc_update_irq()?
>
Thanks for pointing this out. Indeed I will clear the interrupt flags
before 'rtc_update_irq' call in V6.
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static s64 s32g_rtc_get_time_or_alrm(struct rtc_priv *priv,
>> + u32 offset)
>> +{
>> + u32 counter;
>> +
>> + counter = ioread32(priv->rtc_base + offset);
>> +
>> + if (counter < priv->base.cycles)
>> + return -EINVAL;
>> +
>> + counter -= priv->base.cycles;
>> +
>> + return priv->base.sec + cycles_to_sec(priv->rtc_hz, counter);
>> +}
>> +
>> +static int s32g_rtc_read_time(struct device *dev,
>> + struct rtc_time *tm)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + s64 sec;
>> +
>> + sec = s32g_rtc_get_time_or_alrm(priv, RTCCNT_OFFSET);
>> + if (sec < 0)
>> + return -EINVAL;
>> +
>> + rtc_time64_to_tm(sec, tm);
>> +
>> + return 0;
>> +}
>> +
>> +static int s32g_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + u32 rtcc, rtccnt, rtcval;
>> + s64 sec;
>> +
>> + sec = s32g_rtc_get_time_or_alrm(priv, RTCVAL_OFFSET);
>> + if (sec < 0)
>> + return -EINVAL;
>> +
>> + rtc_time64_to_tm(sec, &alrm->time);
>> +
>> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
>> + alrm->enabled = sec && (rtcc & RTCC_RTCIE);
>> +
>> + alrm->pending = 0;
>> + if (alrm->enabled) {
>> + rtccnt = ioread32(priv->rtc_base + RTCCNT_OFFSET);
>> + rtcval = ioread32(priv->rtc_base + RTCVAL_OFFSET);
>> +
>> + if (rtccnt < rtcval)
>> + alrm->pending = 1;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int s32g_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + u32 rtcc;
>> +
>> + if (!priv->dt_irq_id)
>> + return -EIO;
>> +
>> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
>> + if (enabled)
>> + rtcc |= RTCC_RTCIE;
>> +
>> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
>> +
>> + return 0;
>> +}
>> +
>> +static int s32g_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + struct rtc_time time_crt;
>> + long long t_crt, t_alrm;
>> + u32 rtcval, rtcs;
>> + int ret = 0;
>> +
>> + iowrite32(0x0, priv->rtc_base + RTCVAL_OFFSET);
>> +
>> + t_alrm = rtc_tm_to_time64(&alrm->time);
>> +
>> + /*
>> + * Assuming the alarm is being set relative to the same time
>> + * returned by our s32g_rtc_read_time callback
>> + */
>> + ret = s32g_rtc_read_time(dev, &time_crt);
>> + if (ret)
>> + return ret;
>> +
>> + t_crt = rtc_tm_to_time64(&time_crt);
>> + ret = sec_to_rtcval(priv, t_alrm - t_crt, &rtcval);
>> + if (ret) {
>> + dev_warn(dev, "Alarm is set too far in the future\n");
>> + return -ERANGE;
>> + }
>> +
>> + ret = read_poll_timeout(ioread32, rtcs, !(rtcs & RTCS_INV_RTC),
>> + 0, RTC_SYNCH_TIMEOUT, false, priv->rtc_base + RTCS_OFFSET);
>> + if (ret)
>> + return ret;
>> +
>> + iowrite32(rtcval, priv->rtc_base + RTCVAL_OFFSET);
>> +
>> + return 0;
>> +}
>> +
>> +static int s32g_rtc_set_time(struct device *dev,
>> + struct rtc_time *time)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> +
>> + priv->base.cycles = ioread32(priv->rtc_base + RTCCNT_OFFSET);
>> + priv->base.sec = rtc_tm_to_time64(time);
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * Disable the 32-bit free running counter.
>> + * This allows Clock Source and Divisors selection
>> + * to be performed without causing synchronization issues.
>> + */
>> +static void s32g_rtc_disable(struct rtc_priv *priv)
>> +{
>> + u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
>> +
>> + rtcc &= ~RTCC_CNTEN;
>> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
>> +}
>> +
>> +static void s32g_rtc_enable(struct rtc_priv *priv)
>> +{
>> + u32 rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
>> +
>> + rtcc |= RTCC_CNTEN;
>> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
>> +}
>> +
>> +static int rtc_clk_src_setup(struct rtc_priv *priv)
>> +{
>> + u32 rtcc = 0;
>> +
>> + switch (priv->clk_src_idx) {
>> + case RTC_CLK_SRC0:
>> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC0);
>> + break;
>> + case RTC_CLK_SRC1:
>> + if (is_src1_reserved(priv))
>> + return -EOPNOTSUPP;
>> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC1);
>> + break;
>> + case RTC_CLK_SRC2:
>> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC2);
>> + break;
>> + case RTC_CLK_SRC3:
>> + rtcc |= RTCC_CLKSEL(RTC_CLK_SRC3);
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>
> Suggest a reserve_mask at platform data
>
> if (priv->reserve_mask & (1 << priv->clk_src_idx));
> return -EOPNOTSUPP;
>
> rtcc = RTCC_CLKSEL(priv->clk_src_idx);
>
Looks better. I will refactor accordingly in V6.
>
>
>> +
>> + switch (priv->rtc_data->clk_div) {
>> + case DIV512_32:
>> + rtcc |= RTCC_DIV512EN;
>> + rtcc |= RTCC_DIV32EN;
>> + break;
>> + case DIV512:
>> + rtcc |= RTCC_DIV512EN;
>> + break;
>> + case DIV32:
>> + rtcc |= RTCC_DIV32EN;
>> + break;
>> + case DIV1:
>> + break;
>> + default:
>> + return -EINVAL;
>> + }
>> +
>> + rtcc |= RTCC_RTCIE;
>> + /*
>> + * Make sure the CNTEN is 0 before we configure
>> + * the clock source and dividers.
>> + */
>> + s32g_rtc_disable(priv);
>> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
>> + s32g_rtc_enable(priv);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct rtc_class_ops rtc_ops = {
>> + .read_time = s32g_rtc_read_time,
>> + .set_time = s32g_rtc_set_time,
>> + .read_alarm = s32g_rtc_read_alarm,
>> + .set_alarm = s32g_rtc_set_alarm,
>> + .alarm_irq_enable = s32g_rtc_alarm_irq_enable,
>> +};
>> +
>> +static int rtc_clk_dts_setup(struct rtc_priv *priv,
>> + struct device *dev)
>> +{
>> + int i;
>> +
>> + priv->ipg = devm_clk_get_enabled(dev, "ipg");
>> + if (IS_ERR(priv->ipg))
>> + return dev_err_probe(dev, PTR_ERR(priv->ipg),
>> + "Failed to get 'ipg' clock\n");
>> +
>> + for (i = 0; i < RTC_CLK_MUX_SIZE; i++) {
>> + priv->clk_src = devm_clk_get_enabled(dev, rtc_clk_src[i]);
>> + if (!IS_ERR(priv->clk_src)) {
>> + priv->clk_src_idx = i;
>> + break;
>> + }
>> + }
>> +
>> + if (IS_ERR(priv->clk_src))
>> + return dev_err_probe(dev, PTR_ERR(priv->clk_src),
>> + "Failed to get rtc module clock source\n");
>> +
>> + return 0;
>> +}
>> +
>> +static int s32g_rtc_probe(struct platform_device *pdev)
>> +{
>> + struct device *dev = &pdev->dev;
>> + struct rtc_priv *priv;
>> + int ret = 0;
>> +
>> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> + if (!priv)
>> + return -ENOMEM;
>> +
>> + priv->rtc_data = of_device_get_match_data(dev);
>> + if (!priv->rtc_data)
>> + return -ENODEV;
>> +
>> + priv->rtc_base = devm_platform_ioremap_resource(pdev, 0);
>> + if (IS_ERR(priv->rtc_base))
>> + return PTR_ERR(priv->rtc_base);
>> +
>> + device_init_wakeup(dev, true);
>> +
>> + ret = rtc_clk_dts_setup(priv, dev);
>> + if (ret)
>> + return ret;
>> +
>> + priv->rdev = devm_rtc_allocate_device(dev);
>> + if (IS_ERR(priv->rdev))
>> + return PTR_ERR(priv->rdev);
>> +
>> + ret = rtc_clk_src_setup(priv);
>> + if (ret)
>> + return ret;
>> +
>> + priv->rtc_hz = clk_get_rate(priv->clk_src);
>> + if (!priv->rtc_hz)
>> + return dev_err_probe(dev, -EINVAL, "Failed to get RTC frequency\n");
>> +
>> + priv->rtc_hz /= priv->rtc_data->clk_div;
>> +
>> + platform_set_drvdata(pdev, priv);
>> + priv->rdev->ops = &rtc_ops;
>> +
>> + priv->dt_irq_id = platform_get_irq(pdev, 0);
>
> 'dt_irq_id' is too long. 'irq' should be enough.
>
Agree. I will rename in V6.
>> + if (priv->dt_irq_id < 0)
>> + return priv->dt_irq_id;
>> +
>> + ret = devm_request_irq(dev, priv->dt_irq_id,
>> + s32g_rtc_handler, 0, dev_name(dev), pdev);
>> + if (ret) {
>> + dev_err(dev, "Request interrupt %d failed, error: %d\n",
>> + priv->dt_irq_id, ret);
>> + goto disable_rtc;
>
>
> Already enable rtc at rtc_clk_src_setup(), you direct return fail after
> check clk_get_rate();
>
> if you want to disable_rtc, you use devm_add_action_or_reset() to add
> a disable action callback and return dev_err_probe() here directly.
>
> Frank
>
Thanks for pointing this out. I will use 'devm_add_action_or_reset' in V6.
>> + }
>> +
>> + ret = devm_rtc_register_device(priv->rdev);
>> + if (ret)
>> + goto disable_rtc;
>> +
>> + return 0;
>> +
>> +disable_rtc:
>> + s32g_rtc_disable(priv);
>> + return ret;
>> +}
>> +
>> +static void enable_api_irq(struct device *dev, unsigned int enabled)
>
> s32_enable_api_irq()?
>
I will rename this function in V6.
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + u32 api_irq = RTCC_APIEN | RTCC_APIIE;
>> + u32 rtcc;
>> +
>> + rtcc = ioread32(priv->rtc_base + RTCC_OFFSET);
>> + if (enabled)
>> + rtcc |= api_irq;
>> + else
>> + rtcc &= ~api_irq;
>> + iowrite32(rtcc, priv->rtc_base + RTCC_OFFSET);
>> +}
>> +
>> +static int s32g_rtc_suspend(struct device *dev)
>> +{
>> + struct rtc_priv *init_priv = dev_get_drvdata(dev);
>> + struct rtc_priv priv;
>> + long long base_sec;
>> + u32 rtcval, rtccnt;
>> + int ret = 0;
>> + u32 sec;
>> +
>> + if (!device_may_wakeup(dev))
>> + return 0;
>> +
>> + /* Save last known timestamp */
>> + ret = s32g_rtc_read_time(dev, &init_priv->base.tm);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * Use a local copy of the RTC control block to
>> + * avoid restoring it on resume path.
>> + */
>> + memcpy(&priv, init_priv, sizeof(priv));
>> +
>> + rtccnt = ioread32(init_priv->rtc_base + RTCCNT_OFFSET);
>> + rtcval = ioread32(init_priv->rtc_base + RTCVAL_OFFSET);
>> + sec = cycles_to_sec(init_priv->rtc_hz, rtcval - rtccnt);
>> +
>> + /* Adjust for the number of seconds we'll be asleep */
>> + base_sec = rtc_tm_to_time64(&init_priv->base.tm);
>> + base_sec += sec;
>> + rtc_time64_to_tm(base_sec, &init_priv->base.tm);
>> +
>> + /* Reset RTC to prevent overflow.
>> + * RTCCNT (RTC Counter) cannot be individually reset
>> + * since it is RO (read-only).
>> + */
>
> what's happen if overflow happen? I suppose it should go back to 0 and
> continue increase?
>
Indeed if overflow happens the 'RTCCNT' counter goes back to 0 and
continues to increase. The reason for resetting it here in 'suspend'
routine comes after dropping the rollover support (as agreed on V4 of
this patchset) to prevent an overflow during the standby state.
Best Regards,
Ciprian
>> + s32g_rtc_disable(&priv);
>> + s32g_rtc_enable(&priv);
>> +
>> + ret = sec_to_rtcval(&priv, sec, &rtcval);
>> + if (ret) {
>> + dev_warn(dev, "Alarm is too far in the future\n");
>> + return -ERANGE;
>> + }
>> +
>> + enable_api_irq(dev, 1);
>> + iowrite32(rtcval, priv.rtc_base + APIVAL_OFFSET);
>> + iowrite32(0, priv.rtc_base + RTCVAL_OFFSET);
>> +
>> + return ret;
>> +}
>> +
>> +static int s32g_rtc_resume(struct device *dev)
>> +{
>> + struct rtc_priv *priv = dev_get_drvdata(dev);
>> + int ret;
>> +
>> + if (!device_may_wakeup(dev))
>> + return 0;
>> +
>> + /* Disable wake-up interrupts */
>> + enable_api_irq(dev, 0);
>> +
>> + ret = rtc_clk_src_setup(priv);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * Now RTCCNT has just been reset, and is out of sync with priv->base;
>> + * reapply the saved time settings.
>> + */
>> + return s32g_rtc_set_time(dev, &priv->base.tm);
>> +}
>> +
>> +static const struct of_device_id rtc_dt_ids[] = {
>> + { .compatible = "nxp,s32g2-rtc", .data = &rtc_s32g2_data},
>> + { /* sentinel */ },
>> +};
>> +
>> +static DEFINE_SIMPLE_DEV_PM_OPS(s32g_rtc_pm_ops,
>> + s32g_rtc_suspend, s32g_rtc_resume);
>> +
>> +static struct platform_driver s32g_rtc_driver = {
>> + .driver = {
>> + .name = "s32g-rtc",
>> + .pm = pm_sleep_ptr(&s32g_rtc_pm_ops),
>> + .of_match_table = rtc_dt_ids,
>> + },
>> + .probe = s32g_rtc_probe,
>> +};
>> +module_platform_driver(s32g_rtc_driver);
>> +
>> +MODULE_AUTHOR("NXP");
>> +MODULE_DESCRIPTION("NXP RTC driver for S32G2/S32G3");
>> +MODULE_LICENSE("GPL");
>> --
>> 2.45.2
>>
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-27 15:07 ` Ciprian Marian Costea
@ 2024-11-27 15:32 ` Alexandre Belloni
2024-12-02 13:06 ` Ciprian Marian Costea
0 siblings, 1 reply; 16+ messages in thread
From: Alexandre Belloni @ 2024-11-27 15:32 UTC (permalink / raw)
To: Ciprian Marian Costea
Cc: Frank Li, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan Hamciuc, Ghennadi Procopciuc
On 27/11/2024 17:07:48+0200, Ciprian Marian Costea wrote:
> > > + if (priv->dt_irq_id < 0)
> > > + return priv->dt_irq_id;
> > > +
> > > + ret = devm_request_irq(dev, priv->dt_irq_id,
> > > + s32g_rtc_handler, 0, dev_name(dev), pdev);
> > > + if (ret) {
> > > + dev_err(dev, "Request interrupt %d failed, error: %d\n",
> > > + priv->dt_irq_id, ret);
> > > + goto disable_rtc;
> >
> >
> > Already enable rtc at rtc_clk_src_setup(), you direct return fail after
> > check clk_get_rate();
> >
> > if you want to disable_rtc, you use devm_add_action_or_reset() to add
> > a disable action callback and return dev_err_probe() here directly.
> >
> > Frank
> >
>
> Thanks for pointing this out. I will use 'devm_add_action_or_reset' in V6.
>
Won't this disable the RTC on driver unload which we already discussed
should not be done?
> > > + /* Reset RTC to prevent overflow.
> > > + * RTCCNT (RTC Counter) cannot be individually reset
> > > + * since it is RO (read-only).
> > > + */
> >
> > what's happen if overflow happen? I suppose it should go back to 0 and
> > continue increase?
> >
>
> Indeed if overflow happens the 'RTCCNT' counter goes back to 0 and continues
> to increase. The reason for resetting it here in 'suspend' routine comes
> after dropping the rollover support (as agreed on V4 of this patchset) to
> prevent an overflow during the standby state.
>
I don't think the overflow matters as the comparator should continue to work
properly after it happens so you always have the complete range to wait
for the alarm to happen.
--
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 11:49 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
2024-11-26 18:05 ` Frank Li
@ 2024-11-28 21:18 ` kernel test robot
1 sibling, 0 replies; 16+ messages in thread
From: kernel test robot @ 2024-11-28 21:18 UTC (permalink / raw)
To: Ciprian Costea, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Catalin Marinas, Will Deacon
Cc: oe-kbuild-all, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Ciprian Marian Costea,
Bogdan Hamciuc, Ghennadi Procopciuc
Hi Ciprian,
kernel test robot noticed the following build warnings:
[auto build test WARNING on robh/for-next]
[also build test WARNING on arm64/for-next/core linus/master v6.12]
[cannot apply to abelloni/rtc-next next-20241128]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ciprian-Costea/dt-bindings-rtc-add-schema-for-NXP-S32G2-S32G3-SoCs/20241128-100010
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20241126114940.421143-3-ciprianmarian.costea%40oss.nxp.com
patch subject: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
config: loongarch-allyesconfig (https://download.01.org/0day-ci/archive/20241129/202411290700.vbqI1pTY-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241129/202411290700.vbqI1pTY-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202411290700.vbqI1pTY-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> drivers/rtc/rtc-s32g.c:109: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst
* Convert a number of seconds to a value suitable for RTCVAL in our clock's
vim +109 drivers/rtc/rtc-s32g.c
107
108 /**
> 109 * Convert a number of seconds to a value suitable for RTCVAL in our clock's
110 * current configuration.
111 * @rtcval: The value to go into RTCVAL[RTCVAL]
112 * Returns: 0 for success, -EINVAL if @seconds push the counter past the
113 * 32bit register range
114 */
115 static int sec_to_rtcval(const struct rtc_priv *priv,
116 unsigned long seconds, u32 *rtcval)
117 {
118 u32 delta_cnt;
119
120 if (!seconds || seconds > cycles_to_sec(priv->rtc_hz, RTCCNT_MAX_VAL))
121 return -EINVAL;
122
123 /*
124 * RTCCNT is read-only; we must return a value relative to the
125 * current value of the counter (and hope we don't linger around
126 * too much before we get to enable the interrupt)
127 */
128 delta_cnt = seconds * priv->rtc_hz;
129 *rtcval = delta_cnt + ioread32(priv->rtc_base + RTCCNT_OFFSET);
130
131 return 0;
132 }
133
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-26 11:44 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
@ 2024-11-30 20:07 ` kernel test robot
0 siblings, 0 replies; 16+ messages in thread
From: kernel test robot @ 2024-11-30 20:07 UTC (permalink / raw)
To: Ciprian Costea, Alexandre Belloni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Catalin Marinas, Will Deacon
Cc: oe-kbuild-all, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Ciprian Marian Costea,
Bogdan Hamciuc, Ghennadi Procopciuc
Hi Ciprian,
kernel test robot noticed the following build errors:
[auto build test ERROR on robh/for-next]
[also build test ERROR on arm64/for-next/core linus/master v6.12]
[cannot apply to abelloni/rtc-next next-20241128]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Ciprian-Costea/rtc-s32g-add-NXP-S32G2-S32G3-SoC-support/20241128-095523
base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
patch link: https://lore.kernel.org/r/20241126114414.419469-4-ciprianmarian.costea%40oss.nxp.com
patch subject: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20241201/202412010328.kJoRexZK-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241201/202412010328.kJoRexZK-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202412010328.kJoRexZK-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: "__udivdi3" [drivers/rtc/rtc-s32g.ko] undefined!
Kconfig warnings: (for reference only)
WARNING: unmet direct dependencies detected for GET_FREE_REGION
Depends on [n]: SPARSEMEM [=n]
Selected by [m]:
- RESOURCE_KUNIT_TEST [=m] && RUNTIME_TESTING_MENU [=y] && KUNIT [=m]
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support
2024-11-27 15:32 ` Alexandre Belloni
@ 2024-12-02 13:06 ` Ciprian Marian Costea
0 siblings, 0 replies; 16+ messages in thread
From: Ciprian Marian Costea @ 2024-12-02 13:06 UTC (permalink / raw)
To: Alexandre Belloni
Cc: Frank Li, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Catalin Marinas, Will Deacon, linux-rtc, devicetree, linux-kernel,
linux-arm-kernel, imx, NXP S32 Linux, Christophe Lizzi,
Alberto Ruiz, Enric Balletbo, Bogdan Hamciuc, Ghennadi Procopciuc
On 11/27/2024 5:32 PM, Alexandre Belloni wrote:
> On 27/11/2024 17:07:48+0200, Ciprian Marian Costea wrote:
>>>> + if (priv->dt_irq_id < 0)
>>>> + return priv->dt_irq_id;
>>>> +
>>>> + ret = devm_request_irq(dev, priv->dt_irq_id,
>>>> + s32g_rtc_handler, 0, dev_name(dev), pdev);
>>>> + if (ret) {
>>>> + dev_err(dev, "Request interrupt %d failed, error: %d\n",
>>>> + priv->dt_irq_id, ret);
>>>> + goto disable_rtc;
>>>
>>>
>>> Already enable rtc at rtc_clk_src_setup(), you direct return fail after
>>> check clk_get_rate();
>>>
>>> if you want to disable_rtc, you use devm_add_action_or_reset() to add
>>> a disable action callback and return dev_err_probe() here directly.
>>>
>>> Frank
>>>
>>
>> Thanks for pointing this out. I will use 'devm_add_action_or_reset' in V6.
>>
>
> Won't this disable the RTC on driver unload which we already discussed
> should not be done?
>
This is true. Thanks for reminding me about this discussion/issue.
I will just fix the error paths for 'clk_get_rate' and 'platform_get_irq'.
>>>> + /* Reset RTC to prevent overflow.
>>>> + * RTCCNT (RTC Counter) cannot be individually reset
>>>> + * since it is RO (read-only).
>>>> + */
>>>
>>> what's happen if overflow happen? I suppose it should go back to 0 and
>>> continue increase?
>>>
>>
>> Indeed if overflow happens the 'RTCCNT' counter goes back to 0 and continues
>> to increase. The reason for resetting it here in 'suspend' routine comes
>> after dropping the rollover support (as agreed on V4 of this patchset) to
>> prevent an overflow during the standby state.
>>
>
> I don't think the overflow matters as the comparator should continue to work
> properly after it happens so you always have the complete range to wait
> for the alarm to happen.
>
>
I think I can drop the RTC reset from the suspend routine for this
current patchset.
Best Regards,
Ciprian
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2024-12-02 13:06 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-26 11:49 [PATCH v5 0/4] add NXP RTC driver support for S32G2/S32G3 SoCs Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 1/4] dt-bindings: rtc: add schema for NXP " Ciprian Costea
2024-11-26 19:08 ` Krzysztof Kozlowski
2024-11-27 12:01 ` Ciprian Marian Costea
2024-11-27 14:43 ` Rob Herring
2024-11-27 14:49 ` Ciprian Marian Costea
2024-11-26 11:49 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
2024-11-26 18:05 ` Frank Li
2024-11-27 15:07 ` Ciprian Marian Costea
2024-11-27 15:32 ` Alexandre Belloni
2024-12-02 13:06 ` Ciprian Marian Costea
2024-11-28 21:18 ` kernel test robot
2024-11-26 11:49 ` [PATCH v5 3/4] arm64: defconfig: add S32G RTC module support Ciprian Costea
2024-11-26 11:49 ` [PATCH v5 4/4] MAINTAINERS: add NXP S32G RTC driver Ciprian Costea
-- strict thread matches above, loose matches on Subject: below --
2024-11-26 11:44 [PATCH] mmc: sdhci-esdhc-imx: enable 'SDHCI_QUIRK_NO_LED' quirk for S32G Ciprian Costea
2024-11-26 11:44 ` [PATCH v5 2/4] rtc: s32g: add NXP S32G2/S32G3 SoC support Ciprian Costea
2024-11-30 20:07 ` kernel test robot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).