From 5d93b32cfb582141aae0c444490359f4cb62f156 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 17 Sep 2026 16:26:53 +0200 Subject: [PATCH 01/89] rtc: omap: remove unused argument omap_rtc_power_off_program takes one argument that is never used. One call passes the struct device of the rtc but the other one passes its parent. To avoid confusion, stop taking any argument Reviewed-by: Nishanth Menon Link: https://patch.msgid.link/20260917142654.2576889-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-omap.c | 5 ++--- drivers/soc/ti/pm33xx.c | 2 +- include/linux/rtc/rtc-omap.h | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-omap.c b/drivers/rtc/rtc-omap.c index 0f90065e352c..18492165a19f 100644 --- a/drivers/rtc/rtc-omap.c +++ b/drivers/rtc/rtc-omap.c @@ -408,7 +408,7 @@ static struct omap_rtc *omap_rtc_power_off_rtc; * generates pmic_pwr_enable control, which can be used to control an external * PMIC. */ -int omap_rtc_power_off_program(struct device *dev) +int omap_rtc_power_off_program(void) { struct omap_rtc *rtc = omap_rtc_power_off_rtc; struct rtc_time tm; @@ -481,10 +481,9 @@ EXPORT_SYMBOL(omap_rtc_power_off_program); */ static void omap_rtc_power_off(void) { - struct rtc_device *rtc = omap_rtc_power_off_rtc->rtc; u32 val; - omap_rtc_power_off_program(rtc->dev.parent); + omap_rtc_power_off_program(); /* Set PMIC power enable and EXT_WAKEUP in case PB power on is used */ omap_rtc_power_off_rtc->type->unlock(omap_rtc_power_off_rtc); diff --git a/drivers/soc/ti/pm33xx.c b/drivers/soc/ti/pm33xx.c index dc52a2197d24..82979122417d 100644 --- a/drivers/soc/ti/pm33xx.c +++ b/drivers/soc/ti/pm33xx.c @@ -174,7 +174,7 @@ static struct wkup_m3_wakeup_src rtc_wake_src(void) static int am33xx_rtc_only_idle(unsigned long wfi_flags) { - omap_rtc_power_off_program(&omap_rtc->dev); + omap_rtc_power_off_program(); am33xx_do_wfi_sram(wfi_flags); return 0; } diff --git a/include/linux/rtc/rtc-omap.h b/include/linux/rtc/rtc-omap.h index 9f03a329e63f..d6ec7c4daea6 100644 --- a/include/linux/rtc/rtc-omap.h +++ b/include/linux/rtc/rtc-omap.h @@ -3,5 +3,5 @@ #ifndef _LINUX_RTCOMAP_H_ #define _LINUX_RTCOMAP_H_ -int omap_rtc_power_off_program(struct device *dev); +int omap_rtc_power_off_program(void); #endif /* _LINUX_RTCOMAP_H_ */ From a867106d8a8951c8f390e21082a08c1c2b674498 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 17 Sep 2026 20:59:04 +0200 Subject: [PATCH 02/89] dt-bindings: rtc: rzn1: drop unneeded 'start-year' from example We don't use 'start-year' in upstream DTs because the driver does the right thing by default, ranging from 2000 to 2099. So, drop it from the example, too. Signed-off-by: Wolfram Sang Reviewed-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260917185903.61253-7-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index ea7b039a91e7..e96007981c34 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -91,5 +91,4 @@ examples: clocks = <&sysctrl R9A06G032_HCLK_RTC>; clock-names = "hclk"; power-domains = <&sysctrl>; - start-year = <2000>; }; From 73defdbfce6228e815d1590769589c4d210b339c Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 17 Sep 2026 20:59:05 +0200 Subject: [PATCH 03/89] dt-bindings: rtc: rzn1: add SoC names next to their IDs This helps maintaining because the numbers are easy to confuse. Signed-off-by: Wolfram Sang Reviewed-by: Lad Prabhakar Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260917185903.61253-8-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/renesas,rzn1-rtc.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index e96007981c34..7ea77cd0bee1 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -14,14 +14,14 @@ properties: oneOf: - items: - enum: - - renesas,r9a06g032-rtc - - const: renesas,rzn1-rtc + - renesas,r9a06g032-rtc # RZ/N1D + - const: renesas,rzn1-rtc # RZ/N1 - - const: renesas,r9a09g077-rtc + - const: renesas,r9a09g077-rtc # RZ/T2H - items: - - const: renesas,r9a09g087-rtc - - const: renesas,r9a09g077-rtc + - const: renesas,r9a09g087-rtc # RZ/N2H + - const: renesas,r9a09g077-rtc # RZ/T2H reg: maxItems: 1 From 3b3fdc9fd40dc9a0f1e52f1b1965c6fa369d0e37 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 17 Sep 2026 20:59:06 +0200 Subject: [PATCH 04/89] dt-bindings: rtc: rzn1: add R-Car X5H support The RTC on R-Car X5H is a bit special because interrupts are not wired. Future R-Car Gen5 SoCs will have them wired again. So, create a dedicated entry for X5H without interrupts. Signed-off-by: Wolfram Sang Reviewed-by: Geert Uytterhoeven Reviewed-by: Lad Prabhakar Link: https://patch.msgid.link/20260917185903.61253-9-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/renesas,rzn1-rtc.yaml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index 7ea77cd0bee1..b448725671ec 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -23,6 +23,8 @@ properties: - const: renesas,r9a09g087-rtc # RZ/N2H - const: renesas,r9a09g077-rtc # RZ/T2H + - const: renesas,r8a78000-rtc # R-Car X5H + reg: maxItems: 1 @@ -52,8 +54,6 @@ properties: required: - compatible - reg - - interrupts - - interrupt-names - clocks - clock-names - power-domains @@ -66,6 +66,7 @@ allOf: compatible: contains: enum: + - renesas,r8a78000-rtc - renesas,r9a09g077-rtc - renesas,r9a09g087-rtc then: @@ -75,6 +76,20 @@ allOf: clock-names: minItems: 2 + - if: + properties: + compatible: + contains: + const: renesas,r8a78000-rtc + then: + properties: + interrupts: false + interrupt-names: false + else: + required: + - interrupts + - interrupt-names + unevaluatedProperties: false examples: From cba3b5f4cc008ca9877f8add2096ad09c7bfc6cb Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 17 Sep 2026 20:59:07 +0200 Subject: [PATCH 05/89] rtc: rzn1: add R-Car X5H support The RTC on R-Car X5H is a bit special because interrupts are not wired. Future R-Car Gen5 SoCs will have them wired again. So, create a dedicated entry for X5H without interrupts. Signed-off-by: Wolfram Sang Reviewed-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20260917185903.61253-10-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rzn1.c | 52 ++++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/drivers/rtc/rtc-rzn1.c b/drivers/rtc/rtc-rzn1.c index b9d69ebdd224..1db518ab3a3a 100644 --- a/drivers/rtc/rtc-rzn1.c +++ b/drivers/rtc/rtc-rzn1.c @@ -68,6 +68,7 @@ struct rzn1_rtc_data { bool has_subu; + bool broken_irqs; }; struct rzn1_rtc { @@ -396,6 +397,11 @@ static const struct rtc_class_ops rzn1_rtc_ops_scmp = { .alarm_irq_enable = rzn1_rtc_alarm_irq_enable, }; +static const struct rtc_class_ops rzn1_rtc_ops_scmp_no_alarm = { + .read_time = rzn1_rtc_read_time, + .set_time = rzn1_rtc_set_time, +}; + static void rzn1_rtc_disable_hardware(void *data) { struct device *dev = data; @@ -431,10 +437,6 @@ static int rzn1_rtc_probe(struct platform_device *pdev) if (IS_ERR(rtc->base)) return dev_err_probe(dev, PTR_ERR(rtc->base), "Missing reg\n"); - irq = platform_get_irq_byname(pdev, "alarm"); - if (irq < 0) - return irq; - rtc->rtcdev = devm_rtc_allocate_device(dev); if (IS_ERR(rtc->rtcdev)) return PTR_ERR(rtc->rtcdev); @@ -503,25 +505,40 @@ static int rzn1_rtc_probe(struct platform_device *pdev) spin_lock_init(&rtc->ctl1_access_lock); - ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); - if (ret) - return dev_err_probe(dev, ret, "RTC alarm interrupt not available\n"); - - irq = platform_get_irq_byname_optional(pdev, "pps"); - if (irq == -EPROBE_DEFER) - return irq; - if (irq >= 0) - ret = devm_request_irq(dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); - - if (irq < 0 || ret) { - set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features); + if (data->broken_irqs) { + /* All known broken irqs are on SCMP only variants */ + rtc->rtcdev->ops = &rzn1_rtc_ops_scmp_no_alarm; clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features); - dev_warn(dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); + } else { + irq = platform_get_irq_byname(pdev, "alarm"); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rzn1_rtc_alarm_irq, 0, "RZN1 RTC Alarm", rtc); + if (ret) + return dev_err_probe(dev, ret, "RTC alarm interrupt not available\n"); + + irq = platform_get_irq_byname_optional(pdev, "pps"); + if (irq == -EPROBE_DEFER) + return irq; + if (irq >= 0) + ret = devm_request_irq(dev, irq, rzn1_rtc_1s_irq, 0, "RZN1 RTC 1s", rtc); + + if (irq < 0 || ret) { + set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->rtcdev->features); + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtcdev->features); + dev_warn(dev, "RTC pps interrupt not available. Alarm has only minute accuracy\n"); + } } return devm_rtc_register_device(rtc->rtcdev); } +static const struct rzn1_rtc_data rzn1_rtc_r8a78000_data = { + .has_subu = false, + .broken_irqs = true, +}; + static const struct rzn1_rtc_data rzn1_rtc_rzt2h_data = { .has_subu = false, }; @@ -531,6 +548,7 @@ static const struct rzn1_rtc_data rzn1_rtc_rzn1_data = { }; static const struct of_device_id rzn1_rtc_of_match[] = { + { .compatible = "renesas,r8a78000-rtc", .data = &rzn1_rtc_r8a78000_data }, { .compatible = "renesas,r9a09g077-rtc", .data = &rzn1_rtc_rzt2h_data }, { .compatible = "renesas,rzn1-rtc", .data = &rzn1_rtc_rzn1_data }, { /* sentinel */ } From 8423c476ab2a193e67d7f8db8621dbc02854dd9d Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Thu, 17 Sep 2026 23:57:46 -0500 Subject: [PATCH 06/89] dt-bindings: mfd: mediatek: mt6397: describe the RTC's nvmem layout Four of the MT6397 RTC's alarm registers use only their low bits; the high byte of each is storage the clock and the alarm don't touch. MediaTek's documentation names them RTC_NEW_SPARE0 to RTC_NEW_SPARE3 and assigns the first to a fuel gauge, which is what a battery driver reads at boot so that the reported capacity does not jump across a reboot. They sit in the RTC's always-on domain, so the driver can offer them as a battery-backed nvmem provider. Allow a board to lay cells out over them. Assisted-by: LLM Signed-off-by: Ryan Brue Reviewed-by: AngeloGioacchino Del Regno Reviewed-by: Rob Herring (Arm) Link: https://patch.msgid.link/20260917-rbrue-suez-upstreaming-mt6397-rtc-nvmem-v1-1-558b8f95cfbc@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/mfd/mediatek,mt6397.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml index 3cbc0dc12c31..5c89c589b53c 100644 --- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml +++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml @@ -81,6 +81,15 @@ properties: start-year: true + nvmem-layout: + $ref: /schemas/nvmem/layouts/nvmem-layout.yaml + description: + The RTC carries four bytes of storage that neither the clock nor the + alarm uses, in the high half of four of the alarm registers, and + offers them as a battery-backed nvmem provider. MediaTek's + documentation names them RTC_NEW_SPARE0 to RTC_NEW_SPARE3 and gives + the first to a fuel gauge. + required: - compatible From 1aaaf94a61eeaab1ab1f872fc818f6f688c7dd7f Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Thu, 17 Sep 2026 23:57:47 -0500 Subject: [PATCH 07/89] rtc: mt6397: expose the spare bytes of the alarm registers as nvmem Each alarm field lives in the low bits of its register and the driver masks its writes accordingly, so the high byte of four of them is storage the RTC never touches. MediaTek names these RTC_NEW_SPARE0 to RTC_NEW_SPARE3 and gives the first to a fuel gauge, which is how its PMIC battery drivers carry a state of charge over a reboot. Offer all four as a battery-backed nvmem provider, so that a consumer does not have to reach into this block behind the driver's back. Doing it here is what makes it safe: a write lands under the same lock the alarm paths take, so it can neither be lost inside mtk_rtc_set_alarm()'s read-modify-write nor fire the write trigger in the middle of one. The nvmem core does not range check a cell against the provider size, so the callbacks check the offset themselves. Tested on an MT6397; MediaTek's spare map for mt6323 matches, and the alarm field masks are common to every compatible this driver binds. Assisted-by: LLM Signed-off-by: Ryan Brue Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20260917-rbrue-suez-upstreaming-mt6397-rtc-nvmem-v1-2-558b8f95cfbc@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mt6397.c | 91 +++++++++++++++++++++++++++++++++- include/linux/mfd/mt6397/rtc.h | 7 +++ 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-mt6397.c b/drivers/rtc/rtc-mt6397.c index 3d857681f760..d6e156516aa6 100644 --- a/drivers/rtc/rtc-mt6397.c +++ b/drivers/rtc/rtc-mt6397.c @@ -4,6 +4,8 @@ * Author: Tianping.Fang */ +#include +#include #include #include #include @@ -243,10 +245,91 @@ static const struct rtc_class_ops mtk_rtc_ops = { .set_alarm = mtk_rtc_set_alarm, }; +/* + * The spare byte of each of these registers, in the order a board addresses + * them as RTC_NEW_SPARE0 to RTC_NEW_SPARE3. + */ +static const u32 mtk_rtc_spare_reg[] = { + RTC_AL_HOU, RTC_AL_DOM, RTC_AL_DOW, RTC_AL_MTH, +}; + +static int mtk_rtc_nvram_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct mt6397_rtc *rtc = priv; + u8 *buf = val; + u32 data; + int ret = 0; + + if (offset >= ARRAY_SIZE(mtk_rtc_spare_reg) || + bytes > ARRAY_SIZE(mtk_rtc_spare_reg) - offset) + return -EINVAL; + + mutex_lock(&rtc->lock); + + while (bytes--) { + ret = regmap_read(rtc->regmap, + rtc->addr_base + mtk_rtc_spare_reg[offset++], + &data); + if (ret) + break; + + *buf++ = FIELD_GET(RTC_SPARE_MASK, data); + } + + mutex_unlock(&rtc->lock); + + return ret; +} + +static int mtk_rtc_nvram_write(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct mt6397_rtc *rtc = priv; + u8 *buf = val; + int ret = 0; + + if (offset >= ARRAY_SIZE(mtk_rtc_spare_reg) || + bytes > ARRAY_SIZE(mtk_rtc_spare_reg) - offset) + return -EINVAL; + + mutex_lock(&rtc->lock); + + while (bytes--) { + ret = regmap_update_bits(rtc->regmap, + rtc->addr_base + mtk_rtc_spare_reg[offset++], + RTC_SPARE_MASK, + FIELD_PREP(RTC_SPARE_MASK, *buf++)); + if (ret) + goto out; + } + + /* + * None of it reaches the always-on domain until the write trigger, + * which commits every pending alarm register at once -- so this runs + * under the same lock the alarm paths take, rather than landing in + * the middle of one of them. + */ + ret = mtk_rtc_write_trigger(rtc); +out: + mutex_unlock(&rtc->lock); + + return ret; +} + static int mtk_rtc_probe(struct platform_device *pdev) { struct resource *res; struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent); + struct nvmem_config nvmem_cfg = { + .name = "mt6397_rtc_spare", + .word_size = 1, + .stride = 1, + .size = ARRAY_SIZE(mtk_rtc_spare_reg), + .type = NVMEM_TYPE_BATTERY_BACKED, + .reg_read = mtk_rtc_nvram_read, + .reg_write = mtk_rtc_nvram_write, + }; struct mt6397_rtc *rtc; int ret; @@ -293,7 +376,13 @@ static int mtk_rtc_probe(struct platform_device *pdev) rtc->rtc_dev->start_secs = mktime64(1968, 1, 2, 0, 0, 0); rtc->rtc_dev->set_start_time = true; - return devm_rtc_register_device(rtc->rtc_dev); + ret = devm_rtc_register_device(rtc->rtc_dev); + if (ret) + return ret; + + nvmem_cfg.priv = rtc; + + return devm_rtc_nvmem_register(rtc->rtc_dev, &nvmem_cfg); } #ifdef CONFIG_PM_SLEEP diff --git a/include/linux/mfd/mt6397/rtc.h b/include/linux/mfd/mt6397/rtc.h index 27883af44f87..f4da579ec638 100644 --- a/include/linux/mfd/mt6397/rtc.h +++ b/include/linux/mfd/mt6397/rtc.h @@ -49,6 +49,13 @@ #define RTC_AL_SEC 0x0018 +/* The high byte of four of the alarms is spare, always-on storage */ +#define RTC_AL_HOU 0x001c +#define RTC_AL_DOM 0x001e +#define RTC_AL_DOW 0x0020 +#define RTC_AL_MTH 0x0022 +#define RTC_SPARE_MASK GENMASK(15, 8) + #define RTC_AL_SEC_MASK 0x003f #define RTC_AL_MIN_MASK 0x003f #define RTC_AL_HOU_MASK 0x001f From cb8a6249236fd970fc3418d5d2b27dc37376f3ff Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Tue, 22 Sep 2026 15:20:15 +0200 Subject: [PATCH 08/89] rtc: bd70528: properly enable wakeup Wakeup on RTC alarm now only accidentially works when wakeup for the power button connected to the same PMIC is also enabled. Fix that by properly specifying the wakeup irq. Reviewed-by: Matti Vaittinen Fixes: 32a4a4ebf768 ("rtc: bd70528: Initial support for ROHM bd70528 RTC") Signed-off-by: Andreas Kemnade Link: https://patch.msgid.link/20260922-rtc-bd-v3-1-7f2580c55d58@kemnade.info Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-bd70528.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-bd70528.c b/drivers/rtc/rtc-bd70528.c index 482810b61495..a58cf85bc1be 100644 --- a/drivers/rtc/rtc-bd70528.c +++ b/drivers/rtc/rtc-bd70528.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -321,6 +322,10 @@ static int bd70528_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); device_wakeup_enable(&pdev->dev); + ret = devm_pm_set_wake_irq(&pdev->dev, irq); + if (ret) + return ret; + rtc = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(rtc)) { dev_err(&pdev->dev, "RTC device creation failed\n"); From c60478875a445dee1206aad6285c18cd8c91f7f2 Mon Sep 17 00:00:00 2001 From: Liu Dalin Date: Thu, 27 Aug 2026 16:03:18 +0800 Subject: [PATCH 09/89] rtc: ftrtc010: fix clock resource leak on probe failure When devm_rtc_register_device() fails, ftrtc010_rtc_probe() returns the error directly without releasing clocks obtained via clk_prepare_enable(). This leaks both extclk and pclk resources, which may prevent subsequent probe attempts or module removal. Capture the return value and jump to the error handling path to properly release clocks via clk_disable_unprepare(). Fixes smatch warnings: - drivers/rtc/rtc-ftrtc010.c:189 ftrtc010_rtc_probe() warn: 'rtc->extclk' from clk_prepare_enable() not released on lines: 183. - drivers/rtc/rtc-ftrtc010.c:189 ftrtc010_rtc_probe() warn: 'rtc->pclk' from clk_prepare_enable() not released on lines: 183. Fixes: fdcfd854333b ("rtc: rework rtc_register_device() resource management") Assisted-by: smatch:2.0 [static analysis] Signed-off-by: Liu Dalin Reviewed-by: Linus Walleij Tested-by: Liu Dalin Link: https://patch.msgid.link/AC51B5F2C5FFEBB0+20260827080320.3351155-2-liudalin@kylinsec.com.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ftrtc010.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ftrtc010.c b/drivers/rtc/rtc-ftrtc010.c index 98fa0f518303..512790bc3f2b 100644 --- a/drivers/rtc/rtc-ftrtc010.c +++ b/drivers/rtc/rtc-ftrtc010.c @@ -180,7 +180,11 @@ static int ftrtc010_rtc_probe(struct platform_device *pdev) if (unlikely(ret)) goto err_disable_extclk; - return devm_rtc_register_device(rtc_dev); + ret = devm_rtc_register_device(rtc_dev); + if (ret) + goto err_disable_extclk; + + return 0; err_disable_extclk: clk_disable_unprepare(rtc->extclk); From d41ee317c9907d714014d0d5e4726fbd6acf338e Mon Sep 17 00:00:00 2001 From: Liu Dalin Date: Thu, 27 Aug 2026 16:03:19 +0800 Subject: [PATCH 10/89] rtc: ftrtc010: use devm clock APIs to fix use-after-disable in remove The driver mixes devm_rtc_register_device() with manual clock disabling in ftrtc010_rtc_remove(). Because the RTC device was registered via devm, it remains registered until devres cleanup runs after .remove() returns. An unprivileged process holding an open /dev/rtc0 file descriptor could issue ioctl(RTC_RD_TIME) during this window, triggering MMIO reads on clock-gated hardware and causing a synchronous external abort. Replace devm_clk_get() + clk_prepare_enable() with devm_clk_get_enabled() to tie clock lifecycle to devres. Remove the manual .remove() callback and the corresponding error handling labels in ftrtc010_rtc_probe(). Fixes: 1d61d2592c1f ("rtc: ftrtc010: Rename to Faraday FTRTC010") Assisted-by: Sashiko AI [static analysis] Signed-off-by: Liu Dalin Reviewed-by: Linus Walleij Tested-by: Liu Dalin Link: https://patch.msgid.link/0238203A74C9B9F6+20260827080320.3351155-3-liudalin@kylinsec.com.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ftrtc010.c | 67 +++++++++----------------------------- 1 file changed, 16 insertions(+), 51 deletions(-) diff --git a/drivers/rtc/rtc-ftrtc010.c b/drivers/rtc/rtc-ftrtc010.c index 512790bc3f2b..19e6333bfbab 100644 --- a/drivers/rtc/rtc-ftrtc010.c +++ b/drivers/rtc/rtc-ftrtc010.c @@ -119,51 +119,33 @@ static int ftrtc010_rtc_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, rtc); - rtc->pclk = devm_clk_get(dev, "PCLK"); - if (IS_ERR(rtc->pclk)) { + rtc->pclk = devm_clk_get_enabled(dev, "PCLK"); + if (IS_ERR_OR_NULL(rtc->pclk)) { dev_err(dev, "could not get PCLK\n"); - } else { - ret = clk_prepare_enable(rtc->pclk); - if (ret) { - dev_err(dev, "failed to enable PCLK\n"); - return ret; - } + return PTR_ERR_OR_ZERO(rtc->pclk); } - rtc->extclk = devm_clk_get(dev, "EXTCLK"); - if (IS_ERR(rtc->extclk)) { + rtc->extclk = devm_clk_get_enabled(dev, "EXTCLK"); + if (IS_ERR_OR_NULL(rtc->extclk)) { dev_err(dev, "could not get EXTCLK\n"); - } else { - ret = clk_prepare_enable(rtc->extclk); - if (ret) { - dev_err(dev, "failed to enable EXTCLK\n"); - goto err_disable_pclk; - } + return PTR_ERR_OR_ZERO(rtc->extclk); } rtc->rtc_irq = platform_get_irq(pdev, 0); - if (rtc->rtc_irq < 0) { - ret = rtc->rtc_irq; - goto err_disable_extclk; - } + if (rtc->rtc_irq < 0) + return rtc->rtc_irq; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - ret = -ENODEV; - goto err_disable_extclk; - } + if (!res) + return -ENODEV; rtc->rtc_base = devm_ioremap(dev, res->start, resource_size(res)); - if (!rtc->rtc_base) { - ret = -ENOMEM; - goto err_disable_extclk; - } + if (!rtc->rtc_base) + return -ENOMEM; rtc_dev = devm_rtc_allocate_device(dev); - if (IS_ERR(rtc_dev)) { - ret = PTR_ERR(rtc_dev); - goto err_disable_extclk; - } + if (IS_ERR(rtc_dev)) + return PTR_ERR(rtc_dev); rtc_dev->ops = &ftrtc010_rtc_ops; @@ -178,29 +160,13 @@ static int ftrtc010_rtc_probe(struct platform_device *pdev) ret = devm_request_irq(dev, rtc->rtc_irq, ftrtc010_rtc_interrupt, IRQF_SHARED, pdev->name, dev); if (unlikely(ret)) - goto err_disable_extclk; + return ret; ret = devm_rtc_register_device(rtc_dev); if (ret) - goto err_disable_extclk; + return ret; return 0; - -err_disable_extclk: - clk_disable_unprepare(rtc->extclk); -err_disable_pclk: - clk_disable_unprepare(rtc->pclk); - return ret; -} - -static void ftrtc010_rtc_remove(struct platform_device *pdev) -{ - struct ftrtc010_rtc *rtc = platform_get_drvdata(pdev); - - if (!IS_ERR(rtc->extclk)) - clk_disable_unprepare(rtc->extclk); - if (!IS_ERR(rtc->pclk)) - clk_disable_unprepare(rtc->pclk); } static const struct of_device_id ftrtc010_rtc_dt_match[] = { @@ -216,7 +182,6 @@ static struct platform_driver ftrtc010_rtc_driver = { .of_match_table = ftrtc010_rtc_dt_match, }, .probe = ftrtc010_rtc_probe, - .remove = ftrtc010_rtc_remove, }; module_platform_driver_probe(ftrtc010_rtc_driver, ftrtc010_rtc_probe); From 32f04694fa87b8ac9fa58f9ceb2dc6cbc476e2a1 Mon Sep 17 00:00:00 2001 From: Liu Dalin Date: Thu, 27 Aug 2026 16:03:20 +0800 Subject: [PATCH 11/89] rtc: ftrtc010: fix integer overflow in time calculation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In ftrtc010_rtc_read_time() and ftrtc010_rtc_set_time(), all variables (offset, days, hour, min, sec) are u32. The expression "days * 86400 + hour * 3600 + min * 60 + sec" is evaluated in 32-bit arithmetic, which silently wraps around for dates beyond approximately year 2106 (U32_MAX / 86400 ≈ 49710 days). In ftrtc010_rtc_read_time(), perform the addition in 32-bit arithmetic first to preserve two's complement wrapping for negative offsets, then cast the result to timeu64_t. In ftrtc010_rtc_set_time(), cast individual u32 operands to timeu64_t before multiplication to prevent overflow in the right-hand side of the offset calculation. Fixes: 1d61d2592c1f ("rtc: gemini/ftrtc010: rename driver and symbols") Assisted-by: Sashiko AI [static analysis] Signed-off-by: Liu Dalin Reviewed-by: Linus Walleij Tested-by: Liu Dalin Link: https://patch.msgid.link/42C8FBD59AA34EA9+20260827080320.3351155-4-liudalin@kylinsec.com.cn Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ftrtc010.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ftrtc010.c b/drivers/rtc/rtc-ftrtc010.c index 19e6333bfbab..b29c96be40f4 100644 --- a/drivers/rtc/rtc-ftrtc010.c +++ b/drivers/rtc/rtc-ftrtc010.c @@ -72,7 +72,7 @@ static int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm) days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); offset = readl(rtc->rtc_base + FTRTC010_RTC_RECORD); - time = offset + days * 86400 + hour * 3600 + min * 60 + sec; + time = (timeu64_t)(offset + days * 86400 + hour * 3600 + min * 60 + sec); rtc_time64_to_tm(time, tm); @@ -92,7 +92,8 @@ static int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm) hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); - offset = time - (day * 86400 + hour * 3600 + min * 60 + sec); + offset = time - ((timeu64_t)day * 86400 + (timeu64_t)hour * 3600 + + (timeu64_t)min * 60 + (timeu64_t)sec); writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD); writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR); From b8f98c67b3e822296ef278f9fc640e8bb142cfde Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 5 Oct 2026 13:15:01 +0200 Subject: [PATCH 12/89] rtc: ftrtc010: fix cast The hour and minute registers will rollover before the multiplication to convert to a number of seconds can exceed an 32bit integer, there is no need to cast them. Reported-by: Dan Carpenter Link: https://patch.msgid.link/20261002105946.8rdteIpO7uWGEcTlS65F8iSi4gsRoXi2kgq8FTSaDPA@z/ Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20261005111502.159855-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ftrtc010.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-ftrtc010.c b/drivers/rtc/rtc-ftrtc010.c index b29c96be40f4..c66d4a85f23c 100644 --- a/drivers/rtc/rtc-ftrtc010.c +++ b/drivers/rtc/rtc-ftrtc010.c @@ -72,7 +72,7 @@ static int ftrtc010_rtc_read_time(struct device *dev, struct rtc_time *tm) days = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); offset = readl(rtc->rtc_base + FTRTC010_RTC_RECORD); - time = (timeu64_t)(offset + days * 86400 + hour * 3600 + min * 60 + sec); + time = offset + (timeu64_t)days * 86400 + hour * 3600 + min * 60 + sec; rtc_time64_to_tm(time, tm); @@ -92,8 +92,7 @@ static int ftrtc010_rtc_set_time(struct device *dev, struct rtc_time *tm) hour = readl(rtc->rtc_base + FTRTC010_RTC_HOUR); day = readl(rtc->rtc_base + FTRTC010_RTC_DAYS); - offset = time - ((timeu64_t)day * 86400 + (timeu64_t)hour * 3600 + - (timeu64_t)min * 60 + (timeu64_t)sec); + offset = time - ((timeu64_t)day * 86400 + hour * 3600 + min * 60 + sec); writel(offset, rtc->rtc_base + FTRTC010_RTC_RECORD); writel(0x01, rtc->rtc_base + FTRTC010_RTC_CR); From 237378fd6be421eb729808010953f50463687ccf Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Thu, 8 Oct 2026 09:13:05 +0200 Subject: [PATCH 13/89] rtc: pcf8563: add SMBus support Since commit 00f1bb9b8486 ("rtc: pcf8563: Switch to regmap") the driver uses regmap to access the RTC. Regmap itself will figure out if there is a suitable I2C or SMBus host controllers. Thus, don't restict the driver to just I2C controllers and make it work on SMBus controllers supporting i2c block accesses, too. This was tested on a custom x86 board. Signed-off-by: Michael Walle Link: https://patch.msgid.link/20261008071408.3548442-1-mwalle@kernel.org Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf8563.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 7eebdb058067..56e75664c7b4 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -479,7 +479,8 @@ static int pcf8563_probe(struct i2c_client *client) dev_dbg(&client->dev, "%s\n", __func__); - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C) && + !i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) return -ENODEV; pcf8563 = devm_kzalloc(&client->dev, sizeof(struct pcf8563), From 953c3d2b5836b6cb6fb987823fa573f38041e636 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:30 +0200 Subject: [PATCH 14/89] dt-bindings: rtc: abx80x: document ABX81X RTCs Abracon AB0815 and AB1815 are RTC modules with SPI interface. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-2-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- .../bindings/rtc/abracon,abx80x.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/rtc/abracon,abx80x.yaml b/Documentation/devicetree/bindings/rtc/abracon,abx80x.yaml index 355b0598411a..d105570bcd5a 100644 --- a/Documentation/devicetree/bindings/rtc/abracon,abx80x.yaml +++ b/Documentation/devicetree/bindings/rtc/abracon,abx80x.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/rtc/abracon,abx80x.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Abracon ABX80X I2C ultra low power RTC/Alarm chip +title: Abracon ABX80X (I2C) and ABX81X (SPI) ultra low power RTC/Alarm chips maintainers: - linux-rtc@vger.kernel.org @@ -21,10 +21,12 @@ properties: - abracon,ab0803 - abracon,ab0804 - abracon,ab0805 + - abracon,ab0815 - abracon,ab1801 - abracon,ab1803 - abracon,ab1804 - abracon,ab1805 + - abracon,ab1815 - microcrystal,rv1805 reg: @@ -61,6 +63,7 @@ required: - reg allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# - $ref: rtc.yaml# - if: properties: @@ -73,6 +76,8 @@ allOf: - abracon,ab1804 - abracon,ab0805 - abracon,ab1805 + - abracon,ab0815 + - abracon,ab1815 then: properties: abracon,tc-diode: false @@ -96,3 +101,14 @@ examples: interrupts = <44 IRQ_TYPE_EDGE_FALLING>; }; }; + + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + rtc@0 { + compatible = "abracon,ab1815"; + reg = <0>; + }; + }; From 07c57022afb3afc0d7c20efef45a4d3c7389b1c0 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:31 +0200 Subject: [PATCH 15/89] rtc: abx80x: fix error check after i2c_smbus_read in read_alarm() i2c_smbus_read_i2c_block_data() returns the number of bytes read on success and a negative error code on failure. The existing code treated any non-zero return value as an error. Fix the error handling by checking only for negative return values. Fixes: 718a820a303c ("rtc: abx80x: add alarm support") Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-3-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 5486d9d0b1e5..e4fd7b5d4b11 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -294,8 +294,10 @@ static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_ASC, sizeof(buf), buf); - if (err) + if (err < 0) return err; + if (err < ARRAY_SIZE(buf)) + return -EIO; irq_mask = i2c_smbus_read_byte_data(client, ABX8XX_REG_IRQ); if (irq_mask < 0) @@ -311,7 +313,7 @@ static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) t->enabled = !!(irq_mask & ABX8XX_IRQ_AIE); t->pending = (buf[6] & ABX8XX_STATUS_AF) && t->enabled; - return err; + return 0; } static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) From 74bff654a4ced9f5fc6bac9931a5d251f7795db4 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:32 +0200 Subject: [PATCH 16/89] rtc: abx80x: add mutex protection for register writes The ABX80X RTC driver performs multi-step register operations such as NVMEM transfers or register writes preceded by a configuration key write. Add a mutex to serialize all the register writes to protect these sequences against race conditions. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-4-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 45 +++++++++++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index e4fd7b5d4b11..44de234efecf 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,7 @@ struct abx80x_priv { struct rtc_device *rtc; struct i2c_client *client; struct watchdog_device wdog; + struct mutex lock; }; static int abx80x_write_config_key(struct i2c_client *client, u8 key) @@ -219,6 +221,7 @@ static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm) static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); unsigned char buf[8]; int err, flags; @@ -234,6 +237,8 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) buf[ABX8XX_REG_YR] = bin2bcd(tm->tm_year - 100); buf[ABX8XX_REG_WD] = tm->tm_wday; + guard(mutex)(&priv->lock); + err = i2c_smbus_write_i2c_block_data(client, ABX8XX_REG_HTH, sizeof(buf), buf); if (err < 0) { @@ -263,6 +268,8 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) struct rtc_device *rtc = priv->rtc; int status; + guard(mutex)(&priv->lock); + status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); if (status < 0) return IRQ_NONE; @@ -319,6 +326,7 @@ static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); u8 alarm[6]; int err; @@ -332,6 +340,8 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) alarm[4] = bin2bcd(t->time.tm_mday); alarm[5] = bin2bcd(t->time.tm_mon + 1); + guard(mutex)(&priv->lock); + err = i2c_smbus_write_i2c_block_data(client, ABX8XX_REG_AHTH, sizeof(alarm), alarm); if (err < 0) { @@ -354,6 +364,7 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, int autocalibration) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); int retval, flags = 0; if ((autocalibration != 0) && (autocalibration != 1024) && @@ -362,6 +373,8 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, return -EINVAL; } + guard(mutex)(&priv->lock); + flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); if (flags < 0) return flags; @@ -445,6 +458,7 @@ static ssize_t oscillator_store(struct device *dev, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev->parent); + struct abx80x_priv *priv = i2c_get_clientdata(client); int retval, flags, rc_mode = 0; if (strncmp(buf, "rc", 2) == 0) { @@ -456,6 +470,8 @@ static ssize_t oscillator_store(struct device *dev, return -EINVAL; } + guard(mutex)(&priv->lock); + flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); if (flags < 0) return flags; @@ -513,8 +529,11 @@ static const struct attribute_group rtc_calib_attr_group = { static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); int err; + guard(mutex)(&priv->lock); + if (enabled) err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, (ABX8XX_IRQ_IM_1_4 | @@ -528,6 +547,7 @@ static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled) static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); int status, tmp; switch (cmd) { @@ -541,16 +561,18 @@ static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) return put_user(tmp, (unsigned int __user *)arg); case RTC_VL_CLR: - status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); - if (status < 0) - return status; + scoped_guard(mutex, &priv->lock) { + status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); + if (status < 0) + return status; - status &= ~ABX8XX_STATUS_BLF; + status &= ~ABX8XX_STATUS_BLF; - tmp = i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, - status); - if (tmp < 0) - return tmp; + tmp = i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, + status); + if (tmp < 0) + return tmp; + } return 0; @@ -619,6 +641,8 @@ static int __abx80x_wdog_set_timeout(struct watchdog_device *wdog, struct abx80x_priv *priv = watchdog_get_drvdata(wdog); u8 val = ABX8XX_WDT_WDS | timeout_bits(timeout); + guard(mutex)(&priv->lock); + /* * Writing any timeout to the WDT register resets the watchdog timer. * Writing 0 disables it. @@ -703,6 +727,8 @@ static int abx80x_nvmem_xfer(struct abx80x_priv *priv, unsigned int offset, len = min(lower + bytes, (size_t)ABX8XX_SRAM_WIN_SIZE) - lower; len = min_t(u8, len, I2C_SMBUS_BLOCK_MAX); + guard(mutex)(&priv->lock); + ret = i2c_smbus_write_byte_data(priv->client, ABX8XX_REG_EXTRAM, extram); if (ret) @@ -910,6 +936,9 @@ static int abx80x_probe(struct i2c_client *client) priv->rtc->ops = &abx80x_rtc_ops; priv->client = client; + err = devm_mutex_init(&client->dev, &priv->lock); + if (err) + return err; i2c_set_clientdata(client, priv); From 632916c7bde35495f2f6776d8e335c3ec6e306ec Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:33 +0200 Subject: [PATCH 17/89] rtc: abx80x: properly handle shared IRQs The abx80x RTC driver registers its interrupt handler with IRQF_SHARED, but the handler always returned IRQ_HANDLED and cleared the status register even when no interrupt source from the RTC was active. This could cause the driver to claim interrupts belonging to other devices sharing the same IRQ line and could also clear unrelated status bits in the RTC status register. Only handle the interrupt when the alarm or watchdog flags are set, and clear only those interrupt status bits that were handled. Return IRQ_NONE when the interrupt was not generated by the RTC. Fixes: 718a820a303c ("rtc: abx80x: add alarm support") Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-5-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 52 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 44de234efecf..2fd0079b7c51 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -51,12 +51,18 @@ #define ABX8XX_CTRL2_RSVD BIT(5) #define ABX8XX_REG_IRQ 0x12 +#define ABX8XX_IRQ_EX1E BIT(0) +#define ABX8XX_IRQ_EX2E BIT(1) #define ABX8XX_IRQ_AIE BIT(2) +#define ABX8XX_IRQ_TIE BIT(3) +#define ABX8XX_IRQ_BLIE BIT(4) #define ABX8XX_IRQ_IM_1_4 (0x3 << 5) #define ABX8XX_REG_CD_TIMER_CTL 0x18 #define ABX8XX_REG_OSC 0x1c +#define ABX8XX_OSC_ACIE BIT(0) +#define ABX8XX_OSC_OFIE BIT(1) #define ABX8XX_OSC_FOS BIT(3) #define ABX8XX_OSC_BOS BIT(4) #define ABX8XX_OSC_ACAL_512 BIT(5) @@ -266,6 +272,7 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) struct i2c_client *client = dev_id; struct abx80x_priv *priv = i2c_get_clientdata(client); struct rtc_device *rtc = priv->rtc; + unsigned int status_flags_clear; int status; guard(mutex)(&priv->lock); @@ -284,9 +291,15 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) if (status & ABX8XX_STATUS_WDT) dev_alert(&client->dev, "watchdog timeout interrupt.\n"); - i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, 0); + status_flags_clear = status & (ABX8XX_STATUS_AF | ABX8XX_STATUS_WDT); - return IRQ_HANDLED; + if (status_flags_clear) { + i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, + status & ~status_flags_clear); + return IRQ_HANDLED; + } + + return IRQ_NONE; } static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) @@ -942,6 +955,41 @@ static int abx80x_probe(struct i2c_client *client) i2c_set_clientdata(client, priv); + /* Disable unused interrupts */ + data = i2c_smbus_read_byte_data(client, ABX8XX_REG_IRQ); + if (data < 0) { + dev_err(&client->dev, "Unable to read irq register\n"); + return -EIO; + } + + err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, + data & ~(ABX8XX_IRQ_EX1E | + ABX8XX_IRQ_EX2E | + ABX8XX_IRQ_TIE | + ABX8XX_IRQ_BLIE)); + if (err < 0) { + dev_err(&client->dev, "Unable to write irq register\n"); + return -EIO; + } + + data = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); + if (data < 0) { + dev_err(&client->dev, "Unable to read Oscillator Control register\n"); + return -EIO; + } + + /* Unlock write access to Oscillator Control Register */ + if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) + return -EIO; + + err = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, + data & ~(ABX8XX_OSC_ACIE | + ABX8XX_OSC_OFIE)); + if (err < 0) { + dev_err(&client->dev, "Unable to write Oscillator Control register\n"); + return -EIO; + } + if (abx80x_caps[part].has_wdog) { err = abx80x_setup_watchdog(priv); if (err) From 00b978f8008e8107da149532cfdcd996e06ac657 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:34 +0200 Subject: [PATCH 18/89] rtc: abx80x: add irq to struct abx80x_priv This way the irq number is not tied to the i2c_client and can be easily accessed also in the case of ABX81X SPI clocks. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-6-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 2fd0079b7c51..91844c0d9063 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -135,6 +135,7 @@ struct abx80x_priv { struct i2c_client *client; struct watchdog_device wdog; struct mutex lock; + int irq; }; static int abx80x_write_config_key(struct i2c_client *client, u8 key) @@ -305,11 +306,12 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) { struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = i2c_get_clientdata(client); unsigned char buf[7]; int irq_mask, err; - if (client->irq <= 0) + if (priv->irq <= 0) return -EINVAL; err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_ASC, @@ -343,7 +345,7 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) u8 alarm[6]; int err; - if (client->irq <= 0) + if (priv->irq <= 0) return -EINVAL; alarm[0] = 0x0; @@ -949,6 +951,7 @@ static int abx80x_probe(struct i2c_client *client) priv->rtc->ops = &abx80x_rtc_ops; priv->client = client; + priv->irq = client->irq; err = devm_mutex_init(&client->dev, &priv->lock); if (err) return err; @@ -1000,19 +1003,19 @@ static int abx80x_probe(struct i2c_client *client) if (err) return err; - if (client->irq > 0) { - dev_info(&client->dev, "IRQ %d supplied\n", client->irq); - err = devm_request_threaded_irq(&client->dev, client->irq, NULL, + if (priv->irq > 0) { + dev_info(&client->dev, "IRQ %d supplied\n", priv->irq); + err = devm_request_threaded_irq(&client->dev, priv->irq, NULL, abx80x_handle_irq, IRQF_SHARED | IRQF_ONESHOT, "abx8xx", client); if (err) { dev_err(&client->dev, "unable to request IRQ, alarms disabled\n"); - client->irq = 0; + priv->irq = 0; } } - if (client->irq <= 0) + if (priv->irq <= 0) clear_bit(RTC_FEATURE_ALARM, priv->rtc->features); err = rtc_add_group(priv->rtc, &rtc_calib_attr_group); From 724320a613251470a1b3bc56a81596f4d23a6e88 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:35 +0200 Subject: [PATCH 19/89] rtc: abx80x: use regmap instead of I2C specific API Rework the driver so that it uses regmap_* functions for communication with the device instead of the i2c_smbus_* API. This way the support for ABX81X RTCs which use SPI can be added later. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-7-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 1 + drivers/rtc/rtc-abx80x.c | 395 +++++++++++++++++++++------------------ 2 files changed, 213 insertions(+), 183 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 05b9233b9418..7be1b1b7070a 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -215,6 +215,7 @@ config RTC_DRV_ABEOZ9 config RTC_DRV_ABX80X tristate "Abracon ABx80x" select WATCHDOG_CORE if WATCHDOG + select REGMAP_I2C help If you say yes here you get support for Abracon AB080X and AB180X families of ultra-low-power battery- and capacitor-backed real-time diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 91844c0d9063..d41656a4d518 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -132,53 +133,55 @@ static struct abx80x_cap abx80x_caps[] = { struct abx80x_priv { struct rtc_device *rtc; - struct i2c_client *client; + struct regmap *regmap; struct watchdog_device wdog; struct mutex lock; int irq; }; -static int abx80x_write_config_key(struct i2c_client *client, u8 key) +static int abx80x_write_config_key(struct device *dev, u8 key) { - if (i2c_smbus_write_byte_data(client, ABX8XX_REG_CFG_KEY, key) < 0) { - dev_err(&client->dev, "Unable to write configuration key\n"); + struct abx80x_priv *priv = dev_get_drvdata(dev); + + if (regmap_write(priv->regmap, ABX8XX_REG_CFG_KEY, key) < 0) { + dev_err(dev, "Unable to write configuration key\n"); return -EIO; } return 0; } -static int abx80x_is_rc_mode(struct i2c_client *client) +static int abx80x_is_rc_mode(struct device *dev) { - int flags = 0; + struct abx80x_priv *priv = dev_get_drvdata(dev); + unsigned int flags = 0; + int err; - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSS); - if (flags < 0) { - dev_err(&client->dev, - "Failed to read autocalibration attribute\n"); - return flags; + err = regmap_read(priv->regmap, ABX8XX_REG_OSS, &flags); + if (err < 0) { + dev_err(dev, "Failed to read autocalibration attribute\n"); + return err; } return (flags & ABX8XX_OSS_OMODE) ? 1 : 0; } -static int abx80x_enable_trickle_charger(struct i2c_client *client, - u8 trickle_cfg) +static int abx80x_enable_trickle_charger(struct device *dev, u8 trickle_cfg) { + struct abx80x_priv *priv = dev_get_drvdata(dev); int err; /* * Write the configuration key register to enable access to the Trickle * register */ - if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_MISC) < 0) + if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_MISC) < 0) return -EIO; - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_TRICKLE, - ABX8XX_TRICKLE_CHARGE_ENABLE | - trickle_cfg); + err = regmap_write(priv->regmap, ABX8XX_REG_TRICKLE, + ABX8XX_TRICKLE_CHARGE_ENABLE | trickle_cfg); if (err < 0) { - dev_err(&client->dev, "Unable to write trickle register\n"); + dev_err(dev, "Unable to write trickle register\n"); return -EIO; } @@ -187,19 +190,20 @@ static int abx80x_enable_trickle_charger(struct i2c_client *client, static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm) { - struct i2c_client *client = to_i2c_client(dev); + struct abx80x_priv *priv = dev_get_drvdata(dev); unsigned char buf[8]; - int err, flags, rc_mode = 0; + unsigned int flags; + int err, rc_mode = 0; /* Read the Oscillator Failure only in XT mode */ - rc_mode = abx80x_is_rc_mode(client); + rc_mode = abx80x_is_rc_mode(dev); if (rc_mode < 0) return rc_mode; if (!rc_mode) { - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSS); - if (flags < 0) - return flags; + err = regmap_read(priv->regmap, ABX8XX_REG_OSS, &flags); + if (err < 0) + return err; if (flags & ABX8XX_OSS_OF) { dev_err(dev, "Oscillator failure, data is invalid.\n"); @@ -207,10 +211,9 @@ static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm) } } - err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_HTH, - sizeof(buf), buf); + err = regmap_bulk_read(priv->regmap, ABX8XX_REG_HTH, buf, sizeof(buf)); if (err < 0) { - dev_err(&client->dev, "Unable to read date\n"); + dev_err(dev, "Unable to read date\n"); return -EIO; } @@ -227,10 +230,10 @@ static int abx80x_rtc_read_time(struct device *dev, struct rtc_time *tm) static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); + struct abx80x_priv *priv = dev_get_drvdata(dev); unsigned char buf[8]; - int err, flags; + unsigned int flags; + int err; if (tm->tm_year < 100) return -EINVAL; @@ -246,22 +249,22 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) guard(mutex)(&priv->lock); - err = i2c_smbus_write_i2c_block_data(client, ABX8XX_REG_HTH, - sizeof(buf), buf); + err = regmap_bulk_write(priv->regmap, ABX8XX_REG_HTH, buf, + sizeof(buf)); if (err < 0) { - dev_err(&client->dev, "Unable to write to date registers\n"); + dev_err(dev, "Unable to write to date registers\n"); return -EIO; } /* Clear the OF bit of Oscillator Status Register */ - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSS); - if (flags < 0) - return flags; + err = regmap_read(priv->regmap, ABX8XX_REG_OSS, &flags); + if (err < 0) + return err; - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSS, - flags & ~ABX8XX_OSS_OF); + err = regmap_write(priv->regmap, ABX8XX_REG_OSS, + flags & ~ABX8XX_OSS_OF); if (err < 0) { - dev_err(&client->dev, "Unable to write oscillator status register\n"); + dev_err(dev, "Unable to write oscillator status register\n"); return err; } @@ -270,16 +273,16 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) { - struct i2c_client *client = dev_id; - struct abx80x_priv *priv = i2c_get_clientdata(client); + struct device *dev = dev_id; + struct abx80x_priv *priv = dev_get_drvdata(dev); struct rtc_device *rtc = priv->rtc; - unsigned int status_flags_clear; - int status; + unsigned int status, status_flags_clear; + int err; guard(mutex)(&priv->lock); - status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); - if (status < 0) + err = regmap_read(priv->regmap, ABX8XX_REG_STATUS, &status); + if (err < 0) return IRQ_NONE; if (status & ABX8XX_STATUS_AF) @@ -290,13 +293,13 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) * reset kicks in. */ if (status & ABX8XX_STATUS_WDT) - dev_alert(&client->dev, "watchdog timeout interrupt.\n"); + dev_alert(dev, "watchdog timeout interrupt.\n"); status_flags_clear = status & (ABX8XX_STATUS_AF | ABX8XX_STATUS_WDT); if (status_flags_clear) { - i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, - status & ~status_flags_clear); + regmap_write(priv->regmap, ABX8XX_REG_STATUS, + status & ~status_flags_clear); return IRQ_HANDLED; } @@ -305,25 +308,22 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); + struct abx80x_priv *priv = dev_get_drvdata(dev); unsigned char buf[7]; - int irq_mask, err; + unsigned int irq_mask; + int err; if (priv->irq <= 0) return -EINVAL; - err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_ASC, - sizeof(buf), buf); + err = regmap_bulk_read(priv->regmap, ABX8XX_REG_ASC, buf, sizeof(buf)); + if (err) + return err; + + err = regmap_read(priv->regmap, ABX8XX_REG_IRQ, &irq_mask); if (err < 0) return err; - if (err < ARRAY_SIZE(buf)) - return -EIO; - - irq_mask = i2c_smbus_read_byte_data(client, ABX8XX_REG_IRQ); - if (irq_mask < 0) - return irq_mask; t->time.tm_sec = bcd2bin(buf[0] & 0x7F); t->time.tm_min = bcd2bin(buf[1] & 0x7F); @@ -340,8 +340,7 @@ static int abx80x_read_alarm(struct device *dev, struct rtc_wkalrm *t) static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); + struct abx80x_priv *priv = dev_get_drvdata(dev); u8 alarm[6]; int err; @@ -357,17 +356,16 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) guard(mutex)(&priv->lock); - err = i2c_smbus_write_i2c_block_data(client, ABX8XX_REG_AHTH, - sizeof(alarm), alarm); + err = regmap_bulk_write(priv->regmap, ABX8XX_REG_AHTH, + alarm, sizeof(alarm)); if (err < 0) { - dev_err(&client->dev, "Unable to write alarm registers\n"); + dev_err(dev, "Unable to write alarm registers\n"); return -EIO; } if (t->enabled) { - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, - (ABX8XX_IRQ_IM_1_4 | - ABX8XX_IRQ_AIE)); + err = regmap_write(priv->regmap, ABX8XX_REG_IRQ, + ABX8XX_IRQ_IM_1_4 | ABX8XX_IRQ_AIE); if (err) return err; } @@ -378,9 +376,9 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) static int abx80x_rtc_set_autocalibration(struct device *dev, int autocalibration) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); - int retval, flags = 0; + struct abx80x_priv *priv = dev_get_drvdata(dev); + unsigned int flags = 0; + int retval; if ((autocalibration != 0) && (autocalibration != 1024) && (autocalibration != 512)) { @@ -390,9 +388,9 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, guard(mutex)(&priv->lock); - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); - if (flags < 0) - return flags; + retval = regmap_read(priv->regmap, ABX8XX_REG_OSC, &flags); + if (retval < 0) + return retval; if (autocalibration == 0) { flags &= ~(ABX8XX_OSC_ACAL_512 | ABX8XX_OSC_ACAL_1024); @@ -406,22 +404,23 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, } /* Unlock write access to Oscillator Control Register */ - if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) + if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, flags); + retval = regmap_write(priv->regmap, ABX8XX_REG_OSC, flags); return retval; } static int abx80x_rtc_get_autocalibration(struct device *dev) { - struct i2c_client *client = to_i2c_client(dev); - int flags = 0, autocalibration; + struct abx80x_priv *priv = dev_get_drvdata(dev); + unsigned int flags = 0; + int autocalibration, err; - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); - if (flags < 0) - return flags; + err = regmap_read(priv->regmap, ABX8XX_REG_OSC, &flags); + if (err < 0) + return err; if (flags & ABX8XX_OSC_ACAL_512) autocalibration = 512; @@ -472,9 +471,9 @@ static ssize_t oscillator_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { - struct i2c_client *client = to_i2c_client(dev->parent); - struct abx80x_priv *priv = i2c_get_clientdata(client); - int retval, flags, rc_mode = 0; + struct abx80x_priv *priv = dev_get_drvdata(dev->parent); + int retval, rc_mode = 0; + unsigned int flags; if (strncmp(buf, "rc", 2) == 0) { rc_mode = 1; @@ -487,9 +486,9 @@ static ssize_t oscillator_store(struct device *dev, guard(mutex)(&priv->lock); - flags = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); - if (flags < 0) - return flags; + retval = regmap_read(priv->regmap, ABX8XX_REG_OSC, &flags); + if (retval < 0) + return retval; if (rc_mode == 0) flags &= ~(ABX8XX_OSC_OSEL); @@ -497,10 +496,10 @@ static ssize_t oscillator_store(struct device *dev, flags |= (ABX8XX_OSC_OSEL); /* Unlock write access on Oscillator Control register */ - if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) + if (abx80x_write_config_key(dev->parent, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - retval = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, flags); + retval = regmap_write(priv->regmap, ABX8XX_REG_OSC, flags); if (retval < 0) { dev_err(dev, "Failed to write Oscillator Control register\n"); return retval; @@ -513,9 +512,8 @@ static ssize_t oscillator_show(struct device *dev, struct device_attribute *attr, char *buf) { int rc_mode = 0; - struct i2c_client *client = to_i2c_client(dev->parent); - rc_mode = abx80x_is_rc_mode(client); + rc_mode = abx80x_is_rc_mode(dev->parent); if (rc_mode < 0) { dev_err(dev, "Failed to read RTC oscillator selection\n"); @@ -543,33 +541,31 @@ static const struct attribute_group rtc_calib_attr_group = { static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); + struct abx80x_priv *priv = dev_get_drvdata(dev); int err; guard(mutex)(&priv->lock); if (enabled) - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, - (ABX8XX_IRQ_IM_1_4 | - ABX8XX_IRQ_AIE)); + err = regmap_write(priv->regmap, ABX8XX_REG_IRQ, + ABX8XX_IRQ_IM_1_4 | ABX8XX_IRQ_AIE); else - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, - ABX8XX_IRQ_IM_1_4); + err = regmap_write(priv->regmap, ABX8XX_REG_IRQ, + ABX8XX_IRQ_IM_1_4); return err; } static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) { - struct i2c_client *client = to_i2c_client(dev); - struct abx80x_priv *priv = i2c_get_clientdata(client); - int status, tmp; + struct abx80x_priv *priv = dev_get_drvdata(dev); + unsigned int status; + int err, tmp; switch (cmd) { case RTC_VL_READ: - status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); - if (status < 0) - return status; + err = regmap_read(priv->regmap, ABX8XX_REG_STATUS, &status); + if (err < 0) + return err; tmp = status & ABX8XX_STATUS_BLF ? RTC_VL_BACKUP_LOW : 0; @@ -577,16 +573,15 @@ static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) case RTC_VL_CLR: scoped_guard(mutex, &priv->lock) { - status = i2c_smbus_read_byte_data(client, ABX8XX_REG_STATUS); - if (status < 0) - return status; + err = regmap_read(priv->regmap, ABX8XX_REG_STATUS, &status); + if (err < 0) + return err; status &= ~ABX8XX_STATUS_BLF; - tmp = i2c_smbus_write_byte_data(client, ABX8XX_REG_STATUS, - status); - if (tmp < 0) - return tmp; + err = regmap_write(priv->regmap, ABX8XX_REG_STATUS, status); + if (err < 0) + return err; } return 0; @@ -605,9 +600,9 @@ static const struct rtc_class_ops abx80x_rtc_ops = { .ioctl = abx80x_ioctl, }; -static int abx80x_dt_trickle_cfg(struct i2c_client *client) +static int abx80x_dt_trickle_cfg(struct device *dev) { - struct device_node *np = client->dev.of_node; + struct device_node *np = dev->of_node; const char *diode; int trickle_cfg = 0; int i, ret; @@ -622,7 +617,7 @@ static int abx80x_dt_trickle_cfg(struct i2c_client *client) } else if (!strcmp(diode, "schottky")) { trickle_cfg |= ABX8XX_TRICKLE_SCHOTTKY_DIODE; } else { - dev_dbg(&client->dev, "Invalid tc-diode value: %s\n", diode); + dev_dbg(dev, "Invalid tc-diode value: %s\n", diode); return -EINVAL; } @@ -635,7 +630,7 @@ static int abx80x_dt_trickle_cfg(struct i2c_client *client) break; if (i == sizeof(trickle_resistors)) { - dev_dbg(&client->dev, "Invalid tc-resistor value: %u\n", tmp); + dev_dbg(dev, "Invalid tc-resistor value: %u\n", tmp); return -EINVAL; } @@ -662,7 +657,7 @@ static int __abx80x_wdog_set_timeout(struct watchdog_device *wdog, * Writing any timeout to the WDT register resets the watchdog timer. * Writing 0 disables it. */ - return i2c_smbus_write_byte_data(priv->client, ABX8XX_REG_WDT, val); + return regmap_write(priv->regmap, ABX8XX_REG_WDT, val); } static int abx80x_wdog_set_timeout(struct watchdog_device *wdog, @@ -707,9 +702,11 @@ static const struct watchdog_ops abx80x_wdog_ops = { .set_timeout = abx80x_wdog_set_timeout, }; -static int abx80x_setup_watchdog(struct abx80x_priv *priv) +static int abx80x_setup_watchdog(struct device *dev) { - priv->wdog.parent = &priv->client->dev; + struct abx80x_priv *priv = dev_get_drvdata(dev); + + priv->wdog.parent = dev; priv->wdog.ops = &abx80x_wdog_ops; priv->wdog.info = &abx80x_wdog_info; priv->wdog.min_timeout = 1; @@ -718,10 +715,10 @@ static int abx80x_setup_watchdog(struct abx80x_priv *priv) watchdog_set_drvdata(&priv->wdog, priv); - return devm_watchdog_register_device(&priv->client->dev, &priv->wdog); + return devm_watchdog_register_device(dev, &priv->wdog); } #else -static int abx80x_setup_watchdog(struct abx80x_priv *priv) +static int abx80x_setup_watchdog(struct device *dev) { return 0; } @@ -733,33 +730,29 @@ static int abx80x_nvmem_xfer(struct abx80x_priv *priv, unsigned int offset, int ret; while (bytes) { - u8 extram, reg, len, lower, upper; + u8 reg, len, lower, upper; lower = FIELD_GET(NVMEM_ADDR_LOWER, offset); upper = FIELD_GET(NVMEM_ADDR_UPPER, offset); - extram = FIELD_PREP(ABX8XX_EXTRAM_XADS, upper); reg = ABX8XX_SRAM_BASE + lower; len = min(lower + bytes, (size_t)ABX8XX_SRAM_WIN_SIZE) - lower; len = min_t(u8, len, I2C_SMBUS_BLOCK_MAX); guard(mutex)(&priv->lock); - ret = i2c_smbus_write_byte_data(priv->client, ABX8XX_REG_EXTRAM, - extram); + ret = regmap_update_bits(priv->regmap, ABX8XX_REG_EXTRAM, + ABX8XX_EXTRAM_XADS, upper); if (ret) return ret; if (write) { - ret = i2c_smbus_write_i2c_block_data(priv->client, reg, - len, val); + ret = regmap_bulk_write(priv->regmap, reg, val, len); if (ret) return ret; } else { - ret = i2c_smbus_read_i2c_block_data(priv->client, reg, - len, val); - if (ret <= 0) - return ret ? ret : -EIO; - len = ret; + ret = regmap_bulk_read(priv->regmap, reg, val, len); + if (ret) + return ret; } offset += len; @@ -795,6 +788,38 @@ static int abx80x_setup_nvmem(struct abx80x_priv *priv) return devm_rtc_nvmem_register(priv->rtc, &config); } +static const struct regmap_range abx80x_no_read_ranges[] = { + regmap_reg_range(0x1e, 0x1e), + regmap_reg_range(0x22, 0x25), + regmap_reg_range(0x31, 0x3e), +}; + +static const struct regmap_range abx80x_no_write_ranges[] = { + regmap_reg_range(0x1e, 0x1e), + regmap_reg_range(0x22, 0x25), + regmap_reg_range(ABX8XX_REG_ID0, ABX8XX_REG_ID0 + 6), + regmap_reg_range(0x31, 0x3e), +}; + +static const struct regmap_access_table abx80x_read_table = { + .no_ranges = abx80x_no_read_ranges, + .n_no_ranges = ARRAY_SIZE(abx80x_no_read_ranges), +}; + +static const struct regmap_access_table abx80x_write_table = { + .no_ranges = abx80x_no_write_ranges, + .n_no_ranges = ARRAY_SIZE(abx80x_no_write_ranges), +}; + +static const struct regmap_config abx80x_regmap_config_i2c = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ABX8XX_SRAM_BASE + ABX8XX_SRAM_WIN_SIZE - 1, + + .rd_table = &abx80x_read_table, + .wr_table = &abx80x_write_table, +}; + static const struct i2c_device_id abx80x_id[] = { { .name = "abx80x", .driver_data = ABX80X }, { .name = "ab0801", .driver_data = AB0801 }, @@ -812,9 +837,11 @@ MODULE_DEVICE_TABLE(i2c, abx80x_id); static int abx80x_probe(struct i2c_client *client) { + struct regmap *regmap; struct device_node *np = client->dev.of_node; struct abx80x_priv *priv; - int i, data, err, trickle_cfg = -EINVAL; + int i, err, trickle_cfg = -EINVAL; + unsigned int data; char buf[7]; unsigned int part = (uintptr_t)i2c_get_match_data(client); unsigned int partnumber; @@ -826,8 +853,30 @@ static int abx80x_probe(struct i2c_client *client) if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; - err = i2c_smbus_read_i2c_block_data(client, ABX8XX_REG_ID0, - sizeof(buf), buf); + regmap = devm_regmap_init_i2c(client, &abx80x_regmap_config_i2c); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Unable to allocate regmap\n"); + return PTR_ERR(regmap); + } + + priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->rtc = devm_rtc_allocate_device(&client->dev); + if (IS_ERR(priv->rtc)) + return PTR_ERR(priv->rtc); + + priv->rtc->ops = &abx80x_rtc_ops; + priv->irq = client->irq; + priv->regmap = regmap; + err = devm_mutex_init(&client->dev, &priv->lock); + if (err) + return err; + + dev_set_drvdata(&client->dev, priv); + + err = regmap_bulk_read(regmap, ABX8XX_REG_ID0, buf, sizeof(buf)); if (err < 0) { dev_err(&client->dev, "Unable to read partnumber\n"); return -EIO; @@ -842,16 +891,14 @@ static int abx80x_probe(struct i2c_client *client) dev_info(&client->dev, "model %04x, revision %u.%u, lot %x, wafer %x, uid %x\n", partnumber, majrev, minrev, lot, wafer, uid); - data = i2c_smbus_read_byte_data(client, ABX8XX_REG_CTRL1); - if (data < 0) { + err = regmap_read(regmap, ABX8XX_REG_CTRL1, &data); + if (err < 0) { dev_err(&client->dev, "Unable to read control register\n"); return -EIO; } - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_CTRL1, - ((data & ~(ABX8XX_CTRL_12_24 | - ABX8XX_CTRL_ARST)) | - ABX8XX_CTRL_WRITE)); + err = regmap_write(regmap, ABX8XX_REG_CTRL1, + (data & ~(ABX8XX_CTRL_12_24 | ABX8XX_CTRL_ARST)) | ABX8XX_CTRL_WRITE); if (err < 0) { dev_err(&client->dev, "Unable to write control register\n"); return -EIO; @@ -865,15 +912,15 @@ static int abx80x_probe(struct i2c_client *client) * register is set. RV-1805-C3 datasheet indicates that * the bit should be cleared in section 11h - Control2. */ - data = i2c_smbus_read_byte_data(client, ABX8XX_REG_CTRL2); - if (data < 0) { + err = regmap_read(regmap, ABX8XX_REG_CTRL2, &data); + if (err < 0) { dev_err(&client->dev, "Unable to read control2 register\n"); return -EIO; } - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_CTRL2, - data & ~ABX8XX_CTRL2_RSVD); + err = regmap_write(regmap, ABX8XX_REG_CTRL2, + data & ~ABX8XX_CTRL2_RSVD); if (err < 0) { dev_err(&client->dev, "Unable to write control2 register\n"); @@ -885,8 +932,8 @@ static int abx80x_probe(struct i2c_client *client) * 10pin package and the EXTI input is not present. * Disable it to avoid leakage. */ - data = i2c_smbus_read_byte_data(client, ABX8XX_REG_OUT_CTRL); - if (data < 0) { + err = regmap_read(regmap, ABX8XX_REG_OUT_CTRL, &data); + if (err < 0) { dev_err(&client->dev, "Unable to read output control register\n"); return -EIO; @@ -896,11 +943,11 @@ static int abx80x_probe(struct i2c_client *client) * Write the configuration key register to enable access to * the config2 register */ - if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_MISC) < 0) + if (abx80x_write_config_key(&client->dev, ABX8XX_CFG_KEY_MISC) < 0) return -EIO; - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_OUT_CTRL, - data | ABX8XX_OUT_CTRL_EXDS); + err = regmap_write(regmap, ABX8XX_REG_OUT_CTRL, + data | ABX8XX_OUT_CTRL_EXDS); if (err < 0) { dev_err(&client->dev, "Unable to write output control register\n"); @@ -928,55 +975,37 @@ static int abx80x_probe(struct i2c_client *client) } if (np && abx80x_caps[part].has_tc) - trickle_cfg = abx80x_dt_trickle_cfg(client); + trickle_cfg = abx80x_dt_trickle_cfg(&client->dev); if (trickle_cfg > 0) { dev_info(&client->dev, "Enabling trickle charger: %02x\n", trickle_cfg); - abx80x_enable_trickle_charger(client, trickle_cfg); + abx80x_enable_trickle_charger(&client->dev, trickle_cfg); } - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_CD_TIMER_CTL, - BIT(2)); + err = regmap_write(regmap, ABX8XX_REG_CD_TIMER_CTL, BIT(2)); if (err) return err; - priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); - if (priv == NULL) - return -ENOMEM; - - priv->rtc = devm_rtc_allocate_device(&client->dev); - if (IS_ERR(priv->rtc)) - return PTR_ERR(priv->rtc); - - priv->rtc->ops = &abx80x_rtc_ops; - priv->client = client; - priv->irq = client->irq; - err = devm_mutex_init(&client->dev, &priv->lock); - if (err) - return err; - - i2c_set_clientdata(client, priv); - /* Disable unused interrupts */ - data = i2c_smbus_read_byte_data(client, ABX8XX_REG_IRQ); - if (data < 0) { + err = regmap_read(regmap, ABX8XX_REG_IRQ, &data); + if (err < 0) { dev_err(&client->dev, "Unable to read irq register\n"); return -EIO; } - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_IRQ, - data & ~(ABX8XX_IRQ_EX1E | - ABX8XX_IRQ_EX2E | - ABX8XX_IRQ_TIE | - ABX8XX_IRQ_BLIE)); + err = regmap_write(regmap, ABX8XX_REG_IRQ, + data & ~(ABX8XX_IRQ_EX1E | + ABX8XX_IRQ_EX2E | + ABX8XX_IRQ_TIE | + ABX8XX_IRQ_BLIE)); if (err < 0) { dev_err(&client->dev, "Unable to write irq register\n"); return -EIO; } - data = i2c_smbus_read_byte_data(client, ABX8XX_REG_OSC); - if (data < 0) { + err = regmap_read(regmap, ABX8XX_REG_OSC, &data); + if (err < 0) { dev_err(&client->dev, "Unable to read Oscillator Control register\n"); return -EIO; } @@ -985,16 +1014,16 @@ static int abx80x_probe(struct i2c_client *client) if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - err = i2c_smbus_write_byte_data(client, ABX8XX_REG_OSC, - data & ~(ABX8XX_OSC_ACIE | - ABX8XX_OSC_OFIE)); + err = regmap_write(regmap, ABX8XX_REG_OSC, + data & ~(ABX8XX_OSC_ACIE | + ABX8XX_OSC_OFIE)); if (err < 0) { dev_err(&client->dev, "Unable to write Oscillator Control register\n"); return -EIO; } if (abx80x_caps[part].has_wdog) { - err = abx80x_setup_watchdog(priv); + err = abx80x_setup_watchdog(&client->dev); if (err) return err; } @@ -1009,7 +1038,7 @@ static int abx80x_probe(struct i2c_client *client) abx80x_handle_irq, IRQF_SHARED | IRQF_ONESHOT, "abx8xx", - client); + &client->dev); if (err) { dev_err(&client->dev, "unable to request IRQ, alarms disabled\n"); priv->irq = 0; From 0ba70c2b1e3e394bfea1258f27323a9b3a2f6025 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:36 +0200 Subject: [PATCH 20/89] rtc: abx80x: replace read-modify-write pattern with regmap helpers Before introducing the regmap usage in the driver, updating specific bits of a register was performed using the read-modify-write pattern. Now, the functions regmap_update_bits() and regmap_write_bits() can be used to simplify the code. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-8-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 134 ++++++++++----------------------------- 1 file changed, 34 insertions(+), 100 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index d41656a4d518..53fc788cbf96 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -232,7 +232,6 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct abx80x_priv *priv = dev_get_drvdata(dev); unsigned char buf[8]; - unsigned int flags; int err; if (tm->tm_year < 100) @@ -257,18 +256,11 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) } /* Clear the OF bit of Oscillator Status Register */ - err = regmap_read(priv->regmap, ABX8XX_REG_OSS, &flags); + err = regmap_update_bits(priv->regmap, ABX8XX_REG_OSS, ABX8XX_OSS_OF, 0); if (err < 0) - return err; - - err = regmap_write(priv->regmap, ABX8XX_REG_OSS, - flags & ~ABX8XX_OSS_OF); - if (err < 0) { dev_err(dev, "Unable to write oscillator status register\n"); - return err; - } - return 0; + return err; } static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) @@ -378,7 +370,6 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, { struct abx80x_priv *priv = dev_get_drvdata(dev); unsigned int flags = 0; - int retval; if ((autocalibration != 0) && (autocalibration != 1024) && (autocalibration != 512)) { @@ -386,12 +377,6 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, return -EINVAL; } - guard(mutex)(&priv->lock); - - retval = regmap_read(priv->regmap, ABX8XX_REG_OSC, &flags); - if (retval < 0) - return retval; - if (autocalibration == 0) { flags &= ~(ABX8XX_OSC_ACAL_512 | ABX8XX_OSC_ACAL_1024); } else if (autocalibration == 1024) { @@ -403,13 +388,15 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, flags |= (ABX8XX_OSC_ACAL_1024 | ABX8XX_OSC_ACAL_512); } + guard(mutex)(&priv->lock); + /* Unlock write access to Oscillator Control Register */ if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - retval = regmap_write(priv->regmap, ABX8XX_REG_OSC, flags); - - return retval; + return regmap_write_bits(priv->regmap, ABX8XX_REG_OSC, + ABX8XX_OSC_ACAL_1024 | ABX8XX_OSC_ACAL_512, + flags); } static int abx80x_rtc_get_autocalibration(struct device *dev) @@ -473,7 +460,6 @@ static ssize_t oscillator_store(struct device *dev, { struct abx80x_priv *priv = dev_get_drvdata(dev->parent); int retval, rc_mode = 0; - unsigned int flags; if (strncmp(buf, "rc", 2) == 0) { rc_mode = 1; @@ -486,24 +472,14 @@ static ssize_t oscillator_store(struct device *dev, guard(mutex)(&priv->lock); - retval = regmap_read(priv->regmap, ABX8XX_REG_OSC, &flags); - if (retval < 0) - return retval; - - if (rc_mode == 0) - flags &= ~(ABX8XX_OSC_OSEL); - else - flags |= (ABX8XX_OSC_OSEL); - /* Unlock write access on Oscillator Control register */ if (abx80x_write_config_key(dev->parent, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - retval = regmap_write(priv->regmap, ABX8XX_REG_OSC, flags); - if (retval < 0) { + retval = regmap_write_bits(priv->regmap, ABX8XX_REG_OSC, ABX8XX_OSC_OSEL, + rc_mode == 0 ? 0 : (ABX8XX_OSC_OSEL)); + if (retval < 0) dev_err(dev, "Failed to write Oscillator Control register\n"); - return retval; - } return retval ? retval : count; } @@ -573,18 +549,11 @@ static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) case RTC_VL_CLR: scoped_guard(mutex, &priv->lock) { - err = regmap_read(priv->regmap, ABX8XX_REG_STATUS, &status); - if (err < 0) - return err; - - status &= ~ABX8XX_STATUS_BLF; - - err = regmap_write(priv->regmap, ABX8XX_REG_STATUS, status); - if (err < 0) - return err; + err = regmap_update_bits(priv->regmap, ABX8XX_REG_STATUS, + ABX8XX_STATUS_BLF, 0); } - return 0; + return err; default: return -ENOIOCTLCMD; @@ -841,7 +810,6 @@ static int abx80x_probe(struct i2c_client *client) struct device_node *np = client->dev.of_node; struct abx80x_priv *priv; int i, err, trickle_cfg = -EINVAL; - unsigned int data; char buf[7]; unsigned int part = (uintptr_t)i2c_get_match_data(client); unsigned int partnumber; @@ -891,14 +859,9 @@ static int abx80x_probe(struct i2c_client *client) dev_info(&client->dev, "model %04x, revision %u.%u, lot %x, wafer %x, uid %x\n", partnumber, majrev, minrev, lot, wafer, uid); - err = regmap_read(regmap, ABX8XX_REG_CTRL1, &data); - if (err < 0) { - dev_err(&client->dev, "Unable to read control register\n"); - return -EIO; - } - - err = regmap_write(regmap, ABX8XX_REG_CTRL1, - (data & ~(ABX8XX_CTRL_12_24 | ABX8XX_CTRL_ARST)) | ABX8XX_CTRL_WRITE); + err = regmap_update_bits(regmap, ABX8XX_REG_CTRL1, + ABX8XX_CTRL_12_24 | ABX8XX_CTRL_ARST | ABX8XX_CTRL_WRITE, + ABX8XX_CTRL_WRITE); if (err < 0) { dev_err(&client->dev, "Unable to write control register\n"); return -EIO; @@ -912,30 +875,10 @@ static int abx80x_probe(struct i2c_client *client) * register is set. RV-1805-C3 datasheet indicates that * the bit should be cleared in section 11h - Control2. */ - err = regmap_read(regmap, ABX8XX_REG_CTRL2, &data); + err = regmap_update_bits(regmap, ABX8XX_REG_CTRL2, + ABX8XX_CTRL2_RSVD, 0); if (err < 0) { - dev_err(&client->dev, - "Unable to read control2 register\n"); - return -EIO; - } - - err = regmap_write(regmap, ABX8XX_REG_CTRL2, - data & ~ABX8XX_CTRL2_RSVD); - if (err < 0) { - dev_err(&client->dev, - "Unable to write control2 register\n"); - return -EIO; - } - - /* - * Avoid extra power leakage. The RV1805 uses smaller - * 10pin package and the EXTI input is not present. - * Disable it to avoid leakage. - */ - err = regmap_read(regmap, ABX8XX_REG_OUT_CTRL, &data); - if (err < 0) { - dev_err(&client->dev, - "Unable to read output control register\n"); + dev_err(&client->dev, "Unable to write control2 register\n"); return -EIO; } @@ -946,8 +889,14 @@ static int abx80x_probe(struct i2c_client *client) if (abx80x_write_config_key(&client->dev, ABX8XX_CFG_KEY_MISC) < 0) return -EIO; - err = regmap_write(regmap, ABX8XX_REG_OUT_CTRL, - data | ABX8XX_OUT_CTRL_EXDS); + /* + * Avoid extra power leakage. The RV1805 uses smaller + * 10pin package and the EXTI input is not present. + * Disable it to avoid leakage. + */ + err = regmap_write_bits(regmap, ABX8XX_REG_OUT_CTRL, + ABX8XX_OUT_CTRL_EXDS, + ABX8XX_OUT_CTRL_EXDS); if (err < 0) { dev_err(&client->dev, "Unable to write output control register\n"); @@ -988,25 +937,11 @@ static int abx80x_probe(struct i2c_client *client) return err; /* Disable unused interrupts */ - err = regmap_read(regmap, ABX8XX_REG_IRQ, &data); + err = regmap_update_bits(regmap, ABX8XX_REG_IRQ, + ABX8XX_IRQ_EX1E | ABX8XX_IRQ_EX2E | + ABX8XX_IRQ_TIE | ABX8XX_IRQ_BLIE, 0); if (err < 0) { - dev_err(&client->dev, "Unable to read irq register\n"); - return -EIO; - } - - err = regmap_write(regmap, ABX8XX_REG_IRQ, - data & ~(ABX8XX_IRQ_EX1E | - ABX8XX_IRQ_EX2E | - ABX8XX_IRQ_TIE | - ABX8XX_IRQ_BLIE)); - if (err < 0) { - dev_err(&client->dev, "Unable to write irq register\n"); - return -EIO; - } - - err = regmap_read(regmap, ABX8XX_REG_OSC, &data); - if (err < 0) { - dev_err(&client->dev, "Unable to read Oscillator Control register\n"); + dev_err(&client->dev, "Unable to update irq register\n"); return -EIO; } @@ -1014,11 +949,10 @@ static int abx80x_probe(struct i2c_client *client) if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) return -EIO; - err = regmap_write(regmap, ABX8XX_REG_OSC, - data & ~(ABX8XX_OSC_ACIE | - ABX8XX_OSC_OFIE)); + err = regmap_write_bits(regmap, ABX8XX_REG_OSC, + ABX8XX_OSC_ACIE | ABX8XX_OSC_OFIE, 0); if (err < 0) { - dev_err(&client->dev, "Unable to write Oscillator Control register\n"); + dev_err(&client->dev, "Unable to update Oscillator Control register\n"); return -EIO; } From 3fcf5a97957b8b5f5b0c2d95a0666ebdef7687d0 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:37 +0200 Subject: [PATCH 21/89] rtc: abx80x: create abx80x_i2c_probe() Move the I2C-specific code from abx80x_probe() to the newly created function. This is a preparation for introducing the support for ABX81X RTCs which use SPI interface. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-9-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 417 +++++++++++++++++++++------------------ 1 file changed, 228 insertions(+), 189 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 53fc788cbf96..3f0f8d7f8761 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -780,6 +780,181 @@ static const struct regmap_access_table abx80x_write_table = { .n_no_ranges = ARRAY_SIZE(abx80x_no_write_ranges), }; +static int abx80x_probe(struct device *dev, struct regmap *regmap, int irq, + struct device_node *np, unsigned int part) +{ + struct abx80x_priv *priv; + int i, err, trickle_cfg = -EINVAL; + char buf[7]; + unsigned int partnumber; + unsigned int majrev, minrev; + unsigned int lot; + unsigned int wafer; + unsigned int uid; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + priv->rtc = devm_rtc_allocate_device(dev); + if (IS_ERR(priv->rtc)) + return PTR_ERR(priv->rtc); + + priv->rtc->ops = &abx80x_rtc_ops; + priv->irq = irq; + priv->regmap = regmap; + err = devm_mutex_init(dev, &priv->lock); + if (err) + return err; + + dev_set_drvdata(dev, priv); + + err = regmap_bulk_read(regmap, ABX8XX_REG_ID0, buf, sizeof(buf)); + if (err < 0) { + dev_err(dev, "Unable to read partnumber\n"); + return -EIO; + } + + partnumber = (buf[0] << 8) | buf[1]; + majrev = buf[2] >> 3; + minrev = buf[2] & 0x7; + lot = ((buf[4] & 0x80) << 2) | ((buf[6] & 0x80) << 1) | buf[3]; + uid = ((buf[4] & 0x7f) << 8) | buf[5]; + wafer = (buf[6] & 0x7c) >> 2; + dev_info(dev, "model %04x, revision %u.%u, lot %x, wafer %x, uid %x\n", + partnumber, majrev, minrev, lot, wafer, uid); + + err = regmap_update_bits(regmap, ABX8XX_REG_CTRL1, + ABX8XX_CTRL_12_24 | ABX8XX_CTRL_ARST | ABX8XX_CTRL_WRITE, + ABX8XX_CTRL_WRITE); + if (err < 0) { + dev_err(dev, "Unable to write control register\n"); + return -EIO; + } + + /* Configure RV1805 specifics */ + if (part == RV1805) { + /* + * Avoid accidentally entering test mode. This can happen + * on the RV1805 in case the reserved bit 5 in control2 + * register is set. RV-1805-C3 datasheet indicates that + * the bit should be cleared in section 11h - Control2. + */ + err = regmap_update_bits(regmap, ABX8XX_REG_CTRL2, + ABX8XX_CTRL2_RSVD, 0); + if (err < 0) { + dev_err(dev, "Unable to write control2 register\n"); + return -EIO; + } + + /* + * Write the configuration key register to enable access to + * the config2 register + */ + if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_MISC) < 0) + return -EIO; + + /* + * Avoid extra power leakage. The RV1805 uses smaller + * 10pin package and the EXTI input is not present. + * Disable it to avoid leakage. + */ + err = regmap_write_bits(regmap, ABX8XX_REG_OUT_CTRL, + ABX8XX_OUT_CTRL_EXDS, + ABX8XX_OUT_CTRL_EXDS); + if (err < 0) { + dev_err(dev, + "Unable to write output control register\n"); + return -EIO; + } + } + + /* part autodetection */ + if (part == ABX80X) { + for (i = 0; abx80x_caps[i].pn; i++) + if (partnumber == abx80x_caps[i].pn) + break; + if (abx80x_caps[i].pn == 0) { + dev_err(dev, "Unknown part: %04x\n", partnumber); + return -EINVAL; + } + part = i; + } + + if (partnumber != abx80x_caps[part].pn) { + dev_err(dev, "partnumber mismatch %04x != %04x\n", + partnumber, abx80x_caps[part].pn); + return -EINVAL; + } + + if (np && abx80x_caps[part].has_tc) + trickle_cfg = abx80x_dt_trickle_cfg(dev); + + if (trickle_cfg > 0) { + dev_info(dev, "Enabling trickle charger: %02x\n", trickle_cfg); + abx80x_enable_trickle_charger(dev, trickle_cfg); + } + + err = regmap_write(regmap, ABX8XX_REG_CD_TIMER_CTL, BIT(2)); + if (err) + return err; + + /* Disable unused interrupts */ + err = regmap_update_bits(regmap, ABX8XX_REG_IRQ, + ABX8XX_IRQ_EX1E | ABX8XX_IRQ_EX2E | + ABX8XX_IRQ_TIE | ABX8XX_IRQ_BLIE, 0); + if (err < 0) { + dev_err(dev, "Unable to update irq register\n"); + return -EIO; + } + + /* Unlock write access to Oscillator Control Register */ + if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_OSC) < 0) + return -EIO; + + err = regmap_write_bits(regmap, ABX8XX_REG_OSC, + ABX8XX_OSC_ACIE | ABX8XX_OSC_OFIE, 0); + if (err < 0) { + dev_err(dev, "Unable to update Oscillator Control register\n"); + return -EIO; + } + + if (abx80x_caps[part].has_wdog) { + err = abx80x_setup_watchdog(dev); + if (err) + return err; + } + + err = abx80x_setup_nvmem(priv); + if (err) + return err; + + if (priv->irq > 0) { + dev_info(dev, "IRQ %d supplied\n", priv->irq); + err = devm_request_threaded_irq(dev, priv->irq, NULL, + abx80x_handle_irq, + IRQF_SHARED | IRQF_ONESHOT, + "abx8xx", + dev); + if (err) { + dev_err(dev, "unable to request IRQ, alarms disabled\n"); + priv->irq = 0; + } + } + if (priv->irq <= 0) + clear_bit(RTC_FEATURE_ALARM, priv->rtc->features); + + err = rtc_add_group(priv->rtc, &rtc_calib_attr_group); + if (err) { + dev_err(dev, "Failed to create sysfs group: %d\n", err); + return err; + } + + return devm_rtc_register_device(priv->rtc); +} + +#if IS_ENABLED(CONFIG_I2C) + static const struct regmap_config abx80x_regmap_config_i2c = { .reg_bits = 8, .val_bits = 8, @@ -804,193 +979,6 @@ static const struct i2c_device_id abx80x_id[] = { }; MODULE_DEVICE_TABLE(i2c, abx80x_id); -static int abx80x_probe(struct i2c_client *client) -{ - struct regmap *regmap; - struct device_node *np = client->dev.of_node; - struct abx80x_priv *priv; - int i, err, trickle_cfg = -EINVAL; - char buf[7]; - unsigned int part = (uintptr_t)i2c_get_match_data(client); - unsigned int partnumber; - unsigned int majrev, minrev; - unsigned int lot; - unsigned int wafer; - unsigned int uid; - - if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) - return -ENODEV; - - regmap = devm_regmap_init_i2c(client, &abx80x_regmap_config_i2c); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Unable to allocate regmap\n"); - return PTR_ERR(regmap); - } - - priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL); - if (priv == NULL) - return -ENOMEM; - - priv->rtc = devm_rtc_allocate_device(&client->dev); - if (IS_ERR(priv->rtc)) - return PTR_ERR(priv->rtc); - - priv->rtc->ops = &abx80x_rtc_ops; - priv->irq = client->irq; - priv->regmap = regmap; - err = devm_mutex_init(&client->dev, &priv->lock); - if (err) - return err; - - dev_set_drvdata(&client->dev, priv); - - err = regmap_bulk_read(regmap, ABX8XX_REG_ID0, buf, sizeof(buf)); - if (err < 0) { - dev_err(&client->dev, "Unable to read partnumber\n"); - return -EIO; - } - - partnumber = (buf[0] << 8) | buf[1]; - majrev = buf[2] >> 3; - minrev = buf[2] & 0x7; - lot = ((buf[4] & 0x80) << 2) | ((buf[6] & 0x80) << 1) | buf[3]; - uid = ((buf[4] & 0x7f) << 8) | buf[5]; - wafer = (buf[6] & 0x7c) >> 2; - dev_info(&client->dev, "model %04x, revision %u.%u, lot %x, wafer %x, uid %x\n", - partnumber, majrev, minrev, lot, wafer, uid); - - err = regmap_update_bits(regmap, ABX8XX_REG_CTRL1, - ABX8XX_CTRL_12_24 | ABX8XX_CTRL_ARST | ABX8XX_CTRL_WRITE, - ABX8XX_CTRL_WRITE); - if (err < 0) { - dev_err(&client->dev, "Unable to write control register\n"); - return -EIO; - } - - /* Configure RV1805 specifics */ - if (part == RV1805) { - /* - * Avoid accidentally entering test mode. This can happen - * on the RV1805 in case the reserved bit 5 in control2 - * register is set. RV-1805-C3 datasheet indicates that - * the bit should be cleared in section 11h - Control2. - */ - err = regmap_update_bits(regmap, ABX8XX_REG_CTRL2, - ABX8XX_CTRL2_RSVD, 0); - if (err < 0) { - dev_err(&client->dev, "Unable to write control2 register\n"); - return -EIO; - } - - /* - * Write the configuration key register to enable access to - * the config2 register - */ - if (abx80x_write_config_key(&client->dev, ABX8XX_CFG_KEY_MISC) < 0) - return -EIO; - - /* - * Avoid extra power leakage. The RV1805 uses smaller - * 10pin package and the EXTI input is not present. - * Disable it to avoid leakage. - */ - err = regmap_write_bits(regmap, ABX8XX_REG_OUT_CTRL, - ABX8XX_OUT_CTRL_EXDS, - ABX8XX_OUT_CTRL_EXDS); - if (err < 0) { - dev_err(&client->dev, - "Unable to write output control register\n"); - return -EIO; - } - } - - /* part autodetection */ - if (part == ABX80X) { - for (i = 0; abx80x_caps[i].pn; i++) - if (partnumber == abx80x_caps[i].pn) - break; - if (abx80x_caps[i].pn == 0) { - dev_err(&client->dev, "Unknown part: %04x\n", - partnumber); - return -EINVAL; - } - part = i; - } - - if (partnumber != abx80x_caps[part].pn) { - dev_err(&client->dev, "partnumber mismatch %04x != %04x\n", - partnumber, abx80x_caps[part].pn); - return -EINVAL; - } - - if (np && abx80x_caps[part].has_tc) - trickle_cfg = abx80x_dt_trickle_cfg(&client->dev); - - if (trickle_cfg > 0) { - dev_info(&client->dev, "Enabling trickle charger: %02x\n", - trickle_cfg); - abx80x_enable_trickle_charger(&client->dev, trickle_cfg); - } - - err = regmap_write(regmap, ABX8XX_REG_CD_TIMER_CTL, BIT(2)); - if (err) - return err; - - /* Disable unused interrupts */ - err = regmap_update_bits(regmap, ABX8XX_REG_IRQ, - ABX8XX_IRQ_EX1E | ABX8XX_IRQ_EX2E | - ABX8XX_IRQ_TIE | ABX8XX_IRQ_BLIE, 0); - if (err < 0) { - dev_err(&client->dev, "Unable to update irq register\n"); - return -EIO; - } - - /* Unlock write access to Oscillator Control Register */ - if (abx80x_write_config_key(client, ABX8XX_CFG_KEY_OSC) < 0) - return -EIO; - - err = regmap_write_bits(regmap, ABX8XX_REG_OSC, - ABX8XX_OSC_ACIE | ABX8XX_OSC_OFIE, 0); - if (err < 0) { - dev_err(&client->dev, "Unable to update Oscillator Control register\n"); - return -EIO; - } - - if (abx80x_caps[part].has_wdog) { - err = abx80x_setup_watchdog(&client->dev); - if (err) - return err; - } - - err = abx80x_setup_nvmem(priv); - if (err) - return err; - - if (priv->irq > 0) { - dev_info(&client->dev, "IRQ %d supplied\n", priv->irq); - err = devm_request_threaded_irq(&client->dev, priv->irq, NULL, - abx80x_handle_irq, - IRQF_SHARED | IRQF_ONESHOT, - "abx8xx", - &client->dev); - if (err) { - dev_err(&client->dev, "unable to request IRQ, alarms disabled\n"); - priv->irq = 0; - } - } - if (priv->irq <= 0) - clear_bit(RTC_FEATURE_ALARM, priv->rtc->features); - - err = rtc_add_group(priv->rtc, &rtc_calib_attr_group); - if (err) { - dev_err(&client->dev, "Failed to create sysfs group: %d\n", - err); - return err; - } - - return devm_rtc_register_device(priv->rtc); -} - #ifdef CONFIG_OF static const struct of_device_id abx80x_of_match[] = { { @@ -1038,16 +1026,67 @@ static const struct of_device_id abx80x_of_match[] = { MODULE_DEVICE_TABLE(of, abx80x_of_match); #endif +static int abx80x_i2c_probe(struct i2c_client *client) +{ + unsigned int part = (uintptr_t)i2c_get_match_data(client); + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &abx80x_regmap_config_i2c); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Unable to allocate regmap\n"); + return PTR_ERR(regmap); + } + + return abx80x_probe(&client->dev, regmap, client->irq, + client->dev.of_node, part); +} + static struct i2c_driver abx80x_driver = { .driver = { .name = "rtc-abx80x", .of_match_table = of_match_ptr(abx80x_of_match), }, - .probe = abx80x_probe, + .probe = abx80x_i2c_probe, .id_table = abx80x_id, }; -module_i2c_driver(abx80x_driver); +static int abx80x_register_driver(void) +{ + return i2c_add_driver(&abx80x_driver); +} + +static void abx80x_unregister_driver(void) +{ + i2c_del_driver(&abx80x_driver); +} + +#else + +static int abx80x_register_driver(void) +{ + return 0; +} + +static void abx80x_unregister_driver(void) +{ +} + +#endif /* IS_ENABLED(CONFIG_I2C) */ + +static int __init abx80x_init(void) +{ + return abx80x_register_driver(); +} +module_init(abx80x_init); + +static void __exit abx80x_exit(void) +{ + abx80x_unregister_driver(); +} +module_exit(abx80x_exit); MODULE_AUTHOR("Philippe De Muyter "); MODULE_AUTHOR("Alexandre Belloni "); From 8a017ab98412c997b9ce6b57a6941e229bf7f89a Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 16 Sep 2026 22:51:38 +0200 Subject: [PATCH 22/89] rtc: abx80x: add support for ABX81X Abracon ABX81X is a family of RTCs with SPI interface featuring the same functionalities as the ABX80X I2C clocks. Signed-off-by: Antoni Pokusinski Link: https://patch.msgid.link/20260916205138.16771-10-apokusinski01@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 26 +++++----- drivers/rtc/rtc-abx80x.c | 106 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 7be1b1b7070a..47505b62e1b4 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -212,18 +212,6 @@ config RTC_DRV_ABEOZ9 This driver can also be built as a module. If so, the module will be called rtc-ab-e0z9. -config RTC_DRV_ABX80X - tristate "Abracon ABx80x" - select WATCHDOG_CORE if WATCHDOG - select REGMAP_I2C - help - If you say yes here you get support for Abracon AB080X and AB180X - families of ultra-low-power battery- and capacitor-backed real-time - clock chips. - - This driver can also be built as a module. If so, the module - will be called rtc-abx80x. - config RTC_DRV_AC100 tristate "X-Powers AC100" depends on MFD_AC100 @@ -955,6 +943,20 @@ config RTC_I2C_AND_SPI comment "SPI and I2C RTC drivers" +config RTC_DRV_ABX80X + tristate "Abracon ABx80x" + depends on RTC_I2C_AND_SPI + select WATCHDOG_CORE if WATCHDOG + select REGMAP_I2C if I2C + select REGMAP_SPI if SPI_MASTER + help + If you say yes here you get support for Abracon AB080X, AB180X, + AB081X and AB181X families of ultra-low-power battery- and + capacitor-backed real-time clock chips. + + This driver can also be built as a module. If so, the module + will be called rtc-abx80x. + config RTC_DRV_DS3232 tristate "Dallas/Maxim DS3232/DS3234" depends on RTC_I2C_AND_SPI diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 3f0f8d7f8761..718e6b8555f5 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* - * A driver for the I2C members of the Abracon AB x8xx RTC family, - * and compatible: AB 1805 and AB 0805 + * A driver for the I2C and SPI members of the Abracon AB x8xx RTC family, + * and compatible: AB 1805, AB 0805, AB 1815 and AB 0815 * * Copyright 2014-2015 Macq S.A. * @@ -19,6 +19,7 @@ #include #include #include +#include #include #define ABX8XX_REG_HTH 0x00 @@ -109,8 +110,8 @@ static u8 trickle_resistors[] = {0, 3, 6, 11}; -enum abx80x_chip {AB0801, AB0803, AB0804, AB0805, - AB1801, AB1803, AB1804, AB1805, RV1805, ABX80X}; +enum abx80x_chip {AB0801, AB0803, AB0804, AB0805, AB0815, + AB1801, AB1803, AB1804, AB1805, AB1815, RV1805, ABX80X}; struct abx80x_cap { u16 pn; @@ -123,10 +124,12 @@ static struct abx80x_cap abx80x_caps[] = { [AB0803] = {.pn = 0x0803}, [AB0804] = {.pn = 0x0804, .has_tc = true, .has_wdog = true}, [AB0805] = {.pn = 0x0805, .has_tc = true, .has_wdog = true}, + [AB0815] = {.pn = 0x0815, .has_tc = true, .has_wdog = true}, [AB1801] = {.pn = 0x1801}, [AB1803] = {.pn = 0x1803}, [AB1804] = {.pn = 0x1804, .has_tc = true, .has_wdog = true}, [AB1805] = {.pn = 0x1805, .has_tc = true, .has_wdog = true}, + [AB1815] = {.pn = 0x1815, .has_tc = true, .has_wdog = true}, [RV1805] = {.pn = 0x1805, .has_tc = true, .has_wdog = true}, [ABX80X] = {.pn = 0} }; @@ -1076,14 +1079,107 @@ static void abx80x_unregister_driver(void) #endif /* IS_ENABLED(CONFIG_I2C) */ +#if IS_ENABLED(CONFIG_SPI_MASTER) + +static const struct regmap_config abx80x_regmap_config_spi = { + .reg_bits = 8, + .val_bits = 8, + .max_register = ABX8XX_SRAM_BASE + ABX8XX_SRAM_WIN_SIZE - 1, + + .rd_table = &abx80x_read_table, + .wr_table = &abx80x_write_table, + + .write_flag_mask = BIT(7), +}; + +static const struct spi_device_id abx81x_id[] = { + { "ab0815", AB0815 }, + { "ab1815", AB1815 }, + { } +}; +MODULE_DEVICE_TABLE(spi, abx81x_id); + +#ifdef CONFIG_OF +static const struct of_device_id abx81x_of_match[] = { + { + .compatible = "abracon,ab0815", + .data = (void *)AB0815 + }, + { + .compatible = "abracon,ab1815", + .data = (void *)AB1815 + }, + { } +}; +MODULE_DEVICE_TABLE(of, abx81x_of_match); +#endif + +static int abx81x_spi_probe(struct spi_device *spi) +{ + unsigned int part = (uintptr_t)spi_get_device_match_data(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &abx80x_regmap_config_spi); + if (IS_ERR(regmap)) { + dev_err(&spi->dev, "Unable to allocate regmap\n"); + return PTR_ERR(regmap); + } + + return abx80x_probe(&spi->dev, regmap, spi->irq, + spi->dev.of_node, part); +} + +static struct spi_driver abx81x_driver = { + .driver = { + .name = "rtc-abx81x", + .of_match_table = of_match_ptr(abx81x_of_match), + }, + .probe = abx81x_spi_probe, + .id_table = abx81x_id, +}; + +static int abx81x_register_driver(void) +{ + return spi_register_driver(&abx81x_driver); +} + +static void abx81x_unregister_driver(void) +{ + spi_unregister_driver(&abx81x_driver); +} + +#else + +static int abx81x_register_driver(void) +{ + return 0; +} + +static void abx81x_unregister_driver(void) +{ +} + +#endif /* IS_ENABLED(CONFIG_SPI_MASTER) */ + static int __init abx80x_init(void) { - return abx80x_register_driver(); + int ret; + + ret = abx80x_register_driver(); + if (ret) + return ret; + + ret = abx81x_register_driver(); + if (ret) + abx80x_unregister_driver(); + + return ret; } module_init(abx80x_init); static void __exit abx80x_exit(void) { + abx81x_unregister_driver(); abx80x_unregister_driver(); } module_exit(abx80x_exit); From 3f67038df8b5cba07254d1cff60d73e988b49ab7 Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Thu, 15 Oct 2026 13:30:28 -0400 Subject: [PATCH 23/89] rtc: cmos: Disable ACPI RTC event before handling it Users of RTC_UIE_ON can stop receiving update interrupts when rtc-cmos handles alarms through ACPI. In one 50-run test, 40 runs passed and 10 timed out after receiving between one and four of five expected events. The ACPI ff_rt_clk counter stopped at 218, exactly matching the number of events delivered to userspace, and reported: 218 STS disabled unmasked rtc_handler() runs in the threaded ACPI SCI handler and calls cmos_interrupt(), which schedules rtc->irqwork. The work can run before rtc_handler() returns: ACPI SCI thread rtc->irqwork cmos_interrupt() schedule_work() __rtc_set_alarm() rtc_wake_on() acpi_clear_event() acpi_disable_event() The SCI thread then disables the event that the worker enabled for the next alarm. That alarm sets its status while delivery remains disabled, so no update interrupt reaches userspace. Clear and disable the current ACPI RTC event before cmos_interrupt() schedules the work. The worker can then enable the next event without the handler undoing it. With this ordering, 150 runs delivered all 750 events without a stall. Fixes: 311ee9c151ad ("rtc: cmos: allow using ACPI for RTC alarm instead of HPET") Cc: stable@vger.kernel.org Signed-off-by: Gregory Price (Meta) Link: https://patch.msgid.link/20261015173029.3091872-2-gourry@gourry.net Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-cmos.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index fa04ece151b8..fe514b1aa343 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -758,7 +758,9 @@ static u32 rtc_handler(void *context) unsigned char rtc_intr; unsigned long flags; - + /* Quiesce this event before rtc->irqwork can arm the next one. */ + acpi_clear_event(ACPI_EVENT_RTC); + acpi_disable_event(ACPI_EVENT_RTC, 0); /* * Always update rtc irq when ACPI is used as RTC Alarm. * Or else, ACPI SCI is enabled during suspend/resume only, @@ -781,8 +783,6 @@ static u32 rtc_handler(void *context) } pm_wakeup_hard_event(dev); - acpi_clear_event(ACPI_EVENT_RTC); - acpi_disable_event(ACPI_EVENT_RTC, 0); return ACPI_INTERRUPT_HANDLED; } From 3c7acb209e1d630b734a2ec72bd988f5da2ac55c Mon Sep 17 00:00:00 2001 From: Gregory Price Date: Thu, 15 Oct 2026 13:30:29 -0400 Subject: [PATCH 24/89] rtc: cmos: Honor explicit use_acpi_alarm parameter value An explicit rtc_cmos.use_acpi_alarm=0 command-line argument is ignored on systems where use_acpi_alarm_quirks() enables ACPI RTC alarms. The argument appears in /proc/cmdline, but the read-only parameter reports Y. The boolean parameter defaults to false, so the driver cannot distinguish an omitted argument from an explicit false value. Use a custom setter to record whether parsing occurred and skip automatic selection when it did. Preserve the bool parameter type metadata. Automatic selection remains unchanged when the parameter is omitted. On the affected system, the explicit false value reports N after this change. The HPET-emulated path completed 100 test runs without a failure while the ACPI RTC event counter remained at zero. Fixes: 36d91a4d401c ("rtc: cmos: introduce quirks to enable use_acpi_alarm mode") Cc: stable@vger.kernel.org Signed-off-by: Gregory Price (Meta) Link: https://patch.msgid.link/20261015173029.3091872-3-gourry@gourry.net Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-cmos.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index fe514b1aa343..209aa4ec732c 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -57,7 +57,28 @@ */ static bool use_acpi_alarm; -module_param(use_acpi_alarm, bool, 0444); +static bool use_acpi_alarm_explicit; + +static int set_use_acpi_alarm(const char *val, const struct kernel_param *kp) +{ + int ret; + + ret = param_set_bool(val, kp); + if (!ret) + use_acpi_alarm_explicit = true; + + return ret; +} + +static const struct kernel_param_ops use_acpi_alarm_ops = { + .flags = KERNEL_PARAM_OPS_FL_NOARG, + .set = set_use_acpi_alarm, + .get = param_get_bool, +}; + +param_check_bool(use_acpi_alarm, &use_acpi_alarm); +module_param_cb(use_acpi_alarm, &use_acpi_alarm_ops, &use_acpi_alarm, 0444); +__MODULE_PARM_TYPE(use_acpi_alarm, "bool"); static inline int cmos_use_acpi_alarm(void) { @@ -822,6 +843,9 @@ static void rtc_wake_off(struct device *dev) #ifdef CONFIG_X86 static void use_acpi_alarm_quirks(void) { + if (use_acpi_alarm_explicit) + return; + if (acpi_gbl_FADT.flags & ACPI_FADT_FIXED_RTC) return; From 2d6f97296143a97653e03a1b2517c554b0950f20 Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Wed, 14 Oct 2026 20:32:20 +0100 Subject: [PATCH 25/89] rtc: sun6i: fix error handling in sun6i_rtc_clk_init() Set sun6i_rtc only once all clocks are registered, and unwind fully on error. Signed-off-by: Pablo Mazzini Link: https://patch.msgid.link/20261014193220.38055-1-pmazzini@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-sun6i.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-sun6i.c b/drivers/rtc/rtc-sun6i.c index 7fc60880e409..dd293e8324b8 100644 --- a/drivers/rtc/rtc-sun6i.c +++ b/drivers/rtc/rtc-sun6i.c @@ -231,6 +231,7 @@ static void __init sun6i_rtc_clk_init(struct device_node *node, const char *iosc_name = "rtc-int-osc"; const char *clkout_name = "osc32k-out"; const char *parents[2]; + struct resource res; u32 reg; rtc = kzalloc_obj(*rtc); @@ -267,9 +268,6 @@ static void __init sun6i_rtc_clk_init(struct device_node *node, } writel(reg, rtc->base + SUN6I_LOSC_CTRL); - /* Yes, I know, this is ugly. */ - sun6i_rtc = rtc; - of_property_read_string_index(node, "clock-output-names", 2, &iosc_name); @@ -280,7 +278,7 @@ static void __init sun6i_rtc_clk_init(struct device_node *node, 300000000); if (IS_ERR(rtc->int_osc)) { pr_crit("Couldn't register the internal oscillator\n"); - goto err; + goto err_unmap; } parents[0] = clk_hw_get_name(rtc->int_osc); @@ -309,7 +307,7 @@ static void __init sun6i_rtc_clk_init(struct device_node *node, &rtc->lock); if (IS_ERR(rtc->ext_losc)) { pr_crit("Couldn't register the LOSC external gate\n"); - goto err_register; + goto err_losc; } clk_data->num = 3; @@ -317,12 +315,22 @@ static void __init sun6i_rtc_clk_init(struct device_node *node, clk_data->hws[1] = __clk_get_hw(rtc->ext_losc); clk_data->hws[2] = rtc->int_osc; of_clk_add_hw_provider(node, of_clk_hw_onecell_get, clk_data); + + /* Yes, I know, this is ugly. */ + sun6i_rtc = rtc; return; +err_losc: + clk_unregister(rtc->losc); err_register: clk_hw_unregister_fixed_rate(rtc->int_osc); +err_unmap: + iounmap(rtc->base); + of_address_to_resource(node, 0, &res); + release_mem_region(res.start, resource_size(&res)); err: kfree(clk_data); + kfree(rtc); } static const struct sun6i_rtc_clk_data sun6i_a31_rtc_data = { From 51f30cb855f0f51b359f42cc9d658d4b450124cc Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Mon, 12 Oct 2026 11:08:10 +0100 Subject: [PATCH 26/89] dt-bindings: rtc: sun6i-a31: add Allwinner B288 The B288 RTC wires one alarm interrupt, not two. Signed-off-by: Pablo Mazzini Acked-by: Rob Herring (Arm) Link: https://patch.msgid.link/20261012100816.331033-6-pmazzini@gmail.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml index f2b91186ed37..ebe35f893f8c 100644 --- a/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/allwinner,sun6i-a31-rtc.yaml @@ -19,6 +19,7 @@ properties: - enum: - allwinner,sun6i-a31-rtc - allwinner,sun8i-a23-rtc + - allwinner,sun8i-b288-rtc - allwinner,sun8i-h3-rtc - allwinner,sun8i-r40-rtc - allwinner,sun8i-v3-rtc @@ -82,6 +83,7 @@ allOf: contains: enum: - allwinner,sun8i-a23-rtc + - allwinner,sun8i-b288-rtc - allwinner,sun8i-r40-rtc - allwinner,sun8i-v3-rtc @@ -162,6 +164,7 @@ allOf: compatible: contains: enum: + - allwinner,sun8i-b288-rtc - allwinner,sun8i-r40-rtc - allwinner,sun50i-h616-rtc - allwinner,sun50i-r329-rtc From 3784706c87c2d00a1b56f01092dc4e4695c29d1b Mon Sep 17 00:00:00 2001 From: Pablo Mazzini Date: Mon, 12 Oct 2026 11:08:11 +0100 Subject: [PATCH 27/89] rtc: sun6i: add Allwinner B288 compatible The 32 kHz clocks are the same as the A23's. Only one alarm interrupt is wired, which is all the driver uses. Signed-off-by: Pablo Mazzini Link: https://patch.msgid.link/20261012100816.331033-7-pmazzini@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-sun6i.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/rtc/rtc-sun6i.c b/drivers/rtc/rtc-sun6i.c index dd293e8324b8..c6916edbf400 100644 --- a/drivers/rtc/rtc-sun6i.c +++ b/drivers/rtc/rtc-sun6i.c @@ -358,6 +358,10 @@ static void __init sun8i_a23_rtc_clk_init(struct device_node *node) CLK_OF_DECLARE_DRIVER(sun8i_a23_rtc_clk, "allwinner,sun8i-a23-rtc", sun8i_a23_rtc_clk_init); +/* The B288 RTC clocks are the same as the A23's */ +CLK_OF_DECLARE_DRIVER(sun8i_b288_rtc_clk, "allwinner,sun8i-b288-rtc", + sun8i_a23_rtc_clk_init); + static const struct sun6i_rtc_clk_data sun8i_h3_rtc_data = { .rc_osc_rate = 16000000, .fixed_prescaler = 32, @@ -867,6 +871,7 @@ static int sun6i_rtc_probe(struct platform_device *pdev) static const struct of_device_id sun6i_rtc_dt_ids[] = { { .compatible = "allwinner,sun6i-a31-rtc" }, { .compatible = "allwinner,sun8i-a23-rtc" }, + { .compatible = "allwinner,sun8i-b288-rtc" }, { .compatible = "allwinner,sun8i-h3-rtc" }, { .compatible = "allwinner,sun8i-r40-rtc" }, { .compatible = "allwinner,sun8i-v3-rtc" }, From 22b9306f1fcf86f80b55f05f08d10a9951ba970a Mon Sep 17 00:00:00 2001 From: Ma Ke Date: Sun, 11 Oct 2026 20:17:58 +0800 Subject: [PATCH 28/89] platform/x86/amd/pmc: Fix RTC reference leak in amd_pmc_verify_czn_rtc() amd_pmc_verify_czn_rtc() opens the RTC with rtc_class_open(), which takes both a device reference and a module reference on the RTC driver. The matching release is rtc_class_close(), and it is never called. Every return path after the device has been opened, the ones that return 0 as well as the ones that report an error, leaves both references behind, so each S0i3 suspend leaks them. Release the reference with a __free() cleanup on the declaration, which covers every return path without adding labels. rtc_class_close() does not accept a NULL argument, so the cleanup helper has to guard against one: rtc_class_open() may fail and the declaration still has to be cleaned up. Found by code review. Fixes: 59348401ebed ("platform/x86: amd-pmc: Add special handling for timer based S0i3 wakeup") Cc: stable@vger.kernel.org Signed-off-by: Ma Ke Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/20261011121759.3267678-1-make_ruc2021@163.com Signed-off-by: Alexandre Belloni --- drivers/platform/x86/amd/pmc/pmc.c | 5 +++-- include/linux/rtc.h | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c index 6792aa2c6187..f96e8a358eb7 100644 --- a/drivers/platform/x86/amd/pmc/pmc.c +++ b/drivers/platform/x86/amd/pmc/pmc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -648,7 +649,6 @@ static int amd_pmc_wa_irq1(struct amd_pmc_dev *pdev) static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) { - struct rtc_device *rtc_device; time64_t then, now, duration; struct rtc_wkalrm alarm; struct rtc_time tm; @@ -664,7 +664,8 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) if (pdev->major < 64 || (pdev->major == 64 && pdev->minor < 53)) return 0; - rtc_device = rtc_class_open("rtc0"); + struct rtc_device *rtc_device __free(rtc_class_close) = + rtc_class_open("rtc0"); if (!rtc_device) return 0; rc = rtc_read_next_alarm(rtc_device, &alarm); diff --git a/include/linux/rtc.h b/include/linux/rtc.h index c09fc22819d0..0f43b5027235 100644 --- a/include/linux/rtc.h +++ b/include/linux/rtc.h @@ -13,6 +13,7 @@ #define _LINUX_RTC_H_ +#include #include #include #include @@ -201,6 +202,7 @@ extern void rtc_update_irq(struct rtc_device *rtc, extern struct rtc_device *rtc_class_open(const char *name); extern void rtc_class_close(struct rtc_device *rtc); +DEFINE_FREE(rtc_class_close, struct rtc_device *, if (_T) rtc_class_close(_T)); extern int rtc_irq_set_state(struct rtc_device *rtc, int enabled); extern int rtc_irq_set_freq(struct rtc_device *rtc, int freq); From e7124bcceb15c069a1b0baa5fff67b9f102a0f84 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Fri, 9 Oct 2026 23:23:47 +0200 Subject: [PATCH 29/89] rtc: nvidia-vrs10: remove locking in IRQ handler Remove locking in the interrupt handler as it is not necessary to be locked while calling rtc_update_irq(). Link: https://patch.msgid.link/20261009212347.490403-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-nvidia-vrs10.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-nvidia-vrs10.c b/drivers/rtc/rtc-nvidia-vrs10.c index b15796698558..caac63f4e0fc 100644 --- a/drivers/rtc/rtc-nvidia-vrs10.c +++ b/drivers/rtc/rtc-nvidia-vrs10.c @@ -358,11 +358,8 @@ static irqreturn_t nvvrs_rtc_irq_handler(int irq, void *data) if (ret < 0) return IRQ_NONE; - if (ret & NVVRS_INT_SRC1_RTC_MASK) { - rtc_lock(info->rtc); + if (ret & NVVRS_INT_SRC1_RTC_MASK) rtc_update_irq(info->rtc, 1, RTC_IRQF | RTC_AF); - rtc_unlock(info->rtc); - } /* Clear all interrupts */ if (nvvrs_pseq_irq_clear(info) < 0) From 9e25de5490fbb8648af497b438d1d16990b2d107 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 8 Oct 2026 22:16:53 +0200 Subject: [PATCH 30/89] dt-bindings: rtc: rzn1: add resets property The RTC on R-Car X5H does have a reset unlike the other versions. Suggested-by: Geert Uytterhoeven Signed-off-by: Wolfram Sang Acked-by: Conor Dooley Reviewed-by: Miquel Raynal Reviewed-by: Nobuhiro Iwamatsu Closes: https://lore.kernel.org/CAMuHMdWy6ksspwvj8jRxf6_OATU5rS5p92KyG4+sEF+ag7S9NA@mail.gmail.com Reviewed-by: Geert Uytterhoeven Link: https://patch.msgid.link/20261008201953.34307-2-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/renesas,rzn1-rtc.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml index b448725671ec..5a869e6c0ae7 100644 --- a/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/renesas,rzn1-rtc.yaml @@ -51,6 +51,9 @@ properties: power-domains: maxItems: 1 + resets: + maxItems: 1 + required: - compatible - reg @@ -85,7 +88,11 @@ allOf: properties: interrupts: false interrupt-names: false + required: + - resets else: + properties: + resets: false required: - interrupts - interrupt-names From 5831f5fef10030c175e585d51b0e793dc6fef6ab Mon Sep 17 00:00:00 2001 From: Igor Velkov Date: Sun, 4 Oct 2026 23:56:53 +0000 Subject: [PATCH 31/89] rtc: pcf8563: pass the driver data to the alarm interrupt handler pcf8563_irq() takes its dev_id as struct pcf8563, but probe still registers the interrupt with the i2c client. The handler then takes the regmap pointer from the wrong structure: the first alarm oopses, the level-low INT is never acknowledged, and the interrupt gets disabled as spurious. Only an RTC that has an interrupt (client->irq > 0) reaches this path. Fixes: 00f1bb9b8486 ("rtc: pcf8563: Switch to regmap") Cc: stable@vger.kernel.org Assisted-by: LLM Signed-off-by: Igor Velkov Reviewed-by: Nobuhiro Iwamtasu Link: https://patch.msgid.link/20261004-pcf8563-irq-data-v1-1-f176763ddf2f@iav.lv Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf8563.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-pcf8563.c b/drivers/rtc/rtc-pcf8563.c index 56e75664c7b4..3801acc9ac8d 100644 --- a/drivers/rtc/rtc-pcf8563.c +++ b/drivers/rtc/rtc-pcf8563.c @@ -532,7 +532,7 @@ static int pcf8563_probe(struct i2c_client *client) err = devm_request_threaded_irq(&client->dev, client->irq, NULL, pcf8563_irq, IRQF_SHARED | IRQF_ONESHOT | irqflags, - pcf8563_driver.driver.name, client); + pcf8563_driver.driver.name, pcf8563); if (err) { dev_err(&client->dev, "unable to request IRQ %d\n", client->irq); From 82e7babfb39fc5a4f58f2fe725876363810f35bb Mon Sep 17 00:00:00 2001 From: Wake Liu Date: Tue, 29 Sep 2026 15:22:12 +0000 Subject: [PATCH 32/89] selftests: rtc: fix flaky date_read_loop test The test case rtc.date_read_loop in rtctest.c fails intermittently because it checks that the RTC time does not advance by more than 1 second per loop iteration. However, the loop sleeps for 11ms via nanosleep(), and if the test thread is descheduled by the OS scheduler (e.g., under heavy system load in a VM), more than 1 second can elapse between consecutive RTC reads. This causes the next RTC time read to be 2 or more seconds ahead of the previous read, triggering a test assertion failure. To make the test more resilient against OS scheduling delays, measure the real elapsed time between iterations using a monotonic clock (clock_gettime(CLOCK_MONOTONIC)), and compute the actual number of seconds elapsed (delta_s) between consecutive RTC reads. Then dynamically adjust the assertion to: ASSERT_GE(prev_rtc_read + delta_s + 1, rtc_read); Signed-off-by: Wake Liu Link: https://patch.msgid.link/20260929152212.2092062-1-wakel@google.com Signed-off-by: Alexandre Belloni --- tools/testing/selftests/rtc/rtctest.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/rtc/rtctest.c b/tools/testing/selftests/rtc/rtctest.c index 8047d9879039..54eb5c255a45 100644 --- a/tools/testing/selftests/rtc/rtctest.c +++ b/tools/testing/selftests/rtc/rtctest.c @@ -116,6 +116,7 @@ TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) { long iter_count = 0; struct rtc_time rtc_tm; time_t start_rtc_read, prev_rtc_read; + struct timespec prev_mono, cur_mono; if (self->fd == -1 && errno == ENOENT) SKIP(return, "Skipping test since %s does not exist", rtc_file); @@ -126,25 +127,31 @@ TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) { rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); ASSERT_NE(-1, rc); + clock_gettime(CLOCK_MONOTONIC, &prev_mono); start_rtc_read = rtc_time_to_timestamp(&rtc_tm); prev_rtc_read = start_rtc_read; do { time_t rtc_read; + time_t delta_s = 0; rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm); ASSERT_NE(-1, rc); + clock_gettime(CLOCK_MONOTONIC, &cur_mono); rtc_read = rtc_time_to_timestamp(&rtc_tm); + delta_s = cur_mono.tv_sec - prev_mono.tv_sec; + /* Time should not go backwards */ ASSERT_LE(prev_rtc_read, rtc_read); - /* Time should not increase more then 1s at a time */ - ASSERT_GE(prev_rtc_read + 1, rtc_read); + /* Time should not increase more then elapsed time + 1s */ + ASSERT_GE(prev_rtc_read + delta_s + 1, rtc_read); /* Sleep 11ms to avoid killing / overheating the RTC */ nanosleep_with_retries(READ_LOOP_SLEEP_MS * 1000000); prev_rtc_read = rtc_read; + prev_mono = cur_mono; iter_count++; } while (prev_rtc_read <= start_rtc_read + READ_LOOP_DURATION_SEC); From ec55c6e9c5cfb8956c6a779acb45b3e0ecd705c7 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 16 Sep 2026 23:31:09 +0200 Subject: [PATCH 33/89] selftest: rtc: rtctest: don't mention skipping twice The framework already annotates that we are skipping something. No need to do in the tests themselves again. Signed-off-by: Wolfram Sang Link: https://patch.msgid.link/20260916213108.9234-6-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- tools/testing/selftests/rtc/rtctest.c | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/tools/testing/selftests/rtc/rtctest.c b/tools/testing/selftests/rtc/rtctest.c index 54eb5c255a45..094417927a1a 100644 --- a/tools/testing/selftests/rtc/rtctest.c +++ b/tools/testing/selftests/rtc/rtctest.c @@ -49,7 +49,7 @@ TEST_F(rtc, date_read) { struct rtc_time rtc_tm; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Read the RTC time/date */ @@ -119,7 +119,7 @@ TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) { struct timespec prev_mono, cur_mono; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); TH_LOG("Continuously reading RTC time for %ds (with %dms breaks after every read).", @@ -163,7 +163,7 @@ TEST_F_TIMEOUT(rtc, uie_read, NUM_UIE + 2) { unsigned long data; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Turn on update interrupts */ @@ -192,7 +192,7 @@ TEST_F(rtc, uie_select) { unsigned long data; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); /* Turn on update interrupts */ @@ -236,14 +236,14 @@ TEST_F(rtc, alarm_alm_set) { enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); alarm_state = get_rtc_alarm_state(self->fd, 1); if (alarm_state == RTC_ALARM_DISABLED) - SKIP(return, "Skipping test since alarms are not supported."); + SKIP(return, "alarms are not supported."); if (alarm_state == RTC_ALARM_RES_MINUTE) - SKIP(return, "Skipping test since alarms has only minute granularity."); + SKIP(return, "alarms has only minute granularity."); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); @@ -306,14 +306,14 @@ TEST_F(rtc, alarm_wkalm_set) { enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); alarm_state = get_rtc_alarm_state(self->fd, 1); if (alarm_state == RTC_ALARM_DISABLED) - SKIP(return, "Skipping test since alarms are not supported."); + SKIP(return, "alarms are not supported."); if (alarm_state == RTC_ALARM_RES_MINUTE) - SKIP(return, "Skipping test since alarms has only minute granularity."); + SKIP(return, "alarms has only minute granularity."); rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); ASSERT_NE(-1, rc); @@ -370,12 +370,12 @@ TEST_F_TIMEOUT(rtc, alarm_alm_set_minute, 65) { enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); alarm_state = get_rtc_alarm_state(self->fd, 0); if (alarm_state == RTC_ALARM_DISABLED) - SKIP(return, "Skipping test since alarms are not supported."); + SKIP(return, "alarms are not supported."); rc = ioctl(self->fd, RTC_RD_TIME, &tm); ASSERT_NE(-1, rc); @@ -438,12 +438,12 @@ TEST_F_TIMEOUT(rtc, alarm_wkalm_set_minute, 65) { enum rtc_alarm_state alarm_state = RTC_ALARM_UNKNOWN; if (self->fd == -1 && errno == ENOENT) - SKIP(return, "Skipping test since %s does not exist", rtc_file); + SKIP(return, "%s does not exist", rtc_file); ASSERT_NE(-1, self->fd); alarm_state = get_rtc_alarm_state(self->fd, 0); if (alarm_state == RTC_ALARM_DISABLED) - SKIP(return, "Skipping test since alarms are not supported."); + SKIP(return, "alarms are not supported."); rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time); ASSERT_NE(-1, rc); @@ -509,8 +509,7 @@ int main(int argc, char **argv) if (access(rtc_file, R_OK) == 0) ret = test_harness_run(argc, argv); else - ksft_exit_skip("[SKIP]: Cannot access rtc file %s - Exiting\n", - rtc_file); + ksft_exit_skip("Cannot access rtc file %s - Exiting\n", rtc_file); return ret; } From 7980352c78f5aac7160941569826a260c6232589 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 16 Sep 2026 23:31:10 +0200 Subject: [PATCH 34/89] selftest: rtc: rtctest: make missing UIEs really skip Use the proper framework call to mark a test as skipped. Currently, the test is considered good, only the printout mentions the skip. Signed-off-by: Wolfram Sang Link: https://patch.msgid.link/20260916213108.9234-7-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- tools/testing/selftests/rtc/rtctest.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/testing/selftests/rtc/rtctest.c b/tools/testing/selftests/rtc/rtctest.c index 094417927a1a..f8e2c0ce13c1 100644 --- a/tools/testing/selftests/rtc/rtctest.c +++ b/tools/testing/selftests/rtc/rtctest.c @@ -170,8 +170,7 @@ TEST_F_TIMEOUT(rtc, uie_read, NUM_UIE + 2) { rc = ioctl(self->fd, RTC_UIE_ON, 0); if (rc == -1) { ASSERT_EQ(EINVAL, errno); - TH_LOG("skip update IRQs not supported."); - return; + SKIP(return, "update IRQs are not supported."); } for (i = 0; i < NUM_UIE; i++) { @@ -199,8 +198,7 @@ TEST_F(rtc, uie_select) { rc = ioctl(self->fd, RTC_UIE_ON, 0); if (rc == -1) { ASSERT_EQ(EINVAL, errno); - TH_LOG("skip update IRQs not supported."); - return; + SKIP(return, "update IRQs are not supported."); } for (i = 0; i < NUM_UIE; i++) { From bd55831c0bfc0aa745753db972253aba2d452da3 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 16 Sep 2026 23:31:11 +0200 Subject: [PATCH 35/89] selftest: rtc: rtctest: skip properly if setting alarm fails Use the proper framework call to mark a test as skipped. Currently, the test is considered good, only the printout mentions the skip. Signed-off-by: Wolfram Sang Link: https://patch.msgid.link/20260916213108.9234-8-wsa+renesas@sang-engineering.com Signed-off-by: Alexandre Belloni --- tools/testing/selftests/rtc/rtctest.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tools/testing/selftests/rtc/rtctest.c b/tools/testing/selftests/rtc/rtctest.c index f8e2c0ce13c1..552191fe1ca0 100644 --- a/tools/testing/selftests/rtc/rtctest.c +++ b/tools/testing/selftests/rtc/rtctest.c @@ -257,8 +257,7 @@ TEST_F(rtc, alarm_alm_set) { */ ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state); ASSERT_EQ(EINVAL, errno); - TH_LOG("skip alarms are not supported."); - return; + SKIP(return, "alarms are not supported."); } rc = ioctl(self->fd, RTC_ALM_READ, &tm); @@ -329,8 +328,7 @@ TEST_F(rtc, alarm_wkalm_set) { */ ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state); ASSERT_EQ(EINVAL, errno); - TH_LOG("skip alarms are not supported."); - return; + SKIP(return, "alarms are not supported."); } rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); @@ -389,8 +387,7 @@ TEST_F_TIMEOUT(rtc, alarm_alm_set_minute, 65) { */ ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state); ASSERT_EQ(EINVAL, errno); - TH_LOG("skip alarms are not supported."); - return; + SKIP(return, "alarms are not supported."); } rc = ioctl(self->fd, RTC_ALM_READ, &tm); @@ -459,8 +456,7 @@ TEST_F_TIMEOUT(rtc, alarm_wkalm_set_minute, 65) { */ ASSERT_EQ(RTC_ALARM_UNKNOWN, alarm_state); ASSERT_EQ(EINVAL, errno); - TH_LOG("skip alarms are not supported."); - return; + SKIP(return, "alarms are not supported."); } rc = ioctl(self->fd, RTC_WKALM_RD, &alarm); From 09399a7cd93222cf72bdb35e873c11cb0fbf5317 Mon Sep 17 00:00:00 2001 From: Sander Speetjens Date: Sun, 18 Oct 2026 17:10:54 +0200 Subject: [PATCH 36/89] dt-bindings: rtc: Add missing type to trickle-voltage-millivolt The trickle-voltage-millivolt property had no associated type. -millivolt is not a default property unit suffix, so the schema does not automatically infer uint32. Explicitly reference the uint32 definition so the type is correctly declared. Suggested-by: Rob Herring Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sander Speetjens Link: https://patch.msgid.link/20261018151056.1051330-2-sander.speetjens@gmail.com Signed-off-by: Alexandre Belloni --- Documentation/devicetree/bindings/rtc/rtc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/rtc/rtc.yaml b/Documentation/devicetree/bindings/rtc/rtc.yaml index efb66df82782..e46d50b1063e 100644 --- a/Documentation/devicetree/bindings/rtc/rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/rtc.yaml @@ -51,6 +51,7 @@ properties: if trickle charger should be enabled. trickle-voltage-millivolt: + $ref: /schemas/types.yaml#/definitions/uint32 description: Selected voltage for trickle charger. Should be given if trickle charger should be enabled and the trickle voltage is different From 58db0fbeef2a2cceef452a874e9983ccd66f3e78 Mon Sep 17 00:00:00 2001 From: Sander Speetjens Date: Sun, 18 Oct 2026 17:10:55 +0200 Subject: [PATCH 37/89] dt-bindings: rtc: Add property for Raspberry Pi 5 RTC The Raspberry Pi 5 has a built-in RTC that can be battery-backed via the VBAT pin. When a rechargeable coin cell is used, the hardware requires a trickle-charge voltage to keep the battery topped up. Expose the trickle-voltage-millivolt DT property so the driver can program the correct charge voltage for board specific configurations with the attached battery. The property is added to the existing raspberrypi,bcm2835-firmware binding (rather than creating a new RTC node) because the RTC is accessed exclusively through the firmware mailbox interface. We only $ref the individual property from rtc.yaml and do not reference the full schema, because the firmware node name does not match the $nodename pattern required by rtc.yaml. Acked-by: Conor Dooley Acked-by: Krzysztof Kozlowski Signed-off-by: Sander Speetjens Link: https://patch.msgid.link/20261018151056.1051330-3-sander.speetjens@gmail.com Signed-off-by: Alexandre Belloni --- .../bindings/arm/bcm/raspberrypi,bcm2835-firmware.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/bcm/raspberrypi,bcm2835-firmware.yaml b/Documentation/devicetree/bindings/arm/bcm/raspberrypi,bcm2835-firmware.yaml index 983ea80eaec9..dc71fd57834e 100644 --- a/Documentation/devicetree/bindings/arm/bcm/raspberrypi,bcm2835-firmware.yaml +++ b/Documentation/devicetree/bindings/arm/bcm/raspberrypi,bcm2835-firmware.yaml @@ -133,6 +133,11 @@ properties: required: - compatible + trickle-voltage-millivolt: + $ref: /schemas/rtc/rtc.yaml#/properties/trickle-voltage-millivolt + minimum: 1300 + maximum: 5000 + required: - compatible - mboxes @@ -144,6 +149,7 @@ examples: firmware { compatible = "raspberrypi,bcm2835-firmware", "simple-mfd"; mboxes = <&mailbox>; + trickle-voltage-millivolt = <3300>; firmware_clocks: clocks { compatible = "raspberrypi,firmware-clocks"; From dba67294e21517ee2c251a143c53cc90535fe43f Mon Sep 17 00:00:00 2001 From: Sander Speetjens Date: Sun, 18 Oct 2026 17:10:56 +0200 Subject: [PATCH 38/89] rtc: Add Raspberry Pi 5 RTC driver Upstreaming the downstream Raspberry Pi 5 RTC driver. This driver supports the custom DA9091, which is accessed through the firmware mailbox. Based on code originally written by Jonathan Bell and Dom Cobley Reviewed-by: Stefan Wahren Signed-off-by: Sander Speetjens Link: https://patch.msgid.link/20261018151056.1051330-4-sander.speetjens@gmail.com Signed-off-by: Alexandre Belloni --- drivers/firmware/raspberrypi.c | 24 ++ drivers/rtc/Kconfig | 12 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-raspberrypi.c | 278 +++++++++++++++++++++ include/soc/bcm2835/raspberrypi-firmware.h | 2 + 5 files changed, 317 insertions(+) create mode 100644 drivers/rtc/rtc-raspberrypi.c diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c index 0aa322e9a2e7..443459c8c90f 100644 --- a/drivers/firmware/raspberrypi.c +++ b/drivers/firmware/raspberrypi.c @@ -24,6 +24,7 @@ static struct platform_device *rpi_hwmon; static struct platform_device *rpi_clk; +static struct platform_device *rpi_rtc; struct rpi_firmware { struct mbox_client cl; @@ -231,6 +232,26 @@ static void rpi_register_clk_driver(struct device *dev) -1, NULL, 0); } +static void rpi_register_rtc_driver(struct device *dev) +{ + struct platform_device *pdev; + + /* + * Check if our model of Raspberry Pi board is from the 5th gen + * as the RTC is only present on that model. + */ + if (!of_machine_is_compatible("brcm,bcm2712")) + return; + + pdev = platform_device_register_data(dev, "raspberrypi-rtc", + -1, NULL, 0); + + if (IS_ERR(pdev)) + return; + + rpi_rtc = pdev; +} + unsigned int rpi_firmware_clk_get_max_rate(struct rpi_firmware *fw, unsigned int id) { struct rpi_firmware_clk_rate_request msg = @@ -305,6 +326,7 @@ static int rpi_firmware_probe(struct platform_device *pdev) rpi_firmware_print_firmware_revision(fw); rpi_register_hwmon_driver(dev, fw); rpi_register_clk_driver(dev); + rpi_register_rtc_driver(dev); return 0; } @@ -327,6 +349,8 @@ static void rpi_firmware_remove(struct platform_device *pdev) rpi_hwmon = NULL; platform_device_unregister(rpi_clk); rpi_clk = NULL; + platform_device_unregister(rpi_rtc); + rpi_rtc = NULL; rpi_firmware_put(fw); } diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 47505b62e1b4..243edd37c0d7 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -2002,6 +2002,18 @@ config RTC_DRV_R7301 This driver can also be built as a module. If so, the module will be called rtc-r7301. +config RTC_DRV_RPI + tristate "Raspberry Pi RTC" + depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) + depends on ARCH_BCM2835 || COMPILE_TEST + default ARCH_BCM2835 + help + If you say yes here you get support for the RTC found on + Raspberry Pi 5th gen devices. + + This driver can also be built as a module. If so, the module + will be called rtc-raspberrypi. + config RTC_DRV_STM32 tristate "STM32 RTC" select REGMAP_MMIO diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 0347645b021f..46f1a0fc2416 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -144,6 +144,7 @@ obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o obj-$(CONFIG_RTC_DRV_PXA) += rtc-pxa.o obj-$(CONFIG_RTC_DRV_R7301) += rtc-r7301.o obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o +obj-$(CONFIG_RTC_DRV_RPI) += rtc-raspberrypi.o obj-$(CONFIG_RTC_DRV_RC5T583) += rtc-rc5t583.o obj-$(CONFIG_RTC_DRV_RC5T619) += rtc-rc5t619.o obj-$(CONFIG_RTC_DRV_RK808) += rtc-rk808.o diff --git a/drivers/rtc/rtc-raspberrypi.c b/drivers/rtc/rtc-raspberrypi.c new file mode 100644 index 000000000000..0547a96c62ac --- /dev/null +++ b/drivers/rtc/rtc-raspberrypi.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause +/** + * rtc-raspberrypi.c + * + * RTC driver using firmware mailbox + * Supports battery backed RTC and wake alarms + * + * Based on rtc-meson-vrtc by Neil Armstrong + * + * Copyright (c) 2023, Raspberry Pi Ltd. + */ + +#include +#include +#include +#include +#include + +#define RTC_TIME 0 +#define RTC_ALARM 1 +#define RTC_ALARM_PENDING 2 +#define RTC_ALARM_ENABLE 3 +#define RTC_BBAT_CHG_VOLTS 4 +#define RTC_BBAT_CHG_VOLTS_MIN 5 +#define RTC_BBAT_CHG_VOLTS_MAX 6 +#define RTC_BBAT_VOLTS 7 + +struct rpi_rtc_data { + struct rtc_device *rtc; + struct rpi_firmware *fw; + u32 bbat_vchg_millivolts; + u32 bbat_vchg_min_millivolts; + u32 bbat_vchg_max_millivolts; +}; + +struct rpi_rtc_reg { + __le32 reg; + __le32 val; +} __packed; + +static int rpi_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_TIME) + }; + int err; + + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + rtc_time64_to_tm(le32_to_cpu(data.val), tm); + return err; +} + +static int rpi_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_TIME), + .val = cpu_to_le32(rtc_tm_to_time64(tm)) + }; + + return rpi_firmware_property(rtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_alarm_irq_is_enabled(struct device *dev, unsigned char *enabled) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_ALARM_ENABLE) + }; + int err; + + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + *enabled = le32_to_cpu(data.val) & 0x1; + return err; +} + +static int rpi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_ALARM_ENABLE), + .val = cpu_to_le32(enabled) + }; + + return rpi_firmware_property(rtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_alarm_clear_pending(struct device *dev) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_ALARM_PENDING), + .val = cpu_to_le32(1) + }; + + return rpi_firmware_property(rtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); +} + +static int rpi_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_ALARM) + }; + int err; + + err = rpi_rtc_alarm_irq_is_enabled(dev, &alarm->enabled); + if (!err) + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + rtc_time64_to_tm(le32_to_cpu(data.val), &alarm->time); + + return err; +} + +static int rpi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_ALARM), + .val = cpu_to_le32(rtc_tm_to_time64(&alarm->time)) + }; + int err; + + err = rpi_rtc_alarm_clear_pending(dev); + + if (!err) + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); + + if (!err) + err = rpi_rtc_alarm_irq_enable(dev, alarm->enabled); + + return err; +} + +static const struct rtc_class_ops rpi_rtc_ops = { + .read_time = rpi_rtc_read_time, + .set_time = rpi_rtc_set_time, + .read_alarm = rpi_rtc_read_alarm, + .set_alarm = rpi_rtc_set_alarm, + .alarm_irq_enable = rpi_rtc_alarm_irq_enable, +}; + +static void rpi_rtc_set_limits(struct device *dev) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_BBAT_CHG_VOLTS_MIN) + }; + + int err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + if (!err) + rtc->bbat_vchg_min_millivolts = le32_to_cpu(data.val) / 1000U; + else + dev_warn(dev, "failed to get minimum trickle charge voltage\n"); + + data.reg = cpu_to_le32(RTC_BBAT_CHG_VOLTS_MAX); + data.val = cpu_to_le32(0); + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_GET_RTC_REG, + &data, sizeof(data)); + if (!err) + rtc->bbat_vchg_max_millivolts = le32_to_cpu(data.val) / 1000U; + else + dev_warn(dev, "failed to get maximum trickle charge voltage\n"); +} + +static void rpi_rtc_set_charge_voltage(struct device *dev) +{ + struct rpi_rtc_data *rtc = dev_get_drvdata(dev); + struct rpi_rtc_reg data = { + .reg = cpu_to_le32(RTC_BBAT_CHG_VOLTS), + .val = cpu_to_le32(rtc->bbat_vchg_millivolts * 1000U) + }; + int err; + + if (rtc->bbat_vchg_millivolts != 0 && + (rtc->bbat_vchg_millivolts < rtc->bbat_vchg_min_millivolts || + rtc->bbat_vchg_millivolts > rtc->bbat_vchg_max_millivolts)) { + dev_warn(dev, "trickle charge voltage %umV is outside of the supported range (%umV - %umV)\n", + rtc->bbat_vchg_millivolts, + rtc->bbat_vchg_min_millivolts, + rtc->bbat_vchg_max_millivolts); + return; + } + + err = rpi_firmware_property(rtc->fw, RPI_FIRMWARE_SET_RTC_REG, + &data, sizeof(data)); + + if (err) + dev_err(dev, "failed to set trickle charge voltage to %umV: %d\n", + rtc->bbat_vchg_millivolts, err); + else if (rtc->bbat_vchg_millivolts) + dev_info(dev, "trickle charging enabled at %umV\n", + rtc->bbat_vchg_millivolts); +} + +static int rpi_rtc_probe(struct platform_device *pdev) +{ + struct rpi_rtc_data *rtc; + struct device *dev = &pdev->dev; + struct rpi_firmware *firmware; + int ret; + + // Get the firmware device from the parent device + firmware = devm_rpi_firmware_get(dev, dev->parent->of_node); + + if (!firmware) + return -EPROBE_DEFER; + + rtc = devm_kzalloc(dev, sizeof(*rtc), GFP_KERNEL); + if (!rtc) + return -ENOMEM; + + rtc->fw = firmware; + + ret = devm_device_init_wakeup(dev); + if (ret) + return ret; + + platform_set_drvdata(pdev, rtc); + + rtc->rtc = devm_rtc_allocate_device(dev); + if (IS_ERR(rtc->rtc)) + return PTR_ERR(rtc->rtc); + + rtc->rtc->range_max = U32_MAX; /* 2106-02-07 */ + + set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, rtc->rtc->features); + clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->rtc->features); + + rtc->rtc->ops = &rpi_rtc_ops; + + rpi_rtc_alarm_clear_pending(dev); + + /* + * The trickle-voltage property lives on the parent firmware node + * because we deliberately do not create a DT child node just to + * instantiate this driver (see DT maintainer guidance). + * The firmware driver registers us as a platform device at runtime. + */ + rtc->bbat_vchg_millivolts = 0; + of_property_read_u32(dev->parent->of_node, "trickle-voltage-millivolt", + &rtc->bbat_vchg_millivolts); + + rpi_rtc_set_limits(dev); + rpi_rtc_set_charge_voltage(dev); + + return devm_rtc_register_device(rtc->rtc); +} + +static const struct platform_device_id raspberrypi_rtc_id[] = { + { "raspberrypi-rtc" }, + { } +}; +MODULE_DEVICE_TABLE(platform, raspberrypi_rtc_id); + +static struct platform_driver rpi_rtc_driver = { + .driver = { + .name = "raspberrypi-rtc", + }, + .id_table = raspberrypi_rtc_id, + .probe = rpi_rtc_probe +}; + +module_platform_driver(rpi_rtc_driver); + +MODULE_AUTHOR("Jonathan Bell "); +MODULE_AUTHOR("Sander Speetjens "); +MODULE_DESCRIPTION("Raspberry Pi RTC driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h index 975bef529854..8c3d43977a03 100644 --- a/include/soc/bcm2835/raspberrypi-firmware.h +++ b/include/soc/bcm2835/raspberrypi-firmware.h @@ -75,6 +75,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_GET_THROTTLED = 0x00030046, RPI_FIRMWARE_GET_CLOCK_MEASURED = 0x00030047, RPI_FIRMWARE_NOTIFY_REBOOT = 0x00030048, + RPI_FIRMWARE_GET_RTC_REG = 0x00030087, RPI_FIRMWARE_SET_CLOCK_STATE = 0x00038001, RPI_FIRMWARE_SET_CLOCK_RATE = 0x00038002, RPI_FIRMWARE_SET_VOLTAGE = 0x00038003, @@ -92,6 +93,7 @@ enum rpi_firmware_property_tag { RPI_FIRMWARE_SET_POE_HAT_VAL = 0x00030050, RPI_FIRMWARE_NOTIFY_XHCI_RESET = 0x00030058, RPI_FIRMWARE_NOTIFY_DISPLAY_DONE = 0x00030066, + RPI_FIRMWARE_SET_RTC_REG = 0x00038087, /* Dispmanx TAGS */ RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE = 0x00040001, From 026f8b1d515b188d0fa9377a9047d2f75e7b43fd Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Tue, 25 Aug 2026 09:19:16 +0200 Subject: [PATCH 39/89] dt-bindings: rtc: add ricoh,r2223x binding The Ricoh R2223x is an I2C RTC of the same family as the r2025sd and r2221tl which the rtc-rs5c372 already supports. It drives a clock output and can be put into an eco mode that lowers its current consumption from the backup supply, so it needs #clock-cells and a property for the eco mode. The trivial-rtc binding does not cover this, so give the device its own binding. Signed-off-by: Heiko Schocher Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20260825071927.4090460-2-hs@nabladev.com Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/ricoh,r2223x.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 Documentation/devicetree/bindings/rtc/ricoh,r2223x.yaml diff --git a/Documentation/devicetree/bindings/rtc/ricoh,r2223x.yaml b/Documentation/devicetree/bindings/rtc/ricoh,r2223x.yaml new file mode 100644 index 000000000000..e64a1ee378e6 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/ricoh,r2223x.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/ricoh,r2223x.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Ricoh R2223x Real-Time Clock + +maintainers: + - Heiko Schocher + +allOf: + - $ref: rtc.yaml# + +properties: + compatible: + const: ricoh,r2223x + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + "#clock-cells": + const: 0 + + ricoh,eco-mode: + type: boolean + description: + Put the RTC into eco mode, which lowers its current consumption while + running from the backup supply. Set this on boards where the RTC is + expected to keep time on a small backup cell. + + start-year: true + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + rtc@32 { + compatible = "ricoh,r2223x"; + reg = <0x32>; + interrupts = <6 IRQ_TYPE_EDGE_FALLING>; + #clock-cells = <0>; + ricoh,eco-mode; + }; + }; From df8a0f6db214cf3e439a512b979f5802556c992e Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Tue, 25 Aug 2026 09:19:17 +0200 Subject: [PATCH 40/89] rtc: rs5c372: add support for Ricoh R2223x The R2223x is an I2C RTC from the same family as the r2025sd and r2221tl that this driver already handles. It shares the R2x2x control register layout, so treat it like the r2221tl. The oscillator interrupt flag is reported through XSTP, and the 24 hour mode bit lives in CTRL1. It also has the DEV bit in the trim register, so same as r2221tl. Signed-off-by: Heiko Schocher Reported-by: Sashiko AI review Closes: https://sashiko.dev/#/patchset/20260824110452.4038870-1-hs@nabladev.com?part=2 Closes: https://sashiko.dev/#/patchset/20260824110452.4038870-1-hs@nabladev.com?part=3 Link: https://patch.msgid.link/20260825071927.4090460-3-hs@nabladev.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rs5c372.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c index 24bd795d9d95..c65b76cc0dc6 100644 --- a/drivers/rtc/rtc-rs5c372.c +++ b/drivers/rtc/rtc-rs5c372.c @@ -68,6 +68,7 @@ enum rtc_type { rtc_undef = 0, rtc_r2025sd, rtc_r2221tl, + rtc_r2223x, rtc_rs5c372a, rtc_rs5c372b, rtc_rv5c386, @@ -77,6 +78,7 @@ enum rtc_type { static const struct i2c_device_id rs5c372_id[] = { { .name = "r2025sd", .driver_data = rtc_r2025sd }, { .name = "r2221tl", .driver_data = rtc_r2221tl }, + { .name = "r2223x", .driver_data = rtc_r2223x }, { .name = "rs5c372a", .driver_data = rtc_rs5c372a }, { .name = "rs5c372b", .driver_data = rtc_rs5c372b }, { .name = "rv5c386", .driver_data = rtc_rv5c386 }, @@ -94,6 +96,10 @@ static const __maybe_unused struct of_device_id rs5c372_of_match[] = { .compatible = "ricoh,r2221tl", .data = (void *)rtc_r2221tl }, + { + .compatible = "ricoh,r2223x", + .data = (void *)rtc_r2223x + }, { .compatible = "ricoh,rs5c372a", .data = (void *)rtc_rs5c372a @@ -221,8 +227,10 @@ static int rs5c372_rtc_read_time(struct device *dev, struct rtc_time *tm) switch (rs5c->type) { case rtc_r2025sd: case rtc_r2221tl: + case rtc_r2223x: if ((rs5c->type == rtc_r2025sd && !(ctrl2 & R2x2x_CTRL2_XSTP)) || - (rs5c->type == rtc_r2221tl && (ctrl2 & R2x2x_CTRL2_XSTP))) { + ((rs5c->type == rtc_r2221tl || rs5c->type == rtc_r2223x) && + (ctrl2 & R2x2x_CTRL2_XSTP))) { dev_warn(&client->dev, "rtc oscillator interruption detected. Please reset the rtc clock.\n"); return -EINVAL; } @@ -292,6 +300,7 @@ static int rs5c372_rtc_set_time(struct device *dev, struct rtc_time *tm) switch (rs5c->type) { case rtc_r2025sd: case rtc_r2221tl: + case rtc_r2223x: ctrl2 &= ~(R2x2x_CTRL2_VDET | R2x2x_CTRL2_PON); if (rs5c->type == rtc_r2025sd) ctrl2 |= R2x2x_CTRL2_XSTP; @@ -511,8 +520,10 @@ static int rs5c372_ioctl(struct device *dev, unsigned int cmd, unsigned long arg switch (rs5c->type) { case rtc_r2025sd: case rtc_r2221tl: + case rtc_r2223x: if ((rs5c->type == rtc_r2025sd && !(ctrl2 & R2x2x_CTRL2_XSTP)) || - (rs5c->type == rtc_r2221tl && (ctrl2 & R2x2x_CTRL2_XSTP))) { + ((rs5c->type == rtc_r2221tl || rs5c->type == rtc_r2223x) && + (ctrl2 & R2x2x_CTRL2_XSTP))) { flags |= RTC_VL_DATA_INVALID; } if (ctrl2 & R2x2x_CTRL2_VDET) @@ -527,7 +538,8 @@ static int rs5c372_ioctl(struct device *dev, unsigned int cmd, unsigned long arg return put_user(flags, (unsigned int __user *)arg); case RTC_VL_CLR: /* clear VDET bit */ - if (rs5c->type == rtc_r2025sd || rs5c->type == rtc_r2221tl) { + if (rs5c->type == rtc_r2025sd || rs5c->type == rtc_r2221tl || + rs5c->type == rtc_r2223x) { ctrl2 &= ~R2x2x_CTRL2_VDET; if (i2c_smbus_write_byte_data(rs5c->client, addr, ctrl2) < 0) { dev_dbg(&rs5c->client->dev, "%s: write error in line %i\n", @@ -554,6 +566,7 @@ static int rs5c372_read_offset(struct device *dev, long *offset) switch (rs5c->type) { case rtc_r2221tl: + case rtc_r2223x: ppb_per_step = val & R2221TL_TRIM_DEV ? 1017 : 3051; break; case rtc_rs5c372a: @@ -600,6 +613,7 @@ static int rs5c372_set_offset(struct device *dev, long offset) } break; case rtc_r2221tl: + case rtc_r2223x: /* * Check if it is possible to use high resolution mode (DEV=1). * In this mode, the minimum resolution is 2 / (32768 * 20 * 3), @@ -750,6 +764,7 @@ static int rs5c_oscillator_setup(struct rs5c372 *rs5c372) return ret; break; case rtc_r2221tl: + case rtc_r2223x: if (!(buf[1] & R2x2x_CTRL2_XSTP)) return ret; break; @@ -768,6 +783,7 @@ static int rs5c_oscillator_setup(struct rs5c372 *rs5c372) break; case rtc_r2025sd: case rtc_r2221tl: + case rtc_r2223x: case rtc_rv5c386: case rtc_rv5c387a: buf[0] |= RV5C387_CTRL1_24; @@ -847,6 +863,7 @@ static int rs5c372_probe(struct i2c_client *client) break; case rtc_r2025sd: case rtc_r2221tl: + case rtc_r2223x: case rtc_rv5c386: case rtc_rv5c387a: if (rs5c372->regs[RS5C_REG_CTRL1] & RV5C387_CTRL1_24) @@ -876,6 +893,7 @@ static int rs5c372_probe(struct i2c_client *client) ({ char *s; switch (rs5c372->type) { case rtc_r2025sd: s = "r2025sd"; break; case rtc_r2221tl: s = "r2221tl"; break; + case rtc_r2223x: s = "r2223x"; break; case rtc_rs5c372a: s = "rs5c372a"; break; case rtc_rs5c372b: s = "rs5c372b"; break; case rtc_rv5c386: s = "rv5c386"; break; From 95e83d98e10438bd76baaab029097ba54e3ea71b Mon Sep 17 00:00:00 2001 From: Heiko Schocher Date: Tue, 25 Aug 2026 09:19:18 +0200 Subject: [PATCH 41/89] rtc: rs5c372: support eco mode on R2223x The R2223x can run in an eco mode that lowers its current consumption from the backup supply. Follow the ricoh,eco-mode property from the device tree: enable the mode when the property is set, turn it off when it is not set, as the bit is in CTRL2 register, which is backed by the same supply and keeps its contents across a reset, and no other path in the driver touches it. Signed-off-by: Heiko Schocher Link: https://patch.msgid.link/20260825071927.4090460-4-hs@nabladev.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rs5c372.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c index c65b76cc0dc6..9ab6f2c28f7b 100644 --- a/drivers/rtc/rtc-rs5c372.c +++ b/drivers/rtc/rtc-rs5c372.c @@ -52,6 +52,7 @@ #define RS5C_REG_CTRL2 15 # define RS5C372_CTRL2_24 (1 << 5) # define RS5C_CTRL2_XSTP (1 << 4) /* only if !R2x2x */ +# define R2223x_CTRL2_ECO (1 << 7) /* only if R2223x */ # define R2x2x_CTRL2_VDET (1 << 6) /* only if R2x2x */ # define R2x2x_CTRL2_XSTP (1 << 5) /* only if R2x2x */ # define R2x2x_CTRL2_PON (1 << 4) /* only if R2x2x */ @@ -807,6 +808,24 @@ static int rs5c_oscillator_setup(struct rs5c372 *rs5c372) return 0; } +static int rs5c372_set_eco_mode(struct rs5c372 *rs5c372, bool eco) +{ + struct i2c_client *client = rs5c372->client; + int ctrl2; + + ctrl2 = i2c_smbus_read_byte_data(client, RS5C_ADDR(RS5C_REG_CTRL2)); + if (ctrl2 < 0) + return ctrl2; + + if (eco) + ctrl2 |= R2223x_CTRL2_ECO; + else + ctrl2 &= ~R2223x_CTRL2_ECO; + + return i2c_smbus_write_byte_data(client, RS5C_ADDR(RS5C_REG_CTRL2), + ctrl2); +} + static int rs5c372_probe(struct i2c_client *client) { int err = 0; @@ -903,6 +922,15 @@ static int rs5c372_probe(struct i2c_client *client) rs5c372->time24 ? "24hr" : "am/pm" ); + if (rs5c372->type == rtc_r2223x) { + bool eco = device_property_read_bool(&client->dev, + "ricoh,eco-mode"); + + err = rs5c372_set_eco_mode(rs5c372, eco); + if (err < 0) + goto exit; + } + /* REVISIT use client->irq to register alarm irq ... */ rs5c372->rtc = devm_rtc_device_register(&client->dev, rs5c372_driver.driver.name, From ae8b3e10087af04a33c024643342e017fd0eb134 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:02 +0200 Subject: [PATCH 42/89] rtc: introduce rtc_device.ops_lock guard() Add guard class for rtc_device.ops_lock locks. This allows to simplify error handling in interrupt handlers. Link: https://patch.msgid.link/20261019211827.126262-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- include/linux/rtc.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/rtc.h b/include/linux/rtc.h index 0f43b5027235..169d93520dbd 100644 --- a/include/linux/rtc.h +++ b/include/linux/rtc.h @@ -168,6 +168,9 @@ struct rtc_device { #define rtc_lock(d) mutex_lock(&d->ops_lock) #define rtc_unlock(d) mutex_unlock(&d->ops_lock) +DEFINE_GUARD(rtc_ops, struct rtc_device *, mutex_lock(&_T->ops_lock), + mutex_unlock(&_T->ops_lock)) + /* useful timestamps */ #define RTC_TIMESTAMP_BEGIN_0000 -62167219200ULL /* 0000-01-01 00:00:00 */ #define RTC_TIMESTAMP_BEGIN_1900 -2208988800LL /* 1900-01-01 00:00:00 */ From f49ca91f8a9a0d1686b3535b5421ee82fb8356bf Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:03 +0200 Subject: [PATCH 43/89] rtc: ac100: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-2-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ac100.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-ac100.c b/drivers/rtc/rtc-ac100.c index bba7115ff3ad..ee9fdb7fb63b 100644 --- a/drivers/rtc/rtc-ac100.c +++ b/drivers/rtc/rtc-ac100.c @@ -529,12 +529,12 @@ static irqreturn_t ac100_rtc_irq(int irq, void *data) unsigned int val = 0; int ret; - rtc_lock(chip->rtc); + guard(rtc_ops)(chip->rtc); /* read status */ ret = regmap_read(regmap, AC100_ALM_INT_STA, &val); if (ret) - goto out; + return IRQ_HANDLED; if (val & AC100_ALM_INT_ENABLE) { /* signal rtc framework */ @@ -543,16 +543,14 @@ static irqreturn_t ac100_rtc_irq(int irq, void *data) /* clear status */ ret = regmap_write(regmap, AC100_ALM_INT_STA, val); if (ret) - goto out; + return IRQ_HANDLED; /* disable interrupt */ ret = ac100_rtc_alarm_irq_enable(chip->dev, 0); if (ret) - goto out; + return IRQ_HANDLED; } -out: - rtc_unlock(chip->rtc); return IRQ_HANDLED; } From 217439c8fcd29fd35cb77e3c5be65ca23dbfe983 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:04 +0200 Subject: [PATCH 44/89] rtc: asm9260: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-3-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-asm9260.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-asm9260.c b/drivers/rtc/rtc-asm9260.c index 705470ae8428..422c2e5d83aa 100644 --- a/drivers/rtc/rtc-asm9260.c +++ b/drivers/rtc/rtc-asm9260.c @@ -116,15 +116,13 @@ static irqreturn_t asm9260_rtc_irq(int irq, void *dev_id) u32 isr; unsigned long events = 0; - rtc_lock(priv->rtc); + guard(rtc_ops)(priv->rtc); isr = ioread32(priv->iobase + HW_CIIR); if (!isr) { - rtc_unlock(priv->rtc); return IRQ_NONE; } iowrite32(0, priv->iobase + HW_CIIR); - rtc_unlock(priv->rtc); events |= RTC_AF | RTC_IRQF; From f371caa4209ce6a671edbd3191a0256702266288 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:05 +0200 Subject: [PATCH 45/89] rtc: atcrtc100: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-4-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-atcrtc100.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-atcrtc100.c b/drivers/rtc/rtc-atcrtc100.c index 9808fc2c5a49..2433df386c40 100644 --- a/drivers/rtc/rtc-atcrtc100.c +++ b/drivers/rtc/rtc-atcrtc100.c @@ -147,7 +147,7 @@ static void atcrtc_alarm_clear(struct work_struct *work) container_of(work, struct atcrtc_dev, rtc_work); int ret; - rtc_lock(rtc->rtc_dev); + guard(rtc_ops)(rtc->rtc_dev); if (!rtc->alarm_en) { ret = atcrtc_check_write_done(rtc); @@ -159,7 +159,6 @@ static void atcrtc_alarm_clear(struct work_struct *work) regmap_update_bits(rtc->regmap, RTC_CR, ALARM_WAKEUP | ALARM_INT, 0); } - rtc_unlock(rtc->rtc_dev); } static int atcrtc_read_time(struct device *dev, struct rtc_time *tm) From d1fd5232fadcdd59ac4cd5fc0d122da2f1f56048 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:06 +0200 Subject: [PATCH 46/89] rtc: ds1305: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-5-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1305.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-ds1305.c b/drivers/rtc/rtc-ds1305.c index d4de401548b4..19a32d3476b5 100644 --- a/drivers/rtc/rtc-ds1305.c +++ b/drivers/rtc/rtc-ds1305.c @@ -440,7 +440,7 @@ static void ds1305_work(struct work_struct *work) int status; /* lock to protect ds1305->ctrl */ - rtc_lock(ds1305->rtc); + guard(rtc_ops)(ds1305->rtc); /* Disable the IRQ, and clear its status ... for now, we "know" * that if more than one alarm is active, they're in sync. @@ -458,8 +458,6 @@ static void ds1305_work(struct work_struct *work) if (status < 0) dev_dbg(&spi->dev, "clear irq --> %d\n", status); - rtc_unlock(ds1305->rtc); - if (!test_bit(FLAG_EXITING, &ds1305->flags)) enable_irq(spi->irq); From 3571eedcfc34504e0a30602c224513d3afcc90a8 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:07 +0200 Subject: [PATCH 47/89] rtc: ds1307: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-6-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1307.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 522f7eef3b4a..0cf4836b81b1 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -579,28 +579,27 @@ static irqreturn_t rx8130_irq(int irq, void *dev_id) u8 ctl[3]; int ret; - rtc_lock(ds1307->rtc); + guard(rtc_ops)(ds1307->rtc); /* Read control registers. */ ret = regmap_bulk_read(ds1307->regmap, RX8130_REG_EXTENSION, ctl, sizeof(ctl)); if (ret < 0) - goto out; + return IRQ_HANDLED; + if (!(ctl[1] & RX8130_REG_FLAG_AF)) - goto out; + return IRQ_HANDLED; + ctl[1] &= ~RX8130_REG_FLAG_AF; ctl[2] &= ~RX8130_REG_CONTROL0_AIE; ret = regmap_bulk_write(ds1307->regmap, RX8130_REG_EXTENSION, ctl, sizeof(ctl)); if (ret < 0) - goto out; + return IRQ_HANDLED; rtc_update_irq(ds1307->rtc, 1, RTC_AF | RTC_IRQF); -out: - rtc_unlock(ds1307->rtc); - return IRQ_HANDLED; } From 0d1e6c4a0ccd917a2b42dc75dfc20a22f23ab042 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:08 +0200 Subject: [PATCH 48/89] rtc: ds1343: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-7-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1343.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-ds1343.c b/drivers/rtc/rtc-ds1343.c index 54f6af7886a8..22abec4ba656 100644 --- a/drivers/rtc/rtc-ds1343.c +++ b/drivers/rtc/rtc-ds1343.c @@ -312,11 +312,11 @@ static irqreturn_t ds1343_thread(int irq, void *dev_id) unsigned int stat; int res = 0; - rtc_lock(priv->rtc); + guard(rtc_ops)(priv->rtc); res = regmap_read(priv->map, DS1343_STATUS_REG, &stat); if (res) - goto out; + return IRQ_HANDLED; if (stat & DS1343_IRQF0) { stat &= ~DS1343_IRQF0; @@ -328,8 +328,6 @@ static irqreturn_t ds1343_thread(int irq, void *dev_id) DS1343_A0IE, 0); } -out: - rtc_unlock(priv->rtc); return IRQ_HANDLED; } From 5162e9ad18f85ac653fe325b21cdb974c4da1aa0 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:09 +0200 Subject: [PATCH 49/89] rtc: ds1685: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-8-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1685.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c index 5fc8e36b1abf..284815e5f478 100644 --- a/drivers/rtc/rtc-ds1685.c +++ b/drivers/rtc/rtc-ds1685.c @@ -666,7 +666,7 @@ ds1685_rtc_irq_handler(int irq, void *dev_id) if (unlikely(!rtc)) return IRQ_HANDLED; - rtc_lock(rtc->dev); + guard(rtc_ops)(rtc->dev); /* Ctrlb holds the interrupt-enable bits and ctrlc the flag bits. */ ctrlb = rtc->read(rtc, RTC_CTRL_B); @@ -711,7 +711,6 @@ ds1685_rtc_irq_handler(int irq, void *dev_id) } } rtc_update_irq(rtc->dev, num_irqs, events); - rtc_unlock(rtc->dev); return events ? IRQ_HANDLED : IRQ_NONE; } From 32643217d84ca78ea266d4b00ad8fc8f7c7a0226 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:10 +0200 Subject: [PATCH 50/89] rtc: ds3232: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-9-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds3232.c | 50 ++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c index d1ef9e0dad34..f6407c631d6d 100644 --- a/drivers/rtc/rtc-ds3232.c +++ b/drivers/rtc/rtc-ds3232.c @@ -389,45 +389,39 @@ static irqreturn_t ds3232_irq(int irq, void *dev_id) int ret; int stat, control; - rtc_lock(ds3232->rtc); + guard(rtc_ops)(ds3232->rtc); ret = regmap_read(ds3232->regmap, DS3232_REG_SR, &stat); if (ret) - goto unlock; + return IRQ_HANDLED; if (stat & DS3232_REG_SR_A1F) { ret = regmap_read(ds3232->regmap, DS3232_REG_CR, &control); if (ret) { dev_warn(ds3232->dev, "Read Control Register error %d\n", ret); - } else { - /* disable alarm1 interrupt */ - control &= ~(DS3232_REG_CR_A1IE); - ret = regmap_write(ds3232->regmap, DS3232_REG_CR, - control); - if (ret) { - dev_warn(ds3232->dev, - "Write Control Register error %d\n", - ret); - goto unlock; - } - - /* clear the alarm pend flag */ - stat &= ~DS3232_REG_SR_A1F; - ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat); - if (ret) { - dev_warn(ds3232->dev, - "Write Status Register error %d\n", - ret); - goto unlock; - } - - rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF); + return IRQ_HANDLED; + } + /* disable alarm1 interrupt */ + control &= ~(DS3232_REG_CR_A1IE); + ret = regmap_write(ds3232->regmap, DS3232_REG_CR, control); + if (ret) { + dev_warn(ds3232->dev, + "Write Control Register error %d\n", ret); + return IRQ_HANDLED; } - } -unlock: - rtc_unlock(ds3232->rtc); + /* clear the alarm pend flag */ + stat &= ~DS3232_REG_SR_A1F; + ret = regmap_write(ds3232->regmap, DS3232_REG_SR, stat); + if (ret) { + dev_warn(ds3232->dev, + "Write Status Register error %d\n", ret); + return IRQ_HANDLED; + } + + rtc_update_irq(ds3232->rtc, 1, RTC_AF | RTC_IRQF); + } return IRQ_HANDLED; } From f1104e3e629ae380c881efd534a623e42f31bb88 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:11 +0200 Subject: [PATCH 51/89] rtc: hym8563: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-10-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-hym8563.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-hym8563.c b/drivers/rtc/rtc-hym8563.c index 1d52cc3def02..e3d7ca477e75 100644 --- a/drivers/rtc/rtc-hym8563.c +++ b/drivers/rtc/rtc-hym8563.c @@ -417,7 +417,7 @@ static irqreturn_t hym8563_irq(int irq, void *dev_id) struct i2c_client *client = hym8563->client; int data, ret; - rtc_lock(hym8563->rtc); + guard(rtc_ops)(hym8563->rtc); /* Clear the alarm flag */ @@ -425,7 +425,7 @@ static irqreturn_t hym8563_irq(int irq, void *dev_id) if (data < 0) { dev_err(&client->dev, "%s: error reading i2c data %d\n", __func__, data); - goto out; + return IRQ_HANDLED; } data &= ~HYM8563_CTL2_AF; @@ -436,8 +436,6 @@ static irqreturn_t hym8563_irq(int irq, void *dev_id) __func__, ret); } -out: - rtc_unlock(hym8563->rtc); return IRQ_HANDLED; } From e2005cd977916f52d7dd1d1aa012e7ce3090b271 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:12 +0200 Subject: [PATCH 52/89] rtc: m41t80: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-11-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-m41t80.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/rtc/rtc-m41t80.c b/drivers/rtc/rtc-m41t80.c index ec86ca3f2c4c..1385cb43864b 100644 --- a/drivers/rtc/rtc-m41t80.c +++ b/drivers/rtc/rtc-m41t80.c @@ -162,19 +162,15 @@ static irqreturn_t m41t80_handle_irq(int irq, void *dev_id) unsigned long events = 0; int flags, flags_afe; - rtc_lock(m41t80->rtc); + guard(rtc_ops)(m41t80->rtc); flags_afe = i2c_smbus_read_byte_data(client, M41T80_REG_ALARM_MON); - if (flags_afe < 0) { - rtc_unlock(m41t80->rtc); + if (flags_afe < 0) return IRQ_NONE; - } flags = i2c_smbus_read_byte_data(client, M41T80_REG_FLAGS); - if (flags <= 0) { - rtc_unlock(m41t80->rtc); + if (flags <= 0) return IRQ_NONE; - } if (flags & M41T80_FLAGS_AF) { flags &= ~M41T80_FLAGS_AF; @@ -189,8 +185,6 @@ static irqreturn_t m41t80_handle_irq(int irq, void *dev_id) flags_afe); } - rtc_unlock(m41t80->rtc); - return IRQ_HANDLED; } From 279900b87b0691d1c1ba605e996123538d343e7a Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:13 +0200 Subject: [PATCH 53/89] rtc: mcp795: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-12-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mcp795.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-mcp795.c b/drivers/rtc/rtc-mcp795.c index e12f0f806ec4..9b5b36b88122 100644 --- a/drivers/rtc/rtc-mcp795.c +++ b/drivers/rtc/rtc-mcp795.c @@ -352,7 +352,7 @@ static irqreturn_t mcp795_irq(int irq, void *data) struct rtc_device *rtc = spi_get_drvdata(spi); int ret; - rtc_lock(rtc); + guard(rtc_ops)(rtc); /* Disable alarm. * There is no need to clear ALM0IF (Alarm 0 Interrupt Flag) bit, @@ -364,7 +364,6 @@ static irqreturn_t mcp795_irq(int irq, void *data) "Failed to disable alarm in IRQ (ret=%d)\n", ret); rtc_update_irq(rtc, 1, RTC_AF | RTC_IRQF); - rtc_unlock(rtc); return IRQ_HANDLED; } From ea0e1e30945df9ab9423ed1f95dd32d95803a07c Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:14 +0200 Subject: [PATCH 54/89] rtc: nct6694: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-13-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-nct6694.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-nct6694.c b/drivers/rtc/rtc-nct6694.c index 35401a0d9cf5..1cf9b672256a 100644 --- a/drivers/rtc/rtc-nct6694.c +++ b/drivers/rtc/rtc-nct6694.c @@ -210,20 +210,16 @@ static irqreturn_t nct6694_irq(int irq, void *dev_id) }; int ret; - rtc_lock(data->rtc); + guard(rtc_ops)(data->rtc); sts->irq_en = NCT6694_RTC_IRQ_EN; sts->irq_pend = NCT6694_RTC_IRQ_STS; ret = nct6694_write_msg(data->nct6694, &cmd_hd, sts); - if (ret) { - rtc_unlock(data->rtc); + if (ret) return IRQ_NONE; - } rtc_update_irq(data->rtc, 1, RTC_IRQF | RTC_AF); - rtc_unlock(data->rtc); - return IRQ_HANDLED; } From a127505cd05fa7725c7aa890d23b21dfed63eabf Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:15 +0200 Subject: [PATCH 55/89] rtc: pcf2123: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-14-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf2123.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-pcf2123.c b/drivers/rtc/rtc-pcf2123.c index e714661e61a9..22c626d57090 100644 --- a/drivers/rtc/rtc-pcf2123.c +++ b/drivers/rtc/rtc-pcf2123.c @@ -310,7 +310,8 @@ static irqreturn_t pcf2123_rtc_irq(int irq, void *dev) unsigned int val = 0; int ret = IRQ_NONE; - rtc_lock(pcf2123->rtc); + guard(rtc_ops)(pcf2123->rtc); + regmap_read(pcf2123->map, PCF2123_REG_CTRL2, &val); /* Alarm? */ @@ -323,8 +324,6 @@ static irqreturn_t pcf2123_rtc_irq(int irq, void *dev) rtc_update_irq(pcf2123->rtc, 1, RTC_IRQF | RTC_AF); } - rtc_unlock(pcf2123->rtc); - return ret; } From d82730f230c2bcd1459f920c87fb065d6aaeabe6 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:16 +0200 Subject: [PATCH 56/89] rtc: rv3029c2: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-15-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rv3029c2.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c index 98953af2a24a..c348b31928e9 100644 --- a/drivers/rtc/rtc-rv3029c2.c +++ b/drivers/rtc/rtc-rv3029c2.c @@ -270,19 +270,17 @@ static irqreturn_t rv3029_handle_irq(int irq, void *dev_id) unsigned long events = 0; int ret; - rtc_lock(rv3029->rtc); + guard(rtc_ops)(rv3029->rtc); ret = regmap_read(rv3029->regmap, RV3029_IRQ_CTRL, &controls); if (ret) { dev_warn(dev, "Read IRQ Control Register error %d\n", ret); - rtc_unlock(rv3029->rtc); return IRQ_NONE; } ret = regmap_read(rv3029->regmap, RV3029_IRQ_FLAGS, &flags); if (ret) { dev_warn(dev, "Read IRQ Flags Register error %d\n", ret); - rtc_unlock(rv3029->rtc); return IRQ_NONE; } @@ -297,7 +295,6 @@ static irqreturn_t rv3029_handle_irq(int irq, void *dev_id) regmap_write(rv3029->regmap, RV3029_IRQ_FLAGS, flags); regmap_write(rv3029->regmap, RV3029_IRQ_CTRL, controls); } - rtc_unlock(rv3029->rtc); return IRQ_HANDLED; } From bde8e458f87ae85da02854124cfd340d0a42dccb Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:17 +0200 Subject: [PATCH 57/89] rtc: rx8010: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-16-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rx8010.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-rx8010.c b/drivers/rtc/rtc-rx8010.c index ce3954132336..0a9be6ea62d0 100644 --- a/drivers/rtc/rtc-rx8010.c +++ b/drivers/rtc/rtc-rx8010.c @@ -73,11 +73,10 @@ static irqreturn_t rx8010_irq_1_handler(int irq, void *dev_id) struct rx8010_data *rx8010 = i2c_get_clientdata(client); int flagreg, err; - rtc_lock(rx8010->rtc); + guard(rtc_ops)(rx8010->rtc); err = regmap_read(rx8010->regs, RX8010_FLAG, &flagreg); if (err) { - rtc_unlock(rx8010->rtc); return IRQ_NONE; } @@ -100,7 +99,6 @@ static irqreturn_t rx8010_irq_1_handler(int irq, void *dev_id) } err = regmap_write(rx8010->regs, RX8010_FLAG, flagreg); - rtc_unlock(rx8010->rtc); return err ? IRQ_NONE : IRQ_HANDLED; } From eff3e65cdc4be8035a8fa06319a6d726ac998876 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:18 +0200 Subject: [PATCH 58/89] rtc: rx8025: use guard Use guard() to simplify error handling Link: https://patch.msgid.link/20261019211827.126262-17-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rx8025.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-rx8025.c b/drivers/rtc/rtc-rx8025.c index 5eeaa929f3ef..2f2deb59cbee 100644 --- a/drivers/rtc/rtc-rx8025.c +++ b/drivers/rtc/rtc-rx8025.c @@ -180,10 +180,11 @@ static irqreturn_t rx8025_handle_irq(int irq, void *dev_id) struct rx8025_data *rx8025 = i2c_get_clientdata(client); int status, xstp; - rtc_lock(rx8025->rtc); + guard(rtc_ops)(rx8025->rtc); + status = rx8025_read_reg(client, RX8025_REG_CTRL2); if (status < 0) - goto out; + return IRQ_HANDLED; xstp = rx8025_is_osc_stopped(rx8025->model, status); if (xstp) @@ -201,13 +202,10 @@ static irqreturn_t rx8025_handle_irq(int irq, void *dev_id) status &= RX8025_BIT_CTRL2_DAFG; if (rx8025_write_reg(client, RX8025_REG_CTRL1, rx8025->ctrl1 & ~RX8025_BIT_CTRL1_DALE)) - goto out; + return IRQ_HANDLED; rtc_update_irq(rx8025->rtc, 1, RTC_AF | RTC_IRQF); } -out: - rtc_unlock(rx8025->rtc); - return IRQ_HANDLED; } From 3d9796708cfaab80cbf3aaf7b775e67f7d195a09 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:19 +0200 Subject: [PATCH 59/89] rtc: stm32: use guard Use guard() to simplify locking Link: https://patch.msgid.link/20261019211827.126262-18-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-stm32.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-stm32.c b/drivers/rtc/rtc-stm32.c index d4ebf3eb54aa..1c4e4bd7afe4 100644 --- a/drivers/rtc/rtc-stm32.c +++ b/drivers/rtc/rtc-stm32.c @@ -465,7 +465,7 @@ static irqreturn_t stm32_rtc_alarm_irq(int irq, void *dev_id) const struct stm32_rtc_events *evts = &rtc->data->events; unsigned int status, cr; - rtc_lock(rtc->rtc_dev); + guard(rtc_ops)(rtc->rtc_dev); status = readl_relaxed(rtc->base + regs->sr); cr = readl_relaxed(rtc->base + regs->cr); @@ -482,8 +482,6 @@ static irqreturn_t stm32_rtc_alarm_irq(int irq, void *dev_id) stm32_rtc_clear_event_flags(rtc, evts->alra); } - rtc_unlock(rtc->rtc_dev); - return IRQ_HANDLED; } From 99473f37cce31b2a23d57bf90f830f24408fa650 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Mon, 19 Oct 2026 23:18:20 +0200 Subject: [PATCH 60/89] rtc: abx80x: simplify locking Use the ops_lock that is used by the core to lock operations. This ensures we will never get any ABBA deadlock. Link: https://patch.msgid.link/20261019211827.126262-19-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-abx80x.c | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/drivers/rtc/rtc-abx80x.c b/drivers/rtc/rtc-abx80x.c index 718e6b8555f5..1c69d0e2f940 100644 --- a/drivers/rtc/rtc-abx80x.c +++ b/drivers/rtc/rtc-abx80x.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -138,7 +137,6 @@ struct abx80x_priv { struct rtc_device *rtc; struct regmap *regmap; struct watchdog_device wdog; - struct mutex lock; int irq; }; @@ -249,8 +247,6 @@ static int abx80x_rtc_set_time(struct device *dev, struct rtc_time *tm) buf[ABX8XX_REG_YR] = bin2bcd(tm->tm_year - 100); buf[ABX8XX_REG_WD] = tm->tm_wday; - guard(mutex)(&priv->lock); - err = regmap_bulk_write(priv->regmap, ABX8XX_REG_HTH, buf, sizeof(buf)); if (err < 0) { @@ -274,7 +270,7 @@ static irqreturn_t abx80x_handle_irq(int irq, void *dev_id) unsigned int status, status_flags_clear; int err; - guard(mutex)(&priv->lock); + guard(rtc_ops)(rtc); err = regmap_read(priv->regmap, ABX8XX_REG_STATUS, &status); if (err < 0) @@ -349,8 +345,6 @@ static int abx80x_set_alarm(struct device *dev, struct rtc_wkalrm *t) alarm[4] = bin2bcd(t->time.tm_mday); alarm[5] = bin2bcd(t->time.tm_mon + 1); - guard(mutex)(&priv->lock); - err = regmap_bulk_write(priv->regmap, ABX8XX_REG_AHTH, alarm, sizeof(alarm)); if (err < 0) { @@ -391,7 +385,7 @@ static int abx80x_rtc_set_autocalibration(struct device *dev, flags |= (ABX8XX_OSC_ACAL_1024 | ABX8XX_OSC_ACAL_512); } - guard(mutex)(&priv->lock); + guard(rtc_ops)(priv->rtc); /* Unlock write access to Oscillator Control Register */ if (abx80x_write_config_key(dev, ABX8XX_CFG_KEY_OSC) < 0) @@ -473,7 +467,7 @@ static ssize_t oscillator_store(struct device *dev, return -EINVAL; } - guard(mutex)(&priv->lock); + guard(rtc_ops)(priv->rtc); /* Unlock write access on Oscillator Control register */ if (abx80x_write_config_key(dev->parent, ABX8XX_CFG_KEY_OSC) < 0) @@ -523,8 +517,6 @@ static int abx80x_alarm_irq_enable(struct device *dev, unsigned int enabled) struct abx80x_priv *priv = dev_get_drvdata(dev); int err; - guard(mutex)(&priv->lock); - if (enabled) err = regmap_write(priv->regmap, ABX8XX_REG_IRQ, ABX8XX_IRQ_IM_1_4 | ABX8XX_IRQ_AIE); @@ -551,10 +543,8 @@ static int abx80x_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) return put_user(tmp, (unsigned int __user *)arg); case RTC_VL_CLR: - scoped_guard(mutex, &priv->lock) { - err = regmap_update_bits(priv->regmap, ABX8XX_REG_STATUS, - ABX8XX_STATUS_BLF, 0); - } + err = regmap_update_bits(priv->regmap, ABX8XX_REG_STATUS, + ABX8XX_STATUS_BLF, 0); return err; @@ -623,7 +613,7 @@ static int __abx80x_wdog_set_timeout(struct watchdog_device *wdog, struct abx80x_priv *priv = watchdog_get_drvdata(wdog); u8 val = ABX8XX_WDT_WDS | timeout_bits(timeout); - guard(mutex)(&priv->lock); + guard(rtc_ops)(priv->rtc); /* * Writing any timeout to the WDT register resets the watchdog timer. @@ -710,7 +700,7 @@ static int abx80x_nvmem_xfer(struct abx80x_priv *priv, unsigned int offset, len = min(lower + bytes, (size_t)ABX8XX_SRAM_WIN_SIZE) - lower; len = min_t(u8, len, I2C_SMBUS_BLOCK_MAX); - guard(mutex)(&priv->lock); + guard(rtc_ops)(priv->rtc); ret = regmap_update_bits(priv->regmap, ABX8XX_REG_EXTRAM, ABX8XX_EXTRAM_XADS, upper); @@ -806,9 +796,6 @@ static int abx80x_probe(struct device *dev, struct regmap *regmap, int irq, priv->rtc->ops = &abx80x_rtc_ops; priv->irq = irq; priv->regmap = regmap; - err = devm_mutex_init(dev, &priv->lock); - if (err) - return err; dev_set_drvdata(dev, priv); From d691a96370b6cd7e684e4da1c473c09df963611b Mon Sep 17 00:00:00 2001 From: "Baek Kyu, Kim" Date: Tue, 20 Oct 2026 00:10:24 +0900 Subject: [PATCH 61/89] rtc: mpfs: Fix alarm register word order mpfs_rtc_readalarm() swaps the low and high words of the alarm time. The RTC core reads this value during device registration to initialize its alarm timer. This can populate the cached alarm and timer queue with an incorrect expiry time, or leave an enabled hardware alarm out of the timer queue if the decoded time appears to be in the past. Read the low 32 bits from ALARM_LOWER_REG and place the masked high 11 bits from ALARM_UPPER_REG in bits 42:32. This matches the layout used by mpfs_rtc_setalarm() and the low/high assembly in mpfs_rtc_readtime(), so the RTC core imports the programmed alarm time correctly during device registration. Fixes: 0b31d703598d ("rtc: Add driver for Microchip PolarFire SoC") Signed-off-by: Baek Kyu, Kim Reviewed-by: Conor Dooley Link: https://patch.msgid.link/20261019151024.2232338-1-pocj8ur4in@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-mpfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-mpfs.c b/drivers/rtc/rtc-mpfs.c index ece6de4a6adb..caabb50d3f9c 100644 --- a/drivers/rtc/rtc-mpfs.c +++ b/drivers/rtc/rtc-mpfs.c @@ -131,8 +131,8 @@ static int mpfs_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm) alrm->enabled = mode & MODE_WAKE_EN; - time = (u64)readl(rtcdev->base + ALARM_LOWER_REG) << 32; - time |= (readl(rtcdev->base + ALARM_UPPER_REG) & ALARM_UPPER_MASK); + time = readl(rtcdev->base + ALARM_LOWER_REG); + time |= ((u64)readl(rtcdev->base + ALARM_UPPER_REG) & ALARM_UPPER_MASK) << 32; rtc_time64_to_tm(time, &alrm->time); return 0; From b253c2e96a80aac8e2bc67b425bf007ed78d4d8c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Tue, 20 Oct 2026 09:59:21 +0200 Subject: [PATCH 62/89] rtc: class: Make the time32 hctosys limit configurable The RTC_HCTOSYS feature faces an impossible dilemma on 32-bit systems: a) systemd requires RTC_HCTOSYS=y with the correct timestamp for its intended operation b) broken RTC hardware can produce random future timestamps beyond 2038 at boot time c) time32 userspace immediately hangs if the time is set to a date beyond 2038 and requires timestamps to be clamped d) time64 userspace is intended to survive the actual 2038 date and requires timestamps to not be clamped Commit b3a5ac42ab18 ("rtc: hctosys: Ensure system time doesn't overflow time_t") introduced this limit to protect time32 userspace from future RTC dates that can break boot and address constraints a), b) and c), but broke d) in the process by limiting RTC_HCTOSYS to the time32 range and falling back to RTC_HCTOSYS=n behavior when the RTC time overflows that. Make the behavior configurable with a new RTC_HCTOSYS_TIME32_LIMIT option that chooses between breaking c) or d). On systems with COMPAT_32BIT_TIME=n, only RTC_HCTOSYS_TIME32_LIMIT=n is allowed since userspace can be assumed to be fully built for time64. 64-bit kernels running 32-bit userland never had the cutoff and continue to not offer the option. Warn on rejection with the RTC date and option name so users can identify the cause and find the setting. [arnd: reword Kconfig text and patch description, add COMPAT_32BIT_TIME dependency] ---- I ended up rewriting most of the text but kept Karl's ownership as he did the original patch. Fixes: b3a5ac42ab18 ("rtc: hctosys: Ensure system time doesn't overflow time_t") Link: https://lore.kernel.org/all/20220908115337.1604277-1-arnd@kernel.org/ Assisted-by: LLM Signed-off-by: Karl Mehltretter Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20261020080026.2461578-1-arnd@kernel.org Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 21 +++++++++++++++++++++ drivers/rtc/class.c | 8 +++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 243edd37c0d7..1e863f1d36fd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -30,6 +30,27 @@ config RTC_HCTOSYS the value read from a specified RTC device. This is useful to avoid unnecessary fsck runs at boot time, and to network better. +config RTC_HCTOSYS_TIME32_LIMIT + bool "Reject RTC dates after the 2038 cutoff" + depends on RTC_HCTOSYS && COMPAT_32BIT_TIME && !64BIT + default y + help + Reject RTC dates after 03:14:07 UTC on 19 January 2038 when + initializing the system clock in order to work around broken + RTC implementations that produce nondeterministic timestamps + without a working backup battery. + + A rejected date leaves system time unchanged, normally near + the Unix epoch, and the RTC's hctosys sysfs attribute reads + 0. Userspace must supply the time if no other source has + initialized it. + + Say Y here if the kernel may have to run old time32 userspace on + hardware that can produce unpredictable RTC timestamps. If the + kernel is intended to only run time64 compatible userspace and + continue working past 2038, you must pick N here in order to + have a working RTC_HCTOSYS functionality. + config RTC_HCTOSYS_DEVICE string "RTC used to set the system time" depends on RTC_HCTOSYS diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index 01ba04028f1f..18ea141ab8c2 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c @@ -72,12 +72,14 @@ static void rtc_hctosys(struct rtc_device *rtc) tv64.tv_sec = rtc_tm_to_time64(&tm); -#if BITS_PER_LONG == 32 - if (tv64.tv_sec > INT_MAX) { + if (IS_ENABLED(CONFIG_RTC_HCTOSYS_TIME32_LIMIT) && + tv64.tv_sec > INT_MAX) { + dev_warn(rtc->dev.parent, + "hctosys: rejecting %ptR UTC due to CONFIG_RTC_HCTOSYS_TIME32_LIMIT=y\n", + &tm); err = -ERANGE; goto err_read; } -#endif err = do_settimeofday64(&tv64); From 0bbeed584e92744c30858233d498215a32faa8eb Mon Sep 17 00:00:00 2001 From: Guoming Chen Date: Fri, 21 Aug 2026 21:16:44 +0800 Subject: [PATCH 63/89] rtc: ds1307: drop redundant dev_set_drvdata() in probe i2c_set_clientdata() is a thin wrapper around dev_set_drvdata(&client->dev, ...), so calling both in ds1307_probe() stores the same driver-data pointer twice. The two calls were introduced together in the regmap conversion in commit 11e5890b5342c ("rtc: ds1307: convert driver to regmap"). Keep i2c_set_clientdata(), which matches the convention used by every other I2C RTC driver in the subsystem, and drop the redundant dev_set_drvdata(). Signed-off-by: Guoming Chen Link: https://patch.msgid.link/20260821131644.71794-1-chenguoming88s@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1307.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 0cf4836b81b1..8627f84232ce 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -1890,7 +1890,6 @@ static int ds1307_probe(struct i2c_client *client) if (!ds1307) return -ENOMEM; - dev_set_drvdata(&client->dev, ds1307); ds1307->dev = &client->dev; ds1307->name = client->name; From c1c260266abf5d600bc7ce8efbd274ca86f9a4aa Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Thu, 1 Oct 2026 14:41:08 +0900 Subject: [PATCH 64/89] rtc: nct3018y: report chassis intrusion through hwmon Add an hwmon intrusion alarm for the chip's chassis intrusion latch. Reading it says whether the chassis has been opened since the latch was last cleared; writing 0 clears it and re-arms. Signed-off-by: Eric Liu Link: https://patch.msgid.link/20261001054108.3346426-1-liuer@nvidia.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-nct3018y.c | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/drivers/rtc/rtc-nct3018y.c b/drivers/rtc/rtc-nct3018y.c index 2f7ad57057a4..194ba077363a 100644 --- a/drivers/rtc/rtc-nct3018y.c +++ b/drivers/rtc/rtc-nct3018y.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #define NCT3018Y_REG_CTRL 0x0A /* timer control */ #define NCT3018Y_REG_ST 0x0B /* status */ #define NCT3018Y_REG_CLKO 0x0C /* clock out */ +#define NCT3018Y_REG_INTR_CTRL 0x12 /* intrusion control */ #define NCT3018Y_REG_PART 0x21 /* part info */ #define NCT3018Y_BIT_AF BIT(7) @@ -34,6 +36,7 @@ #define NCT3018Y_BIT_OFIE BIT(2) #define NCT3018Y_BIT_CIE BIT(1) #define NCT3018Y_BIT_TWO BIT(0) +#define NCT3018Y_BIT_INTRUSION BIT(1) /* in NCT3018Y_REG_INTR_CTRL */ #define NCT3018Y_REG_BAT_MASK 0x07 #define NCT3018Y_REG_CLKO_F_MASK 0x03 /* frequenc mask */ @@ -491,6 +494,95 @@ static const struct rtc_class_ops nct3018y_rtc_ops = { .ioctl = nct3018y_ioctl, }; +#if IS_REACHABLE(CONFIG_HWMON) + +static umode_t nct3018y_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type == hwmon_intrusion && attr == hwmon_intrusion_alarm) + return 0644; + + return 0; +} + +static int nct3018y_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *val) +{ + struct nct3018y *nct3018y = dev_get_drvdata(dev); + int flags; + + flags = i2c_smbus_read_byte_data(nct3018y->client, + NCT3018Y_REG_INTR_CTRL); + if (flags < 0) + return flags; + + *val = !!(flags & NCT3018Y_BIT_INTRUSION); + + return 0; +} + +static int nct3018y_hwmon_write(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct nct3018y *nct3018y = dev_get_drvdata(dev); + int flags; + + /* The alarm latches until it is cleared, and only clearing is a + * meaningful thing to ask for. + */ + if (val) + return -EINVAL; + + flags = i2c_smbus_read_byte_data(nct3018y->client, + NCT3018Y_REG_INTR_CTRL); + if (flags < 0) + return flags; + + return i2c_smbus_write_byte_data(nct3018y->client, + NCT3018Y_REG_INTR_CTRL, + flags & ~NCT3018Y_BIT_INTRUSION); +} + +static const struct hwmon_channel_info * const nct3018y_hwmon_info[] = { + HWMON_CHANNEL_INFO(intrusion, HWMON_INTRUSION_ALARM), + NULL +}; + +static const struct hwmon_ops nct3018y_hwmon_ops = { + .is_visible = nct3018y_hwmon_is_visible, + .read = nct3018y_hwmon_read, + .write = nct3018y_hwmon_write, +}; + +static const struct hwmon_chip_info nct3018y_hwmon_chip_info = { + .ops = &nct3018y_hwmon_ops, + .info = nct3018y_hwmon_info, +}; + +static void nct3018y_hwmon_register(struct nct3018y *nct3018y) +{ + struct device *dev; + + dev = devm_hwmon_device_register_with_info(&nct3018y->client->dev, + "nct3018y", nct3018y, + &nct3018y_hwmon_chip_info, + NULL); + if (IS_ERR(dev)) + dev_warn(&nct3018y->client->dev, + "unable to register hwmon device %ld\n", PTR_ERR(dev)); +} + +#else + +static void nct3018y_hwmon_register(struct nct3018y *nct3018y) +{ +} + +#endif /* IS_REACHABLE(CONFIG_HWMON) */ + static int nct3018y_probe(struct i2c_client *client) { struct nct3018y *nct3018y; @@ -568,6 +660,8 @@ static int nct3018y_probe(struct i2c_client *client) nct3018y_clkout_register_clk(nct3018y); #endif + nct3018y_hwmon_register(nct3018y); + return devm_rtc_register_device(nct3018y->rtc); } From 2166aaa173cd5b2f2f3bbd397469900bafa12f17 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:46 +0000 Subject: [PATCH 65/89] dt-bindings: rtc: Add pinctrl for S35390A Synology NAS devices use the output pin for interrupt signal 1 to wake up the system. Move devicetree bindings for sii,s35390a into its own file. Add necessary properties to configure the individual pins via pinctrl, which allows the interrupt signal 1 to be used for wakeup alarm. Signed-off-by: Markus Probst Reviewed-by: Rob Herring (Arm) Reviewed-by: Linus Walleij Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-1-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- .../devicetree/bindings/rtc/sii,s35390a.yaml | 118 ++++++++++++++++++ .../devicetree/bindings/rtc/trivial-rtc.yaml | 2 - 2 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/rtc/sii,s35390a.yaml diff --git a/Documentation/devicetree/bindings/rtc/sii,s35390a.yaml b/Documentation/devicetree/bindings/rtc/sii,s35390a.yaml new file mode 100644 index 000000000000..f86572e9faff --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/sii,s35390a.yaml @@ -0,0 +1,118 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/sii,s35390a.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: S-35390A 2-wire real-time clock + +maintainers: + - Markus Probst + +description: + The S-35390A is a CMOS 2-wire real-time clock IC which operates with the + very low current consumption in the wide range of operation voltage. + +allOf: + - $ref: rtc.yaml# + +properties: + compatible: + const: sii,s35390a + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + description: Supports up to 2 interrupt lines via the INT1 and INT2 pins. + + interrupt-names: + minItems: 1 + maxItems: 2 + items: + enum: + - int1 + - int2 + + wakeup-parent: true + + wakeup-source: true + + "#clock-cells": + const: 1 + +patternProperties: + "^pins": + type: object + additionalProperties: false + + patternProperties: + "-pins$": + $ref: /schemas/pinctrl/pinmux-node.yaml# + additionalProperties: false + + properties: + pins: + minItems: 1 + items: + enum: + - int1 + - int2 + + function: + description: | + Pin function: + - ignore: Preserve the previous state. + - disable: Disable pin output. + - wakeup: Output wakes up the system. + - clock: Output clock pulse. + - pmin1: Minute periodical output with 50% duty. + - pmin2: Minute periodical output L for 7.81 ms. + Can only be used with pin int1. + enum: + - ignore + - disable + - wakeup + - clock + - pmin1 + - pmin2 + + required: + - pins + - function + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + rtc@30 { + compatible = "sii,s35390a"; + reg = <0x30>; + pinctrl-0 = <&rtc_pins_default>; + pinctrl-names = "default"; + + interrupt-names = "int1"; + interrupts = <23>; + + rtc_pins_default: pins { + int1-pins { + pins = "int1"; + function = "wakeup"; + }; + int2-pins { + pins = "int2"; + function = "disable"; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml index f4d0eed98a08..0bdf0ab4ff97 100644 --- a/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml +++ b/Documentation/devicetree/bindings/rtc/trivial-rtc.yaml @@ -81,8 +81,6 @@ properties: - ricoh,rv5c386 # I2C bus SERIAL INTERFACE REAL-TIME CLOCK IC - ricoh,rv5c387a - # 2-wire CMOS real-time clock - - sii,s35390a # ST SPEAr Real-time Clock - st,spear600-rtc # VIA/Wondermedia VT8500 Real-time Clock From a27b25bb9b422c8468cb6965b2d72b7b64187fd2 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:47 +0000 Subject: [PATCH 66/89] rtc: s35390a: Add missing newline to dev_err Fixes: 3bd32722c827d ("rtc: s35390a: improve irq handling") Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-2-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s35390a.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index b72eef4fb099..4cfe7034c516 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -494,7 +494,7 @@ static int s35390a_probe(struct i2c_client *client) buf = 0; err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &buf, 1); if (err < 0) { - dev_err(dev, "error disabling alarm"); + dev_err(dev, "error disabling alarm\n"); return err; } } else { From d93f29e41089bbb14637289fe77bdd72b71e9ac5 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:49 +0000 Subject: [PATCH 67/89] rtc: s35390a: Fix alarm not disabling Implement alarm_irq_enable callback. Fixes: 542dd33a4925 ("drivers/rtc/rtc-s35390a.c: add wakealarm support for rtc-s35390A rtc chip") Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-3-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s35390a.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index 4cfe7034c516..575bb256eb25 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -270,6 +270,24 @@ static int s35390a_rtc_read_time(struct device *dev, struct rtc_time *tm) return 0; } +static int s35390a_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct s35390a *s35390a = dev_get_drvdata(dev); + u8 sts; + int err; + + if (enabled) + sts = S35390A_INT2_MODE_ALARM; + else + sts = S35390A_INT2_MODE_NOINTR; + + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); + if (err < 0) + return err; + + return 0; +} + static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) { struct i2c_client *client = to_i2c_client(dev); @@ -410,11 +428,12 @@ static int s35390a_rtc_ioctl(struct device *dev, unsigned int cmd, } static const struct rtc_class_ops s35390a_rtc_ops = { - .read_time = s35390a_rtc_read_time, - .set_time = s35390a_rtc_set_time, - .set_alarm = s35390a_rtc_set_alarm, - .read_alarm = s35390a_rtc_read_alarm, - .ioctl = s35390a_rtc_ioctl, + .read_time = s35390a_rtc_read_time, + .set_time = s35390a_rtc_set_time, + .set_alarm = s35390a_rtc_set_alarm, + .read_alarm = s35390a_rtc_read_alarm, + .alarm_irq_enable = s35390a_rtc_alarm_irq_enable, + .ioctl = s35390a_rtc_ioctl, }; static int s35390a_nvmem_read(void *priv, unsigned int offset, void *val, From 6199051016cddba920b6023a44199c5221352c44 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:50 +0000 Subject: [PATCH 68/89] rtc: s35390a: Read 24-hour mode on access Instead of reading if the 24-hour mode is used on probe once, read it on access. This makes it impossible for the mode to be out of sync and fixes time corruption if resetting the chip while in 12-hour mode, as `s35390a_init` did not update the cached value. Fixes: 16486d0c1c65 ("rtc: s35390a: handle invalid RTC time") Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-4-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s35390a.c | 78 +++++++++++++++++++++------------------ 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index 575bb256eb25..12ed504db94d 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -64,7 +64,6 @@ MODULE_DEVICE_TABLE(of, s35390a_of_match); struct s35390a { struct i2c_client *client[8]; - int twentyfourhour; }; static int s35390a_set_reg(struct s35390a *s35390a, int reg, u8 *buf, int len) @@ -102,9 +101,8 @@ static int s35390a_get_reg(struct s35390a *s35390a, int reg, u8 *buf, int len) return 0; } -static int s35390a_init(struct s35390a *s35390a) +static int s35390a_init(struct s35390a *s35390a, u8 *sts) { - u8 buf; int ret; unsigned initcount = 0; @@ -117,17 +115,17 @@ static int s35390a_init(struct s35390a *s35390a) * The 24H bit is kept over reset, so set it already here. */ initialize: - buf = S35390A_FLAG_RESET | S35390A_FLAG_24H; - ret = s35390a_set_reg(s35390a, S35390A_CMD_STATUS1, &buf, 1); + *sts = S35390A_FLAG_RESET | S35390A_FLAG_24H; + ret = s35390a_set_reg(s35390a, S35390A_CMD_STATUS1, sts, 1); if (ret < 0) return ret; - ret = s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, &buf, 1); + ret = s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, sts, 1); if (ret < 0) return ret; - if (buf & (S35390A_FLAG_POC | S35390A_FLAG_BLD)) { + if (*sts & (S35390A_FLAG_POC | S35390A_FLAG_BLD)) { /* Try up to five times to reset the chip */ if (initcount < 5) { ++initcount; @@ -181,9 +179,9 @@ static int s35390a_disable_test_mode(struct s35390a *s35390a) return s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, buf, sizeof(buf)); } -static char s35390a_hr2reg(struct s35390a *s35390a, int hour) +static char s35390a_hr2reg(int hour, bool twentyfourhour) { - if (s35390a->twentyfourhour) + if (twentyfourhour) return bin2bcd(hour); if (hour < 12) @@ -192,11 +190,11 @@ static char s35390a_hr2reg(struct s35390a *s35390a, int hour) return 0x40 | bin2bcd(hour - 12); } -static int s35390a_reg2hr(struct s35390a *s35390a, char reg) +static int s35390a_reg2hr(char reg, bool twentyfourhour) { unsigned hour; - if (s35390a->twentyfourhour) + if (twentyfourhour) return bcd2bin(reg & 0x3f); hour = bcd2bin(reg & 0x3f); @@ -210,7 +208,7 @@ static int s35390a_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct i2c_client *client = to_i2c_client(dev); struct s35390a *s35390a = i2c_get_clientdata(client); - int i; + int i, err; u8 buf[7], status; dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d mday=%d, " @@ -218,14 +216,18 @@ static int s35390a_rtc_set_time(struct device *dev, struct rtc_time *tm) tm->tm_min, tm->tm_hour, tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday); - if (s35390a_read_status(s35390a, &status) == 1) - s35390a_init(s35390a); + err = s35390a_read_status(s35390a, &status); + if (err == 1) + err = s35390a_init(s35390a, &status); + + if (err < 0) + return err; buf[S35390A_BYTE_YEAR] = bin2bcd(tm->tm_year - 100); buf[S35390A_BYTE_MONTH] = bin2bcd(tm->tm_mon + 1); buf[S35390A_BYTE_DAY] = bin2bcd(tm->tm_mday); buf[S35390A_BYTE_WDAY] = bin2bcd(tm->tm_wday); - buf[S35390A_BYTE_HOURS] = s35390a_hr2reg(s35390a, tm->tm_hour); + buf[S35390A_BYTE_HOURS] = s35390a_hr2reg(tm->tm_hour, status & S35390A_FLAG_24H); buf[S35390A_BYTE_MINS] = bin2bcd(tm->tm_min); buf[S35390A_BYTE_SECS] = bin2bcd(tm->tm_sec); @@ -243,8 +245,11 @@ static int s35390a_rtc_read_time(struct device *dev, struct rtc_time *tm) u8 buf[7], status; int i, err; - if (s35390a_read_status(s35390a, &status) == 1) + err = s35390a_read_status(s35390a, &status); + if (err == 1) return -EINVAL; + if (err < 0) + return err; err = s35390a_get_reg(s35390a, S35390A_CMD_TIME1, buf, sizeof(buf)); if (err < 0) @@ -256,7 +261,7 @@ static int s35390a_rtc_read_time(struct device *dev, struct rtc_time *tm) tm->tm_sec = bcd2bin(buf[S35390A_BYTE_SECS]); tm->tm_min = bcd2bin(buf[S35390A_BYTE_MINS]); - tm->tm_hour = s35390a_reg2hr(s35390a, buf[S35390A_BYTE_HOURS]); + tm->tm_hour = s35390a_reg2hr(buf[S35390A_BYTE_HOURS], status & S35390A_FLAG_24H); tm->tm_wday = bcd2bin(buf[S35390A_BYTE_WDAY]); tm->tm_mday = bcd2bin(buf[S35390A_BYTE_DAY]); tm->tm_mon = bcd2bin(buf[S35390A_BYTE_MONTH]) - 1; @@ -292,7 +297,7 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) { struct i2c_client *client = to_i2c_client(dev); struct s35390a *s35390a = i2c_get_clientdata(client); - u8 buf[3], sts = 0; + u8 buf[3], status1, status2 = 0; int err, i; dev_dbg(&client->dev, "%s: alm is secs=%d, mins=%d, hours=%d mday=%d, "\ @@ -301,22 +306,22 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) alm->time.tm_mon, alm->time.tm_year, alm->time.tm_wday); /* disable interrupt (which deasserts the irq line) */ - err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); if (err < 0) return err; /* clear pending interrupt (in STATUS1 only), if any */ - err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, &sts, sizeof(sts)); + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS1, &status1, sizeof(status1)); if (err < 0) return err; if (alm->enabled) - sts = S35390A_INT2_MODE_ALARM; + status2 = S35390A_INT2_MODE_ALARM; else - sts = S35390A_INT2_MODE_NOINTR; + status2 = S35390A_INT2_MODE_NOINTR; /* set interrupt mode*/ - err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); if (err < 0) return err; @@ -325,8 +330,8 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) else buf[S35390A_ALRM_BYTE_WDAY] = 0; - buf[S35390A_ALRM_BYTE_HOURS] = s35390a_hr2reg(s35390a, - alm->time.tm_hour) | 0x80; + buf[S35390A_ALRM_BYTE_HOURS] = s35390a_hr2reg(alm->time.tm_hour, + status1 & S35390A_FLAG_24H) | 0x80; buf[S35390A_ALRM_BYTE_MINS] = bin2bcd(alm->time.tm_min) | 0x80; if (alm->time.tm_hour >= 12) @@ -345,14 +350,20 @@ static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) { struct i2c_client *client = to_i2c_client(dev); struct s35390a *s35390a = i2c_get_clientdata(client); - u8 buf[3], sts; + u8 buf[3], status1, status2; int i, err; - err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); + err = s35390a_read_status(s35390a, &status1); + if (err == 1) + return -EINVAL; if (err < 0) return err; - if ((sts & S35390A_INT2_MODE_MASK) != S35390A_INT2_MODE_ALARM) { + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); + if (err < 0) + return err; + + if ((status2 & S35390A_INT2_MODE_MASK) != S35390A_INT2_MODE_ALARM) { /* * When the alarm isn't enabled, the register to configure * the alarm time isn't accessible. @@ -381,8 +392,8 @@ static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) if (buf[S35390A_ALRM_BYTE_HOURS] & 0x80) alm->time.tm_hour = - s35390a_reg2hr(s35390a, - buf[S35390A_ALRM_BYTE_HOURS] & ~0x80); + s35390a_reg2hr(buf[S35390A_ALRM_BYTE_HOURS] & ~0x80, + status1 & S35390A_FLAG_24H); if (buf[S35390A_ALRM_BYTE_MINS] & 0x80) alm->time.tm_min = bcd2bin(buf[S35390A_ALRM_BYTE_MINS] & ~0x80); @@ -416,7 +427,7 @@ static int s35390a_rtc_ioctl(struct device *dev, unsigned int cmd, break; case RTC_VL_CLR: /* update flag and clear register */ - err = s35390a_init(s35390a); + err = s35390a_init(s35390a, &sts); if (err < 0) return err; break; @@ -503,11 +514,6 @@ static int s35390a_probe(struct i2c_client *client) return err_read; } - if (status1 & S35390A_FLAG_24H) - s35390a->twentyfourhour = 1; - else - s35390a->twentyfourhour = 0; - if (status1 & S35390A_FLAG_INT2) { /* disable alarm (and maybe test mode) */ buf = 0; From cb4e92b151141d2babd2f89e9f67599d38be3878 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:52 +0000 Subject: [PATCH 69/89] rtc: s35390a: Add pinctrl Allow configuration of pin functions, including: - keeping the previous configured function - disabling output - waking up the system - minute periodical interrupt (1: 50% duty, 2: 7.81 ms) If no function has been configured, fallback to previous behaviour. Reviewed-by: Linus Walleij Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-5-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- drivers/rtc/Kconfig | 2 + drivers/rtc/rtc-s35390a.c | 375 +++++++++++++++++++++++++++++++------- 2 files changed, 313 insertions(+), 64 deletions(-) diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 1e863f1d36fd..73ec9230edfd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -678,7 +678,9 @@ config RTC_DRV_RC5T619 config RTC_DRV_S35390A tristate "Seiko Instruments S-35390A" + select PINCTRL select BITREVERSE + select GENERIC_PINCONF help If you say yes here you will get support for the Seiko Instruments S-35390A. diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index 12ed504db94d..649d36d37f1f 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -12,11 +12,17 @@ #include #include #include +#include +#include +#include + +#define DRIVER_NAME "rtc-s35390a" #define S35390A_CMD_STATUS1 0 #define S35390A_CMD_STATUS2 1 #define S35390A_CMD_TIME1 2 #define S35390A_CMD_TIME2 3 +#define S35390A_CMD_INT1_REG1 4 #define S35390A_CMD_INT2_REG1 5 #define S35390A_CMD_FREE_REG 7 @@ -36,19 +42,38 @@ #define S35390A_FLAG_POC BIT(0) #define S35390A_FLAG_BLD BIT(1) #define S35390A_FLAG_INT2 BIT(2) +#define S35390A_FLAG_INT1 BIT(3) #define S35390A_FLAG_24H BIT(6) #define S35390A_FLAG_RESET BIT(7) /* flag for STATUS2 */ #define S35390A_FLAG_TEST BIT(0) +#define S35390A_INT_MODE_NOINTR 0x00 + /* INT2 pin output mode */ #define S35390A_INT2_MODE_MASK 0x0E -#define S35390A_INT2_MODE_NOINTR 0x00 #define S35390A_INT2_MODE_ALARM BIT(1) /* INT2AE */ #define S35390A_INT2_MODE_PMIN_EDG BIT(2) /* INT2ME */ #define S35390A_INT2_MODE_FREQ BIT(3) /* INT2FE */ -#define S35390A_INT2_MODE_PMIN (BIT(3) | BIT(2)) /* INT2FE | INT2ME */ +#define S35390A_INT2_MODE_PMIN1 (BIT(3) | BIT(2)) /* INT2FE | INT2ME */ + +/* INT1 pin output mode */ +#define S35390A_INT1_MODE_MASK 0xF0 +#define S35390A_INT1_MODE_ALARM BIT(5) /* INT1AE */ +#define S35390A_INT1_MODE_PMIN_EDG BIT(6) /* INT1ME */ +#define S35390A_INT1_MODE_FREQ BIT(7) /* INT1FE */ +#define S35390A_INT1_MODE_PMIN1 (BIT(7) | BIT(6)) /* INT1FE | INT1ME */ +#define S35390A_INT1_MODE_PMIN2 (BIT(7) | BIT(6) | BIT(5)) /* INT1FE | INT1ME | INT1AE */ +#define S35390A_INT1_MODE_32768KHZ BIT(4) /* 32kE */ + +#define S35390A_FUNC_IGNORE 0x00 +#define S35390A_FUNC_DISABLE 0x01 +#define S35390A_FUNC_WAKEUP 0x02 +#define S35390A_FUNC_CLOCK 0x03 +#define S35390A_FUNC_PMIN1 0x04 +#define S35390A_FUNC_PMIN2 0x05 + static const struct i2c_device_id s35390a_id[] = { { .name = "s35390a" }, @@ -64,6 +89,10 @@ MODULE_DEVICE_TABLE(of, s35390a_of_match); struct s35390a { struct i2c_client *client[8]; + struct rtc_device *rtc; + + struct mutex pinfunction_lock; /* lock preventing concurrent access of pin function */ + int pinfunction[2]; }; static int s35390a_set_reg(struct s35390a *s35390a, int reg, u8 *buf, int len) @@ -165,20 +194,6 @@ static int s35390a_read_status(struct s35390a *s35390a, char *status1) return 0; } -static int s35390a_disable_test_mode(struct s35390a *s35390a) -{ - u8 buf[1]; - - if (s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, buf, sizeof(buf)) < 0) - return -EIO; - - if (!(buf[0] & S35390A_FLAG_TEST)) - return 0; - - buf[0] &= ~S35390A_FLAG_TEST; - return s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, buf, sizeof(buf)); -} - static char s35390a_hr2reg(int hour, bool twentyfourhour) { if (twentyfourhour) @@ -281,10 +296,25 @@ static int s35390a_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled u8 sts; int err; - if (enabled) - sts = S35390A_INT2_MODE_ALARM; - else - sts = S35390A_INT2_MODE_NOINTR; + guard(mutex)(&s35390a->pinfunction_lock); + + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); + if (err < 0) + return err; + + if (enabled) { + if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP) + sts = (sts & ~S35390A_INT1_MODE_MASK) | S35390A_INT1_MODE_ALARM; + + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP) + sts = (sts & ~S35390A_INT2_MODE_MASK) | S35390A_INT2_MODE_ALARM; + } else { + if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP) + sts = (sts & ~S35390A_INT1_MODE_MASK) | S35390A_INT_MODE_NOINTR; + + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP) + sts = (sts & ~S35390A_INT2_MODE_MASK) | S35390A_INT_MODE_NOINTR; + } err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &sts, sizeof(sts)); if (err < 0) @@ -305,7 +335,19 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) alm->time.tm_min, alm->time.tm_hour, alm->time.tm_mday, alm->time.tm_mon, alm->time.tm_year, alm->time.tm_wday); + guard(mutex)(&s35390a->pinfunction_lock); + + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); + if (err < 0) + return err; + /* disable interrupt (which deasserts the irq line) */ + if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP) + status2 = (status2 & ~S35390A_INT1_MODE_MASK) | S35390A_INT_MODE_NOINTR; + + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP) + status2 = (status2 & ~S35390A_INT2_MODE_MASK) | S35390A_INT_MODE_NOINTR; + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); if (err < 0) return err; @@ -315,16 +357,6 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) if (err < 0) return err; - if (alm->enabled) - status2 = S35390A_INT2_MODE_ALARM; - else - status2 = S35390A_INT2_MODE_NOINTR; - - /* set interrupt mode*/ - err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); - if (err < 0) - return err; - if (alm->time.tm_wday != -1) buf[S35390A_ALRM_BYTE_WDAY] = bin2bcd(alm->time.tm_wday) | 0x80; else @@ -340,10 +372,32 @@ static int s35390a_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) for (i = 0; i < 3; ++i) buf[i] = bitrev8(buf[i]); - err = s35390a_set_reg(s35390a, S35390A_CMD_INT2_REG1, buf, - sizeof(buf)); + if (alm->enabled) { + /* set interrupt mode */ + if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP) + status2 = (status2 & ~S35390A_INT1_MODE_MASK) | S35390A_INT1_MODE_ALARM; - return err; + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP) + status2 = (status2 & ~S35390A_INT2_MODE_MASK) | S35390A_INT2_MODE_ALARM; + + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); + if (err < 0) + return err; + } + + if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP) { + err = s35390a_set_reg(s35390a, S35390A_CMD_INT1_REG1, buf, sizeof(buf)); + if (err < 0) + return err; + } + + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP) { + err = s35390a_set_reg(s35390a, S35390A_CMD_INT2_REG1, buf, sizeof(buf)); + if (err < 0) + return err; + } + + return 0; } static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) @@ -351,7 +405,9 @@ static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) struct i2c_client *client = to_i2c_client(dev); struct s35390a *s35390a = i2c_get_clientdata(client); u8 buf[3], status1, status2; - int i, err; + int i, err, reg; + + guard(mutex)(&s35390a->pinfunction_lock); err = s35390a_read_status(s35390a, &status1); if (err == 1) @@ -363,18 +419,24 @@ static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) if (err < 0) return err; - if ((status2 & S35390A_INT2_MODE_MASK) != S35390A_INT2_MODE_ALARM) { + if (s35390a->pinfunction[1] == S35390A_FUNC_WAKEUP && + (status2 & S35390A_INT2_MODE_MASK) == S35390A_INT2_MODE_ALARM) { + reg = S35390A_CMD_INT2_REG1; + } else if (s35390a->pinfunction[0] == S35390A_FUNC_WAKEUP && + (status2 & S35390A_INT1_MODE_MASK) == S35390A_INT1_MODE_ALARM) { + reg = S35390A_CMD_INT1_REG1; + } else { /* * When the alarm isn't enabled, the register to configure * the alarm time isn't accessible. */ alm->enabled = 0; return 0; - } else { - alm->enabled = 1; } - err = s35390a_get_reg(s35390a, S35390A_CMD_INT2_REG1, buf, sizeof(buf)); + alm->enabled = 1; + + err = s35390a_get_reg(s35390a, reg, buf, sizeof(buf)); if (err < 0) return err; @@ -383,7 +445,7 @@ static int s35390a_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) buf[i] = bitrev8(buf[i]); /* - * B0 of the three matching registers is an enable flag. Iff it is set + * B0 of the three matching registers is an enable flag. If it is set * the configured value is used for matching. */ if (buf[S35390A_ALRM_BYTE_WDAY] & 0x80) @@ -464,13 +526,168 @@ static int s35390a_nvmem_write(void *priv, unsigned int offset, void *val, return s35390a_set_reg(s35390a, S35390A_CMD_FREE_REG, val, bytes); } +static const struct pinctrl_pin_desc s35390a_pins_desc[] = { + PINCTRL_PIN(0, "int1"), + PINCTRL_PIN(1, "int2"), +}; + +static const unsigned int int1_pins[] = { 0 }; +static const unsigned int int2_pins[] = { 1 }; + +static const struct pingroup s35390a_pin_groups[] = { + PINCTRL_PINGROUP("int1_grp", int1_pins, ARRAY_SIZE(int1_pins)), + PINCTRL_PINGROUP("int2_grp", int2_pins, ARRAY_SIZE(int2_pins)), +}; + +static int s35390a_pinctrl_get_groups_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(s35390a_pin_groups); +} + +static const char *s35390a_pinctrl_get_group_name(struct pinctrl_dev *pctldev, + unsigned int group) +{ + return s35390a_pin_groups[group].name; +} + +static int s35390a_pinctrl_get_group_pins(struct pinctrl_dev *pctldev, unsigned int selector, + const unsigned int **pins, unsigned int *npins) +{ + *pins = s35390a_pin_groups[selector].pins; + *npins = s35390a_pin_groups[selector].npins; + return 0; +} + +static const char * const all_groups[] = { "int1_grp", "int2_grp" }; +static const char * const int1_groups[] = { "int1_grp" }; + +static const struct pinfunction s35390a_functions[] = { + [S35390A_FUNC_IGNORE] = PINCTRL_PINFUNCTION("ignore", all_groups, ARRAY_SIZE(all_groups)), + [S35390A_FUNC_DISABLE] = PINCTRL_PINFUNCTION("disable", all_groups, ARRAY_SIZE(all_groups)), + [S35390A_FUNC_WAKEUP] = PINCTRL_PINFUNCTION("wakeup", all_groups, ARRAY_SIZE(all_groups)), + [S35390A_FUNC_CLOCK] = PINCTRL_PINFUNCTION("clock", all_groups, ARRAY_SIZE(all_groups)), + [S35390A_FUNC_PMIN1] = PINCTRL_PINFUNCTION("pmin1", all_groups, ARRAY_SIZE(all_groups)), + [S35390A_FUNC_PMIN2] = PINCTRL_PINFUNCTION("pmin2", int1_groups, ARRAY_SIZE(int1_groups)), +}; + +static int s35390a_pinctrl_get_functions_count(struct pinctrl_dev *pctldev) +{ + return ARRAY_SIZE(s35390a_functions); +} + +static const char *s35390a_pinctrl_get_function_name(struct pinctrl_dev *pctldev, + unsigned int selector) +{ + return s35390a_functions[selector].name; +} + +static int s35390a_pinctrl_get_function_groups(struct pinctrl_dev *pctldev, unsigned int selector, + const char * const **groups, + unsigned int * const ngroups) +{ + *groups = s35390a_functions[selector].groups; + *ngroups = s35390a_functions[selector].ngroups; + return 0; +} + +static int s35390a_pinctrl_set_mux(struct pinctrl_dev *pctldev, unsigned int function, + unsigned int group) +{ + int err; + u8 status2, flag, mask; + struct s35390a *s35390a = pinctrl_dev_get_drvdata(pctldev); + + mask = group == 0 ? S35390A_INT1_MODE_MASK : S35390A_INT2_MODE_MASK; + + guard(mutex)(&s35390a->pinfunction_lock); + + dev_dbg(&s35390a->client[0]->dev, "%s: function=%d group=%d\n", + __func__, function, group); + + if (function == s35390a->pinfunction[group]) + return 0; + + if (function != S35390A_FUNC_IGNORE) { + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &status2, 1); + if (err < 0) { + dev_err(&s35390a->client[0]->dev, "error reading status\n"); + return err; + } + + switch (function) { + case S35390A_FUNC_DISABLE: + case S35390A_FUNC_CLOCK: /* not implemented */ + status2 = (status2 & ~mask) | S35390A_INT_MODE_NOINTR; + break; + case S35390A_FUNC_WAKEUP: + flag = group == 0 ? S35390A_INT1_MODE_ALARM : S35390A_INT2_MODE_ALARM; + + if ((status2 & mask) != flag) + status2 = (status2 & ~mask) | S35390A_INT_MODE_NOINTR; + + break; + case S35390A_FUNC_PMIN1: + flag = group == 0 ? S35390A_INT1_MODE_PMIN1 : S35390A_INT2_MODE_PMIN1; + status2 = (status2 & ~mask) | flag; + break; + + /* INT1 only modes */ + case S35390A_FUNC_PMIN2: + if (group == 1) + return -EINVAL; + + status2 = (status2 & ~mask) | S35390A_INT1_MODE_PMIN2; + break; + } + + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, 1); + if (err < 0) { + dev_err(&s35390a->client[0]->dev, "error setting interrupts\n"); + return err; + } + } + + s35390a->pinfunction[group] = function; + + return 0; +} + +static const struct pinctrl_ops s35390a_pinctrl_ops = { + .get_groups_count = s35390a_pinctrl_get_groups_count, + .get_group_name = s35390a_pinctrl_get_group_name, + .get_group_pins = s35390a_pinctrl_get_group_pins, +#if IS_ENABLED(CONFIG_OF) + .dt_node_to_map = pinconf_generic_dt_node_to_map_all, + .dt_free_map = pinconf_generic_dt_free_map +#endif +}; + +static const struct pinmux_ops s35390a_pinmux_ops = { + .get_functions_count = s35390a_pinctrl_get_functions_count, + .get_function_name = s35390a_pinctrl_get_function_name, + .get_function_groups = s35390a_pinctrl_get_function_groups, + .set_mux = s35390a_pinctrl_set_mux, + .strict = true, +}; + +static struct pinctrl_desc s35390a_pinctrl_desc = { + .name = DRIVER_NAME, + .pins = s35390a_pins_desc, + .npins = ARRAY_SIZE(s35390a_pins_desc), + .pctlops = &s35390a_pinctrl_ops, + .pmxops = &s35390a_pinmux_ops, + .owner = THIS_MODULE, +}; + static int s35390a_probe(struct i2c_client *client) { - int err, err_read; + int err; unsigned int i; struct s35390a *s35390a; struct rtc_device *rtc; - u8 buf, status1; + struct pinctrl_dev *pctl; + u8 status1, status2; + bool irq = false; struct device *dev = &client->dev; struct nvmem_config nvmem_cfg = { .name = "s35390a_nvram", @@ -481,6 +698,7 @@ static int s35390a_probe(struct i2c_client *client) .reg_read = s35390a_nvmem_read, .reg_write = s35390a_nvmem_write, }; + int fallback[ARRAY_SIZE(s35390a_pin_groups)]; if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; @@ -489,7 +707,11 @@ static int s35390a_probe(struct i2c_client *client) if (!s35390a) return -ENOMEM; + mutex_init(&s35390a->pinfunction_lock); + memset(s35390a->pinfunction, -1, sizeof(s35390a->pinfunction)); + s35390a->client[0] = client; + i2c_set_clientdata(client, s35390a); /* This chip uses multiple addresses, use dummy devices for them */ @@ -504,33 +726,34 @@ static int s35390a_probe(struct i2c_client *client) } } - rtc = devm_rtc_allocate_device(dev); - if (IS_ERR(rtc)) - return PTR_ERR(rtc); - - err_read = s35390a_read_status(s35390a, &status1); - if (err_read < 0) { + err = s35390a_read_status(s35390a, &status1); + if (err < 0) { dev_err(dev, "error resetting chip\n"); - return err_read; + return err; + } + + err = s35390a_get_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); + if (err < 0) + return dev_err_probe(dev, err, "disabling alarm and test mode failed\n"); + + if (status1 & S35390A_FLAG_INT1) { + status2 = (status2 & ~S35390A_INT1_MODE_MASK) | S35390A_INT_MODE_NOINTR; + irq = true; } if (status1 & S35390A_FLAG_INT2) { - /* disable alarm (and maybe test mode) */ - buf = 0; - err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &buf, 1); - if (err < 0) { - dev_err(dev, "error disabling alarm\n"); - return err; - } - } else { - err = s35390a_disable_test_mode(s35390a); - if (err < 0) { - dev_err(dev, "error disabling test mode\n"); - return err; - } + status2 = (status2 & ~S35390A_INT2_MODE_MASK) | S35390A_INT_MODE_NOINTR; + irq = true; } - device_set_wakeup_capable(dev, 1); + status2 &= ~S35390A_FLAG_TEST; + err = s35390a_set_reg(s35390a, S35390A_CMD_STATUS2, &status2, sizeof(status2)); + if (err < 0) + return dev_err_probe(dev, err, "disabling alarm and test mode failed\n"); + + rtc = devm_rtc_allocate_device(dev); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); rtc->ops = &s35390a_rtc_ops; rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; @@ -539,9 +762,33 @@ static int s35390a_probe(struct i2c_client *client) set_bit(RTC_FEATURE_ALARM_RES_MINUTE, rtc->features); clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, rtc->features); - if (status1 & S35390A_FLAG_INT2) + s35390a->rtc = rtc; + + device_set_wakeup_capable(dev, 1); + + if (irq) rtc_update_irq(rtc, 1, RTC_AF); + err = devm_pinctrl_register_and_init(dev, &s35390a_pinctrl_desc, s35390a, &pctl); + if (err) + return dev_err_probe(dev, err, "pinctrl register failed\n"); + + err = pinctrl_enable(pctl); + if (err) + return dev_err_probe(dev, err, "pinctrl enable failed\n"); + + /* If no pinmux function is defined in DT, fallback to previous behaviour */ + fallback[0] = S35390A_FUNC_IGNORE; + fallback[1] = S35390A_FUNC_WAKEUP; + + for (i = 0; i < ARRAY_SIZE(s35390a_pin_groups); i++) { + if (s35390a->pinfunction[i] == -1) { + err = s35390a_pinctrl_set_mux(pctl, fallback[i], i); + if (err) + return err; + } + } + nvmem_cfg.priv = s35390a; err = devm_rtc_nvmem_register(rtc, &nvmem_cfg); if (err) @@ -552,7 +799,7 @@ static int s35390a_probe(struct i2c_client *client) static struct i2c_driver s35390a_driver = { .driver = { - .name = "rtc-s35390a", + .name = DRIVER_NAME, .of_match_table = of_match_ptr(s35390a_of_match), }, .probe = s35390a_probe, From f6550426f21ec5293feddb74368be6f4e9eefac1 Mon Sep 17 00:00:00 2001 From: Markus Probst Date: Wed, 30 Sep 2026 15:31:53 +0000 Subject: [PATCH 70/89] rtc: s35390a: Add synology quirk Synology NAS devices use interrupt signal 1 for wakeup alarms. On ACPI it is not possible to configure pinctrl. Use interrupt signal 1 for wakeup if no pinmux function has been configured in devicetree and dmi sys vendor matches "Synology Inc.". Signed-off-by: Markus Probst Link: https://patch.msgid.link/20260930-rtc_s35390a_int1-v10-6-e7b1cbc50a7b@posteo.de Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-s35390a.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-s35390a.c b/drivers/rtc/rtc-s35390a.c index 649d36d37f1f..d3e829052927 100644 --- a/drivers/rtc/rtc-s35390a.c +++ b/drivers/rtc/rtc-s35390a.c @@ -5,6 +5,7 @@ * Copyright (c) 2007 Byron Bradley */ +#include #include #include #include @@ -778,8 +779,18 @@ static int s35390a_probe(struct i2c_client *client) return dev_err_probe(dev, err, "pinctrl enable failed\n"); /* If no pinmux function is defined in DT, fallback to previous behaviour */ - fallback[0] = S35390A_FUNC_IGNORE; - fallback[1] = S35390A_FUNC_WAKEUP; + + if (dmi_match(DMI_SYS_VENDOR, "Synology Inc.")) { + /* + * Synology uses interrupt signal 1 for wakeup. Since pinctrl cannot be + * configured on ACPI, we check against the dmi sys vendor. + */ + fallback[0] = S35390A_FUNC_WAKEUP; + fallback[1] = S35390A_FUNC_DISABLE; + } else { + fallback[0] = S35390A_FUNC_IGNORE; + fallback[1] = S35390A_FUNC_WAKEUP; + } for (i = 0; i < ARRAY_SIZE(s35390a_pin_groups); i++) { if (s35390a->pinfunction[i] == -1) { From a451b6bb922ede877b141beb09c3d2eb3cc9edfe Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Fri, 28 Aug 2026 18:44:39 +0200 Subject: [PATCH 71/89] rtc: pcf85063: use devm_of_clk_add_hw_provider() for clkout pcf85063_clkout_register_clk() registered the OF clock provider with the deprecated of_clk_add_provider() and never removed it, so the provider kept pointing at freed data after unbind, leading to a use-after-free the next time the device-tree clock was resolved. Switch to devm_clk_hw_register() and devm_of_clk_add_hw_provider(): both the clock and its OF provider are managed resources now, and the provider is torn down before the clock on unbind. Register the clock on the parent i2c device instead of the rtc device. devm_of_clk_add_hw_provider() must use the parent, which owns the of_node, and the clkout_hw it points at lives in the driver data allocated on the parent. Tying both to the parent releases them together on unbind, before the driver data is freed, and keeps their teardown ordering guaranteed by a single devres list. Registering the clock on the rtc device instead could defer its unregistration past unbind (e.g. while a /dev/rtcN fd is open), after clkout_hw has already been freed. Signed-off-by: Alexander Sverdlin Link: https://patch.msgid.link/20260828164445.3907839-2-alexander.sverdlin@siemens.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index 8cb9ffc73f6d..ac9ab1c376c0 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -495,12 +495,13 @@ static const struct clk_ops pcf85063_clkout_ops = { .set_rate = pcf85063_clkout_set_rate, }; -static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) +static int pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) { - struct clk *clk; + struct device *dev = pcf85063->rtc->dev.parent; struct clk_init_data init = {}; - struct device_node *node = pcf85063->rtc->dev.parent->of_node; + struct device_node *node = dev->of_node; struct device_node *fixed_clock; + int ret; fixed_clock = of_get_child_by_name(node, "clock"); if (fixed_clock) { @@ -510,7 +511,7 @@ static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) * registered automatically when being referenced. */ of_node_put(fixed_clock); - return NULL; + return 0; } init.name = "pcf85063-clkout"; @@ -524,12 +525,12 @@ static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063) of_property_read_string(node, "clock-output-names", &init.name); /* register the clock */ - clk = devm_clk_register(&pcf85063->rtc->dev, &pcf85063->clkout_hw); + ret = devm_clk_hw_register(dev, &pcf85063->clkout_hw); + if (ret) + return ret; - if (!IS_ERR(clk)) - of_clk_add_provider(node, of_clk_src_simple_get, clk); - - return clk; + return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &pcf85063->clkout_hw); } #endif From 75ba9f3a6a0bb2153a97e5d9c3d186b6c6e517af Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Fri, 28 Aug 2026 18:44:40 +0200 Subject: [PATCH 72/89] rtc: pcf85063: do not clear AIE in the interrupt handler The interrupt handler cleared the alarm interrupt enable (AIE) bit while acknowledging the alarm flag (AF). This races with process-context updates of AIE in pcf85063_rtc_alarm_irq_enable() and pcf85063_rtc_set_alarm(): a handler running concurrently can clobber an AIE change. The RTC core already disables the alarm interrupt after a one-shot alarm via the .alarm_irq_enable callback, so the handler does not need to touch AIE at all. Acknowledge AF only and leave AIE management to the core. Signed-off-by: Alexander Sverdlin Link: https://patch.msgid.link/20260828164445.3907839-3-alexander.sverdlin@siemens.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index ac9ab1c376c0..af900fe8ae15 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -238,8 +238,7 @@ static irqreturn_t pcf85063_rtc_handle_irq(int irq, void *dev_id) if (val & PCF85063_CTRL2_AF) { rtc_update_irq(pcf85063->rtc, 1, RTC_IRQF | RTC_AF); regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF, - 0); + PCF85063_CTRL2_AF, 0); return IRQ_HANDLED; } From 09f5d9b8caafdfed36fb8115348e2cf1f1c31793 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Fri, 28 Aug 2026 18:44:41 +0200 Subject: [PATCH 73/89] rtc: pcf85063: preserve the alarm flag in clkout register updates The clkout rate and enable/disable helpers update CTRL2 with regmap_update_bits() but leave the alarm flag (AF) out of the mask. AF is write-0-to-clear, so an AF that gets asserted between the read and the write of the read-modify-write is written back as 0 and the pending alarm interrupt is lost. Add AF to the mask and write it back as 1 (write-1-to-preserve) so a concurrently asserted alarm flag survives a clkout reconfiguration. Signed-off-by: Alexander Sverdlin Link: https://patch.msgid.link/20260828164445.3907839-4-alexander.sverdlin@siemens.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index af900fe8ae15..a3af86456ccf 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -431,7 +431,8 @@ static int pcf85063_clkout_set_rate(struct clk_hw *hw, unsigned long rate, if (clkout_rates[i] == rate) return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_REG_CLKO_F_MASK, i); + PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF, + i | PCF85063_CTRL2_AF); return -EINVAL; } @@ -460,7 +461,8 @@ static int pcf85063_clkout_control(struct clk_hw *hw, bool enable) } return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_REG_CLKO_F_MASK, buf); + PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF, + buf | PCF85063_CTRL2_AF); } static int pcf85063_clkout_prepare(struct clk_hw *hw) From 45efb584a1c0bcd6d75a239dd8ce3ca554a66cf5 Mon Sep 17 00:00:00 2001 From: Alexander Sverdlin Date: Fri, 28 Aug 2026 18:44:42 +0200 Subject: [PATCH 74/89] rtc: pcf85063: add 1 Hz update interrupt (UIE) support The PCF85063A/RV8263 have no dedicated 1 Hz update interrupt, but their countdown timer can be sourced from a 1 Hz clock (TCF = 1 Hz, T = 1) to generate a periodic interrupt on the INT pin. Use it to offload UIE to the hardware, following the pattern of other RTC drivers: the timer is enabled from set_alarm()/alarm_irq_enable() whenever uie_rtctimer is active, and the IRQ handler reports RTC_UF when the timer flag (TF) is set. RTC_FEATURE_UPDATE_INTERRUPT is now only cleared when no usable interrupt is wired, preserving the polling emulation for those configurations. Co-developed-by: Adrian Freihofer Signed-off-by: Adrian Freihofer Signed-off-by: Alexander Sverdlin Link: https://patch.msgid.link/20260828164445.3907839-5-alexander.sverdlin@siemens.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf85063.c | 123 ++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 14 deletions(-) diff --git a/drivers/rtc/rtc-pcf85063.c b/drivers/rtc/rtc-pcf85063.c index a3af86456ccf..32952e73f2f1 100644 --- a/drivers/rtc/rtc-pcf85063.c +++ b/drivers/rtc/rtc-pcf85063.c @@ -44,6 +44,7 @@ #define PCF85063_REG_CTRL2 0x01 #define PCF85063_CTRL2_AF BIT(6) #define PCF85063_CTRL2_AIE BIT(7) +#define PCF85063_CTRL2_TF BIT(3) #define PCF85063_REG_OFFSET 0x02 #define PCF85063_OFFSET_SIGN_BIT 6 /* 2's complement sign bit */ @@ -63,6 +64,14 @@ #define PCF85063_REG_ALM_S 0x0b #define PCF85063_AEN BIT(7) +#define PCF85063_REG_TIMER_VALUE 0x10 +#define PCF85063_REG_TIMER_MODE 0x11 +#define PCF85063_TIMER_MODE_TI_TP BIT(0) +#define PCF85063_TIMER_MODE_TIE BIT(1) +#define PCF85063_TIMER_MODE_TE BIT(2) +#define PCF85063_TIMER_MODE_TCF_MASK GENMASK(4, 3) +#define PCF85063_TIMER_MODE_TCF_1HZ (2 << 3) + struct pcf85063_config { struct regmap_config regmap; unsigned has_alarms:1; @@ -188,20 +197,75 @@ static int pcf85063_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) return 0; } +/* + * The chip has no dedicated 1 Hz update interrupt. Drive the countdown timer + * at 1 Hz to emulate it, so the RTC core can offload UIE to the hardware + * instead of polling. A free-running timer is left untouched to preserve its + * phase across the per-second re-arming done by the core. + */ +static int pcf85063_set_timer_1hz(struct pcf85063 *pcf85063, bool enable) +{ + unsigned int mask = PCF85063_TIMER_MODE_TCF_MASK | + PCF85063_TIMER_MODE_TIE | + PCF85063_TIMER_MODE_TI_TP | + PCF85063_TIMER_MODE_TE; + unsigned int mode = 0; + unsigned int cur; + int ret; + + if (enable) + mode = PCF85063_TIMER_MODE_TCF_1HZ | PCF85063_TIMER_MODE_TIE | + PCF85063_TIMER_MODE_TI_TP | PCF85063_TIMER_MODE_TE; + + ret = regmap_read(pcf85063->regmap, PCF85063_REG_TIMER_MODE, &cur); + if (ret) + return ret; + + if ((cur & mask) == mode) + return 0; + + /* Stop the counter before changing its reload value. */ + ret = regmap_update_bits(pcf85063->regmap, PCF85063_REG_TIMER_MODE, + PCF85063_TIMER_MODE_TE, 0); + if (ret) + return ret; + + if (enable) { + ret = regmap_write(pcf85063->regmap, PCF85063_REG_TIMER_VALUE, 1); + if (ret) + return ret; + + /* Clear TF but preserve AF */ + ret = regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, + PCF85063_CTRL2_TF | PCF85063_CTRL2_AF, PCF85063_CTRL2_AF); + if (ret) + return ret; + } + + return regmap_update_bits(pcf85063->regmap, PCF85063_REG_TIMER_MODE, + mask, mode); +} + static int pcf85063_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) { struct pcf85063 *pcf85063 = dev_get_drvdata(dev); u8 buf[5]; int ret; + ret = pcf85063_set_timer_1hz(pcf85063, pcf85063->rtc->uie_rtctimer.enabled); + if (ret) + return ret; + buf[0] = bin2bcd(alrm->time.tm_sec); buf[1] = bin2bcd(alrm->time.tm_min); buf[2] = bin2bcd(alrm->time.tm_hour); buf[3] = bin2bcd(alrm->time.tm_mday); buf[4] = PCF85063_AEN; /* Do not match on week day */ + /* Preserve TF */ ret = regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF, 0); + PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF, + PCF85063_CTRL2_TF); if (ret) return ret; @@ -211,23 +275,33 @@ static int pcf85063_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) return ret; return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF, - alrm->enabled ? PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF : PCF85063_CTRL2_AF); + PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF, + pcf85063->rtc->aie_timer.enabled ? + PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF : + PCF85063_CTRL2_AF | PCF85063_CTRL2_TF); } static int pcf85063_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) { struct pcf85063 *pcf85063 = dev_get_drvdata(dev); + int ret; + + ret = pcf85063_set_timer_1hz(pcf85063, pcf85063->rtc->uie_rtctimer.enabled); + if (ret) + return ret; return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_CTRL2_AIE, - enabled ? PCF85063_CTRL2_AIE : 0); + PCF85063_CTRL2_AIE | PCF85063_CTRL2_TF, + pcf85063->rtc->aie_timer.enabled ? + PCF85063_CTRL2_AIE | PCF85063_CTRL2_TF : PCF85063_CTRL2_TF); } static irqreturn_t pcf85063_rtc_handle_irq(int irq, void *dev_id) { struct pcf85063 *pcf85063 = dev_id; + unsigned long events = RTC_IRQF; + irqreturn_t ret = IRQ_NONE; unsigned int val; int err; @@ -236,13 +310,33 @@ static irqreturn_t pcf85063_rtc_handle_irq(int irq, void *dev_id) return IRQ_NONE; if (val & PCF85063_CTRL2_AF) { - rtc_update_irq(pcf85063->rtc, 1, RTC_IRQF | RTC_AF); - regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_CTRL2_AF, 0); - return IRQ_HANDLED; + events |= RTC_AF; + val &= ~PCF85063_CTRL2_AF; + ret = IRQ_HANDLED; + } else { + /* Preserve AF if it comes after the above regmap_read() */ + val |= PCF85063_CTRL2_AF; } - return IRQ_NONE; + if (val & PCF85063_CTRL2_TF) { + events |= RTC_UF; + val &= ~PCF85063_CTRL2_TF; + ret = IRQ_HANDLED; + } else { + /* + * While not documented, TF demonstrates the same + * write-1-to-preserve semantics as AF in real HW + */ + val |= PCF85063_CTRL2_TF; + } + + if (ret == IRQ_HANDLED) { + regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, + PCF85063_CTRL2_AF | PCF85063_CTRL2_TF, val); + rtc_update_irq(pcf85063->rtc, 1, events); + } + + return ret; } static int pcf85063_read_offset(struct device *dev, long *offset) @@ -431,8 +525,8 @@ static int pcf85063_clkout_set_rate(struct clk_hw *hw, unsigned long rate, if (clkout_rates[i] == rate) return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF, - i | PCF85063_CTRL2_AF); + PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF, + i | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF); return -EINVAL; } @@ -461,8 +555,8 @@ static int pcf85063_clkout_control(struct clk_hw *hw, bool enable) } return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2, - PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF, - buf | PCF85063_CTRL2_AF); + PCF85063_REG_CLKO_F_MASK | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF, + buf | PCF85063_CTRL2_AF | PCF85063_CTRL2_TF); } static int pcf85063_clkout_prepare(struct clk_hw *hw) @@ -609,6 +703,7 @@ static int pcf85063_probe(struct device *dev, struct regmap *regmap, int irq, dev_warn(&pcf85063->rtc->dev, "unable to request IRQ, alarms disabled\n"); } else { + set_bit(RTC_FEATURE_UPDATE_INTERRUPT, pcf85063->rtc->features); set_bit(RTC_FEATURE_ALARM, pcf85063->rtc->features); device_init_wakeup(dev, true); err = dev_pm_set_wake_irq(dev, irq); From 55bb5877494d6c99898b959aa593663ce4676f4e Mon Sep 17 00:00:00 2001 From: Akshat Rawat Date: Thu, 22 Oct 2026 08:12:42 +0000 Subject: [PATCH 75/89] rtc: rv3028: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (timestamp0_show and timestamp0_count_show) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Signed-off-by: Akshat Rawat Link: https://patch.msgid.link/20261022081242.421-1-akshatrawat095@gmail.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rv3028.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c index d25e33d0835e..b3a5b1ef33d1 100644 --- a/drivers/rtc/rtc-rv3028.c +++ b/drivers/rtc/rtc-rv3028.c @@ -147,8 +147,8 @@ static ssize_t timestamp0_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%llu\n", - (unsigned long long)rtc_tm_to_time64(&tm)); + return sysfs_emit(buf, "%llu\n", + (unsigned long long)rtc_tm_to_time64(&tm)); }; static DEVICE_ATTR_RW(timestamp0); @@ -164,7 +164,7 @@ static ssize_t timestamp0_count_show(struct device *dev, if (ret) return ret; - return sprintf(buf, "%u\n", count); + return sysfs_emit(buf, "%u\n", count); }; static DEVICE_ATTR_RO(timestamp0_count); From c2373bcd50d9654b76eac585c7c98105665b3321 Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Tue, 13 Oct 2026 14:02:06 +0200 Subject: [PATCH 76/89] rtc: ds1685: arm the wake-up alarm at power-off only if an alarm is set ds1685_rtc_poweroff() always sets WIE. The wake-up alarm then compares the time-of-day and date alarm registers, which hold whatever was last written to them, and a match powers the machine straight back on. Set WIE only when AIE is set in Control B, and clear it otherwise. Fixes: aaaf5fbf56f1 ("rtc: add driver for DS1685 family of real time clocks") Signed-off-by: Imre Kaloz Acked-by: Joshua Kinard Link: https://patch.msgid.link/20261013120208.11268-2-kaloz@kernel.org Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1685.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c index 284815e5f478..e251cb06842d 100644 --- a/drivers/rtc/rtc-ds1685.c +++ b/drivers/rtc/rtc-ds1685.c @@ -1366,7 +1366,7 @@ module_platform_driver(ds1685_rtc_driver); void __noreturn ds1685_rtc_poweroff(struct platform_device *pdev) { - u8 ctrla, ctrl4a, ctrl4b; + u8 ctrla, ctrlb, ctrl4a, ctrl4b; struct ds1685_priv *rtc; /* Check for valid RTC data, else, spin forever. */ @@ -1393,6 +1393,8 @@ ds1685_rtc_poweroff(struct platform_device *pdev) ctrla &= ~(RTC_CTRL_A_DV2); rtc->write(rtc, RTC_CTRL_A, ctrla); + ctrlb = rtc->read(rtc, RTC_CTRL_B); + /* * Read Control 4A and check the status of the auxillary * battery. This must be present and working (VRT2 = 1) @@ -1407,13 +1409,17 @@ ds1685_rtc_poweroff(struct platform_device *pdev) /* * The auxillary battery is present and working. - * Enable extended functions (ABE=1), enable - * wake-up (WIE=1), and enable kickstart (KSE=1) - * in Control 4B. + * Enable extended functions (ABE=1) and kickstart + * (KSE=1) in Control 4B. Enable wake-up (WIE=1) only + * if an alarm is set, else stale alarm registers + * can power the system back on. */ ctrl4b = rtc->read(rtc, RTC_EXT_CTRL_4B); - ctrl4b |= (RTC_CTRL_4B_ABE | RTC_CTRL_4B_WIE | - RTC_CTRL_4B_KSE); + ctrl4b |= (RTC_CTRL_4B_ABE | RTC_CTRL_4B_KSE); + if (ctrlb & RTC_CTRL_B_AIE) + ctrl4b |= RTC_CTRL_4B_WIE; + else + ctrl4b &= ~(RTC_CTRL_4B_WIE); rtc->write(rtc, RTC_EXT_CTRL_4B, ctrl4b); } From 21a829af60a7f5c7456f1f0ff9b6b0eff0ceb738 Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Tue, 13 Oct 2026 14:02:07 +0200 Subject: [PATCH 77/89] rtc: ds1685: do not sleep in the power-off handler Power-off handlers run from an atomic notifier chain and must not sleep. Use mdelay() for the delay before setting PAB. Fixes: aaaf5fbf56f1 ("rtc: add driver for DS1685 family of real time clocks") Reported-by: Sashiko Closes: https://lore.kernel.org/all/sashiko-outbox-169477@kernel.org/ Signed-off-by: Imre Kaloz Acked-by: Joshua Kinard Link: https://patch.msgid.link/20261013120208.11268-3-kaloz@kernel.org Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1685.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c index e251cb06842d..1c84c5debca1 100644 --- a/drivers/rtc/rtc-ds1685.c +++ b/drivers/rtc/rtc-ds1685.c @@ -1425,7 +1425,7 @@ ds1685_rtc_poweroff(struct platform_device *pdev) /* Set PAB to 1 in Control 4A to power the system down. */ dev_warn(&pdev->dev, "Powerdown.\n"); - msleep(20); + mdelay(20); rtc->write(rtc, RTC_EXT_CTRL_4A, (ctrl4a | RTC_CTRL_4A_PAB)); From 3b6cdc36553b1fe627f782a7f5efe5ffc806faa1 Mon Sep 17 00:00:00 2001 From: Imre Kaloz Date: Tue, 13 Oct 2026 14:02:08 +0200 Subject: [PATCH 78/89] rtc: ds1685: keep setting PAB until power is removed A kickstart or wake-up flag set while its interrupt is enabled (KF with KSE, WF with WIE) clears PAB, and the system stays powered. Clear the event flags together with setting PAB, and repeat the write until power is gone. Signed-off-by: Imre Kaloz Acked-by: Joshua Kinard Link: https://patch.msgid.link/20261013120208.11268-4-kaloz@kernel.org Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1685.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c index 1c84c5debca1..220a8b15d15f 100644 --- a/drivers/rtc/rtc-ds1685.c +++ b/drivers/rtc/rtc-ds1685.c @@ -1423,15 +1423,21 @@ ds1685_rtc_poweroff(struct platform_device *pdev) rtc->write(rtc, RTC_EXT_CTRL_4B, ctrl4b); } - /* Set PAB to 1 in Control 4A to power the system down. */ + /* + * Set PAB to 1 in Control 4A to power the system down. PAB + * is cleared again while KF and KSE, or WF and WIE, are set, + * so keep clearing the flags and setting PAB until power is + * gone. We do not switch back to bank0. + */ dev_warn(&pdev->dev, "Powerdown.\n"); mdelay(20); - rtc->write(rtc, RTC_EXT_CTRL_4A, - (ctrl4a | RTC_CTRL_4A_PAB)); - - /* Spin ... we do not switch back to bank0. */ - while(1); - unreachable(); + while (1) { + ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); + rtc->write(rtc, RTC_EXT_CTRL_4A, + (ctrl4a & ~RTC_CTRL_4A_RWK_MASK) | + RTC_CTRL_4A_PAB); + mdelay(100); + } } } EXPORT_SYMBOL_GPL(ds1685_rtc_poweroff); From bc9a04e1e1d8931bc5bf9d96937477b8c6259f6b Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:24 +0100 Subject: [PATCH 79/89] rtc: rv3029: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (rv3029_hwmon_show_temp and rv3029_hwmon_show_update_interval) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-1-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rv3029c2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rv3029c2.c b/drivers/rtc/rtc-rv3029c2.c index c348b31928e9..33b73bbcaf52 100644 --- a/drivers/rtc/rtc-rv3029c2.c +++ b/drivers/rtc/rtc-rv3029c2.c @@ -601,7 +601,7 @@ static ssize_t rv3029_hwmon_show_temp(struct device *dev, if (ret < 0) return ret; - return sprintf(buf, "%d\n", temp_mC); + return sysfs_emit(buf, "%d\n", temp_mC); } static ssize_t rv3029_hwmon_set_update_interval(struct device *dev, @@ -654,7 +654,7 @@ static ssize_t rv3029_hwmon_show_update_interval(struct device *dev, interval_ms = 0; } - return sprintf(buf, "%d\n", interval_ms); + return sysfs_emit(buf, "%d\n", interval_ms); } static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, rv3029_hwmon_show_temp, From 8d2735de39daebb862542b8f340481bf2f506e9e Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:25 +0100 Subject: [PATCH 80/89] rtc: pcf2127: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in the sysfs show callback (timestamp_show) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Reviewed-by: Bruno Thomsen Link: https://patch.msgid.link/20261029132339.925021-2-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-pcf2127.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-pcf2127.c b/drivers/rtc/rtc-pcf2127.c index ff2023908f5a..49542cc53411 100644 --- a/drivers/rtc/rtc-pcf2127.c +++ b/drivers/rtc/rtc-pcf2127.c @@ -972,7 +972,7 @@ static ssize_t timestamp_show(struct device *dev, if (ret) return ret; } - return sprintf(buf, "%llu\n", (unsigned long long)ts); + return sysfs_emit(buf, "%llu\n", (unsigned long long)ts); } static ssize_t timestamp0_show(struct device *dev, From 946584fe01b840625ce26a5a9b91875cad009fae Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:26 +0100 Subject: [PATCH 81/89] rtc: ep93xx: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (comp_preload_show and comp_delete_show) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-3-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ep93xx.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-ep93xx.c b/drivers/rtc/rtc-ep93xx.c index 507c9f22036c..f105aba28459 100644 --- a/drivers/rtc/rtc-ep93xx.c +++ b/drivers/rtc/rtc-ep93xx.c @@ -93,7 +93,7 @@ static ssize_t comp_preload_show(struct device *dev, ep93xx_rtc_get_swcomp(dev->parent, &preload, NULL); - return sprintf(buf, "%d\n", preload); + return sysfs_emit(buf, "%d\n", preload); } static DEVICE_ATTR_RO(comp_preload); @@ -104,7 +104,7 @@ static ssize_t comp_delete_show(struct device *dev, ep93xx_rtc_get_swcomp(dev->parent, NULL, &delete); - return sprintf(buf, "%d\n", delete); + return sysfs_emit(buf, "%d\n", delete); } static DEVICE_ATTR_RO(comp_delete); From 187c4ed20a5636105ae88b28351fc4b730506bc0 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:27 +0100 Subject: [PATCH 82/89] rtc: bq32k: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in the sysfs show callback (bq32k_sysfs_show_tricklecharge_bypass) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-4-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-bq32k.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-bq32k.c b/drivers/rtc/rtc-bq32k.c index 573231613e52..0f50cfce88cb 100644 --- a/drivers/rtc/rtc-bq32k.c +++ b/drivers/rtc/rtc-bq32k.c @@ -210,7 +210,7 @@ static ssize_t bq32k_sysfs_show_tricklecharge_bypass(struct device *dev, if (error) return error; - return sprintf(buf, "%d\n", (reg & BQ32K_TCFE) ? 1 : 0); + return sysfs_emit(buf, "%d\n", (reg & BQ32K_TCFE) ? 1 : 0); } static ssize_t bq32k_sysfs_store_tricklecharge_bypass(struct device *dev, From 0f3bc1c937276458786dc85aaa47142555fe6725 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:28 +0100 Subject: [PATCH 83/89] rtc: x1205: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (x1205_sysfs_show_atrim and x1205_sysfs_show_dtrim) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-5-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-x1205.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-x1205.c b/drivers/rtc/rtc-x1205.c index bb1a5c86f621..6ca6ff45945e 100644 --- a/drivers/rtc/rtc-x1205.c +++ b/drivers/rtc/rtc-x1205.c @@ -575,7 +575,7 @@ static ssize_t x1205_sysfs_show_atrim(struct device *dev, if (err) return err; - return sprintf(buf, "%d.%02d pF\n", atrim / 1000, atrim % 1000); + return sysfs_emit(buf, "%d.%02d pF\n", atrim / 1000, atrim % 1000); } static DEVICE_ATTR(atrim, S_IRUGO, x1205_sysfs_show_atrim, NULL); @@ -588,7 +588,7 @@ static ssize_t x1205_sysfs_show_dtrim(struct device *dev, if (err) return err; - return sprintf(buf, "%d ppm\n", dtrim); + return sysfs_emit(buf, "%d ppm\n", dtrim); } static DEVICE_ATTR(dtrim, S_IRUGO, x1205_sysfs_show_dtrim, NULL); From c0b38531ddc5e5a361d8e5e8315588e856bb2924 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:30 +0100 Subject: [PATCH 84/89] rtc: rx8025: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in the sysfs show callback (rx8025_sysfs_show_clock_adjust) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-7-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rx8025.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-rx8025.c b/drivers/rtc/rtc-rx8025.c index 2f2deb59cbee..96a04c977d0f 100644 --- a/drivers/rtc/rtc-rx8025.c +++ b/drivers/rtc/rtc-rx8025.c @@ -486,7 +486,7 @@ static ssize_t rx8025_sysfs_show_clock_adjust(struct device *dev, if (err) return err; - return sprintf(buf, "%ld\n", -adj); + return sysfs_emit(buf, "%ld\n", -adj); } static ssize_t rx8025_sysfs_store_clock_adjust(struct device *dev, From 67b3eadfb80dee4dfd56ca624f33e2ac7ed5987a Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:31 +0100 Subject: [PATCH 85/89] rtc: ds1343: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (ds1343_show_glitchfilter and ds1343_show_tricklecharger) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-8-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1343.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/rtc/rtc-ds1343.c b/drivers/rtc/rtc-ds1343.c index 22abec4ba656..0ec5eaba6bd4 100644 --- a/drivers/rtc/rtc-ds1343.c +++ b/drivers/rtc/rtc-ds1343.c @@ -94,9 +94,9 @@ static ssize_t ds1343_show_glitchfilter(struct device *dev, glitch_filt_status = !!(data & DS1343_EGFIL); if (glitch_filt_status) - return sprintf(buf, "enabled\n"); + return sysfs_emit(buf, "enabled\n"); else - return sprintf(buf, "disabled\n"); + return sysfs_emit(buf, "disabled\n"); } static ssize_t ds1343_store_glitchfilter(struct device *dev, @@ -180,7 +180,7 @@ static ssize_t ds1343_show_tricklecharger(struct device *dev, } } - return sprintf(buf, "%s %s\n", diodes, resistors); + return sysfs_emit(buf, "%s %s\n", diodes, resistors); } static DEVICE_ATTR(trickle_charger, 0444, ds1343_show_tricklecharger, NULL); From edd541b0fff4e38f019f92939d97e5a604f80a23 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:32 +0100 Subject: [PATCH 86/89] rtc: ds1685: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (ds1685_rtc_sysfs_battery_show, ds1685_rtc_sysfs_auxbatt_show and ds1685_rtc_sysfs_serial_show) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Acked-by: Joshua Kinard Link: https://patch.msgid.link/20261029132339.925021-9-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1685.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c index 220a8b15d15f..3d0f491d0e88 100644 --- a/drivers/rtc/rtc-ds1685.c +++ b/drivers/rtc/rtc-ds1685.c @@ -988,8 +988,8 @@ ds1685_rtc_sysfs_battery_show(struct device *dev, ctrld = rtc->read(rtc, RTC_CTRL_D); - return sprintf(buf, "%s\n", - (ctrld & RTC_CTRL_D_VRT) ? "ok" : "not ok or N/A"); + return sysfs_emit(buf, "%s\n", + (ctrld & RTC_CTRL_D_VRT) ? "ok" : "not ok or N/A"); } static DEVICE_ATTR(battery, S_IRUGO, ds1685_rtc_sysfs_battery_show, NULL); @@ -1010,8 +1010,8 @@ ds1685_rtc_sysfs_auxbatt_show(struct device *dev, ctrl4a = rtc->read(rtc, RTC_EXT_CTRL_4A); ds1685_rtc_switch_to_bank0(rtc); - return sprintf(buf, "%s\n", - (ctrl4a & RTC_CTRL_4A_VRT2) ? "ok" : "not ok or N/A"); + return sysfs_emit(buf, "%s\n", + (ctrl4a & RTC_CTRL_4A_VRT2) ? "ok" : "not ok or N/A"); } static DEVICE_ATTR(auxbatt, S_IRUGO, ds1685_rtc_sysfs_auxbatt_show, NULL); @@ -1032,7 +1032,7 @@ ds1685_rtc_sysfs_serial_show(struct device *dev, ds1685_rtc_get_ssn(rtc, ssn); ds1685_rtc_switch_to_bank0(rtc); - return sprintf(buf, "%8phC\n", ssn); + return sysfs_emit(buf, "%8phC\n", ssn); } static DEVICE_ATTR(serial, S_IRUGO, ds1685_rtc_sysfs_serial_show, NULL); From 26e2f34b862c6f8cbaab6023ccc58021bb3ac4ee Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:33 +0100 Subject: [PATCH 87/89] rtc: isl1208: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (timestamp0_show, isl1208_sysfs_show_atrim, isl1208_sysfs_show_dtrim and isl1208_sysfs_show_usr) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-10-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-isl1208.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-isl1208.c b/drivers/rtc/rtc-isl1208.c index 9bdd5d121571..ee2eb8c475ea 100644 --- a/drivers/rtc/rtc-isl1208.c +++ b/drivers/rtc/rtc-isl1208.c @@ -615,8 +615,8 @@ static ssize_t timestamp0_show(struct device *dev, if (sr) return sr; - return sprintf(buf, "%llu\n", - (unsigned long long)rtc_tm_to_time64(&tm)); + return sysfs_emit(buf, "%llu\n", + (unsigned long long)rtc_tm_to_time64(&tm)); }; static DEVICE_ATTR_RW(timestamp0); @@ -709,7 +709,7 @@ isl1208_sysfs_show_atrim(struct device *dev, if (atr < 0) return atr; - return sprintf(buf, "%d.%.2d pF\n", atr >> 2, (atr & 0x3) * 25); + return sysfs_emit(buf, "%d.%.2d pF\n", atr >> 2, (atr & 0x3) * 25); } static DEVICE_ATTR(atrim, S_IRUGO, isl1208_sysfs_show_atrim, NULL); @@ -722,7 +722,7 @@ isl1208_sysfs_show_dtrim(struct device *dev, if (dtr < 0) return dtr; - return sprintf(buf, "%d ppm\n", dtr - 100); + return sysfs_emit(buf, "%d ppm\n", dtr - 100); } static DEVICE_ATTR(dtrim, S_IRUGO, isl1208_sysfs_show_dtrim, NULL); @@ -735,7 +735,7 @@ isl1208_sysfs_show_usr(struct device *dev, if (usr < 0) return usr; - return sprintf(buf, "0x%.4x\n", usr); + return sysfs_emit(buf, "0x%.4x\n", usr); } static ssize_t From 42602b834325ef7b78c27f45d4cfd91bfe1b7d5f Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:34 +0100 Subject: [PATCH 88/89] rtc: ds1307: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in the sysfs show callback (ds3231_hwmon_show_temp) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-11-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-ds1307.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c index 8627f84232ce..206a79d28568 100644 --- a/drivers/rtc/rtc-ds1307.c +++ b/drivers/rtc/rtc-ds1307.c @@ -1380,7 +1380,7 @@ static ssize_t ds3231_hwmon_show_temp(struct device *dev, if (ret) return ret; - return sprintf(buf, "%d\n", temp); + return sysfs_emit(buf, "%d\n", temp); } static SENSOR_DEVICE_ATTR(temp1_input, 0444, ds3231_hwmon_show_temp, NULL, 0); From 7485659ecb81d5fce3a3edf2cae9fcb29c17b1cd Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Thu, 29 Oct 2026 14:23:35 +0100 Subject: [PATCH 89/89] rtc: rs5c372: use sysfs_emit() instead of sprintf() Use sysfs_emit() instead of sprintf() in sysfs show callbacks (rs5c372_sysfs_show_trim and rs5c372_sysfs_show_osc) to conform to the recommended sysfs attribute formatting guidelines in Documentation/filesystems/sysfs.rst. Link: https://patch.msgid.link/20261029132339.925021-12-alexandre.belloni@bootlin.com Signed-off-by: Alexandre Belloni --- drivers/rtc/rtc-rs5c372.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-rs5c372.c b/drivers/rtc/rtc-rs5c372.c index 9ab6f2c28f7b..a4bd042908b8 100644 --- a/drivers/rtc/rtc-rs5c372.c +++ b/drivers/rtc/rtc-rs5c372.c @@ -699,7 +699,7 @@ static ssize_t rs5c372_sysfs_show_trim(struct device *dev, if (err) return err; - return sprintf(buf, "%d\n", trim); + return sysfs_emit(buf, "%d\n", trim); } static DEVICE_ATTR(trim, S_IRUGO, rs5c372_sysfs_show_trim, NULL); @@ -712,7 +712,7 @@ static ssize_t rs5c372_sysfs_show_osc(struct device *dev, if (err) return err; - return sprintf(buf, "%d.%03d KHz\n", osc / 1000, osc % 1000); + return sysfs_emit(buf, "%d.%03d KHz\n", osc / 1000, osc % 1000); } static DEVICE_ATTR(osc, S_IRUGO, rs5c372_sysfs_show_osc, NULL);