* [PATCH V5 0/3] rtc: atcrtc100: Add Andes ATCRTC100 RTC driver
@ 2025-01-10 9:26 CL Wang
2025-01-10 9:27 ` [PATCH V5 1/3] rtc: atcrtc100: Add " CL Wang
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: CL Wang @ 2025-01-10 9:26 UTC (permalink / raw)
To: cl634, alexandre.belloni, robh, krzk+dt, conor+dt
Cc: devicetree, linux-kernel, linux-rtc, tim609, ycliang
This patch series adds support for the Andes ATCRTC100 Real-Time Clock.
The series is now based on the rtc-next branch from:
git://git.kernel.org/pub/scm/linux/kernel/git/abelloni/linux.git.
This V5 patch series is built upon the V4 series and has been rebased
exclusively onto the latest commit in the rtc-next branch, which
corresponds to Linux 6.13-rc1."
For details of the change log, please refer to the commit log of each patch.
Please kindly review.
CL Wang (3):
rtc: atcrtc100: Add ATCRTC100 RTC driver
dt-bindings: rtc: Add support for ATCRTC100 RTC
MAINTAINERS: Add entry for ATCRTC100 RTC driver
.../bindings/rtc/andestech,atcrtc100.yaml | 43 ++
MAINTAINERS | 6 +
drivers/rtc/Kconfig | 15 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-atcrtc100.c | 524 ++++++++++++++++++
5 files changed, 589 insertions(+)
create mode 100644 Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml
create mode 100644 drivers/rtc/rtc-atcrtc100.c
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH V5 1/3] rtc: atcrtc100: Add ATCRTC100 RTC driver 2025-01-10 9:26 [PATCH V5 0/3] rtc: atcrtc100: Add Andes ATCRTC100 RTC driver CL Wang @ 2025-01-10 9:27 ` CL Wang 2025-03-31 22:15 ` Alexandre Belloni 2025-01-10 9:27 ` [PATCH V5 2/3] dt-bindings: rtc: Add support for ATCRTC100 RTC CL Wang 2025-01-10 9:27 ` [PATCH V5 3/3] MAINTAINERS: Add entry for ATCRTC100 RTC driver CL Wang 2 siblings, 1 reply; 6+ messages in thread From: CL Wang @ 2025-01-10 9:27 UTC (permalink / raw) To: cl634, alexandre.belloni, robh, krzk+dt, conor+dt Cc: devicetree, linux-kernel, linux-rtc, tim609, ycliang RTC driver for Andes ATCRTC100 Real-Time Clock. Signed-off-by: CL Wang <cl634@andestech.com> --- Changes for v1: - Initial version of the ATCRTC100 driver. Changes for v2: - Replaced legacy APIs with devm APIs for system resource allocation. - Used regmap APIs to access I/O registers. Changes for v3: - Removed 'of_match_ptr()'. - Added check for WriteDone status before accessing Counter, Alarm, and Control registers. Changes for v4: - Refined the procedure for setting the wake-up source. - Fixed coding style to comply with Linux coding style guidelines. Changes for v5: - Rebased onto the latest commit in rtc-next. No changes to the code. --- drivers/rtc/Kconfig | 15 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-atcrtc100.c | 524 ++++++++++++++++++++++++++++++++++++ 3 files changed, 540 insertions(+) create mode 100644 drivers/rtc/rtc-atcrtc100.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index a60bcc791a48..eac651f65578 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -1040,6 +1040,21 @@ config RTC_DRV_ALPHA Direct support for the real-time clock found on every Alpha system, specifically MC146818 compatibles. If in doubt, say Y. +config RTC_DRV_ATCRTC100 + tristate "Andes ATCRTC100" + depends on RISCV + select REGMAP_MMIO + help + If you say yes here you will get support for the Andes ATCRTC100 + RTC driver. + + This driver provides support for the Andes ATCRTC100 real-time clock + device. It allows setting and retrieving the time and date, as well + as setting alarms. + + To compile this driver as a module, choose M here: the module will + be called rtc-atcrtc100. + config RTC_DRV_DS1216 tristate "Dallas DS1216" depends on SNI_RM diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 489b4ab07068..1a738d011e20 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_RTC_DRV_ASM9260) += rtc-asm9260.o obj-$(CONFIG_RTC_DRV_ASPEED) += rtc-aspeed.o obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o +obj-$(CONFIG_RTC_DRV_ATCRTC100) += rtc-atcrtc100.o obj-$(CONFIG_RTC_DRV_AU1XXX) += rtc-au1xxx.o obj-$(CONFIG_RTC_DRV_BBNSM) += rtc-nxp-bbnsm.o obj-$(CONFIG_RTC_DRV_BD70528) += rtc-bd70528.o diff --git a/drivers/rtc/rtc-atcrtc100.c b/drivers/rtc/rtc-atcrtc100.c new file mode 100644 index 000000000000..40629f796f3b --- /dev/null +++ b/drivers/rtc/rtc-atcrtc100.c @@ -0,0 +1,524 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Andes ATCRTC100 real time clock. + * + * Copyright (C) 2024 Andes Technology Corporation + */ + +#include <linux/module.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/pm_wakeirq.h> +#include <linux/delay.h> +#include <linux/workqueue.h> + +#define RTC_ID 0x00 /* ID and Revision */ +#define ID_OFF 12 +#define ID_MSK (0xFFFFF << ID_OFF) +#define ATCRTC100ID (0x03011 << ID_OFF) +#define RTC_RSV 0x4 /* Reserve */ +#define RTC_CNT 0x10 /* Counter */ +#define RTC_ALM 0x14 /* Alarm */ +#define DAY_OFF 17 +#define DAY_MSK 0x7FFF +#define HOUR_OFF 12 +#define HOUR_MSK 0x1F +#define MIN_OFF 6 +#define MIN_MSK 0x3F +#define SEC_OFF 0 +#define SEC_MSK 0x3F +#define RTC_SECOND(x) ((x >> SEC_OFF) & SEC_MSK) /* RTC sec */ +#define RTC_MINUTE(x) ((x >> MIN_OFF) & MIN_MSK) /* RTC min */ +#define RTC_HOUR(x) ((x >> HOUR_OFF) & HOUR_MSK) /* RTC hour */ +#define RTC_DAYS(x) ((x >> DAY_OFF) & DAY_MSK) /* RTC day */ + +#define RTC_CR 0x18 /* Control */ +#define RTC_EN (0x1UL << 0) +#define ALARM_WAKEUP (0x1UL << 1) +#define ALARM_INT (0x1UL << 2) +#define DAY_INT (0x1UL << 3) +#define HOUR_INT (0x1UL << 4) +#define MIN_INT (0x1UL << 5) +#define SEC_INT (0x1UL << 6) +#define HSEC_INT (0x1UL << 7) +#define RTC_STA 0x1C /* Status */ +#define WRITE_DONE (0x1UL << 16) +#define RTC_TRIM 0x20 /* Digital Trimming */ + +#define ATCRTC_TIME_TO_SEC(D, H, M, S) (D * 86400LL + H * 3600 + M * 60 + S) + +#define ATCRTC_TIMEOUT_US 1000000 +#define ATCRTC_TIMEOUT_USLEEP_MIN 20 +#define ATCRTC_TIMEOUT_USLEEP_MAX 30 + +struct atcrtc_dev { + struct rtc_device *rtc_dev; + struct regmap *regmap; + struct delayed_work rtc_work; + struct mutex lock; + unsigned int alarm_irq; + unsigned int time_irq; + unsigned char alarm_en; +}; + +static const struct regmap_config atcrtc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = RTC_TRIM, + .cache_type = REGCACHE_NONE, +}; + +/** + * atcrtc_check_write_done - Check whether the ATCRTC100 is ready or not. + * @rtc: Pointer of atcrtc_dev. + * + * The WriteDone bit in the status register indicates the synchronization + * progress of RTC register updates. This bit is cleared to zero whenever + * any RTC control register such as the Counter, Alarm, Control, or Digital + * Trimming registers is updated. It returns to one only after all previous + * updates to these registers have been fully synchronized to the RTC clock + * domain. If a register update is in the process of being synchronized, a + * second update to the same register may be ignored. + */ +static int atcrtc_check_write_done(struct atcrtc_dev *rtc) +{ + int loop; + int timeout; + + might_sleep(); + timeout = ATCRTC_TIMEOUT_US / ATCRTC_TIMEOUT_USLEEP_MIN; + + for (loop = 0; loop < timeout; loop++) { + if (regmap_test_bits(rtc->regmap, RTC_STA, WRITE_DONE)) + return 0; + + usleep_range(ATCRTC_TIMEOUT_USLEEP_MIN, + ATCRTC_TIMEOUT_USLEEP_MAX); + } + dev_err(&rtc->rtc_dev->dev, "Device is busy too long\n"); + return -EBUSY; +} + +static irqreturn_t atcrtc_periodic_isr(int irq, void *dev) +{ + struct atcrtc_dev *rtc = dev; + + if (regmap_test_bits(rtc->regmap, RTC_STA, SEC_INT)) { + regmap_write_bits(rtc->regmap, RTC_STA, SEC_INT, SEC_INT); + rtc_update_irq(rtc->rtc_dev, 1, RTC_UF | RTC_IRQF); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static irqreturn_t atcrtc_alarm_isr(int irq, void *dev) +{ + struct atcrtc_dev *rtc = dev; + + if (regmap_test_bits(rtc->regmap, RTC_STA, ALARM_INT)) { + regmap_write_bits(rtc->regmap, RTC_STA, ALARM_INT, ALARM_INT); + rtc->alarm_en = 0; + schedule_delayed_work(&rtc->rtc_work, 0); + rtc_update_irq(rtc->rtc_dev, 1, RTC_AF | RTC_IRQF); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int atcrtc_alarm_irq_enable(struct device *dev, unsigned int enable) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + int ret; + + mutex_lock(&rtc->lock); + + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return ret; + } + + if (enable) + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, ALARM_INT); + else + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, 0); + + mutex_unlock(&rtc->lock); + + return 0; +} + +static int atcrtc_alarm_enable(struct device *dev, unsigned int enable) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + int ret; + + mutex_lock(&rtc->lock); + + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return ret; + } + + if (enable) { + regmap_update_bits(rtc->regmap, + RTC_CR, + ALARM_WAKEUP, + ALARM_WAKEUP); + } else { + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_WAKEUP, 0); + } + + mutex_unlock(&rtc->lock); + + return 0; +} + +static void atcrtc_alarm_clear(struct work_struct *work) +{ + struct atcrtc_dev *rtc = + container_of(work, struct atcrtc_dev, rtc_work.work); + int ret; + + mutex_lock(&rtc->lock); + if (rtc->alarm_en == 0) { + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return; + } + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, 0); + } + mutex_unlock(&rtc->lock); +} + +static time64_t atcrtc_read_rtc_time(struct atcrtc_dev *rtc) +{ + time64_t time; + unsigned int rtc_cnt; + + regmap_read(rtc->regmap, RTC_CNT, &rtc_cnt); + time = ATCRTC_TIME_TO_SEC(RTC_DAYS(rtc_cnt), + RTC_HOUR(rtc_cnt), + RTC_MINUTE(rtc_cnt), + RTC_SECOND(rtc_cnt)); + return time; +} + +static int atcrtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + time64_t time; + + mutex_lock(&rtc->lock); + time = atcrtc_read_rtc_time(rtc); + mutex_unlock(&rtc->lock); + + rtc_time64_to_tm(time, tm); + if (rtc_valid_tm(tm) < 0) { + dev_err(dev, "Invalid date: %lld\n", time); + rtc_time64_to_tm(0, tm); + } + return 0; +} + +static void atcrtc_set_rtc_time(struct atcrtc_dev *rtc, time64_t time) +{ + int rem; + unsigned int counter; + unsigned int day; + unsigned int hour; + unsigned int min; + unsigned int sec; + + day = div_s64_rem(time, 86400, &rem); + hour = rem / 3600; + rem -= hour * 3600; + min = rem / 60; + sec = rem - min * 60; + counter = ((day & DAY_MSK) << DAY_OFF) + | ((hour & HOUR_MSK) << HOUR_OFF) + | ((min & MIN_MSK) << MIN_OFF) + | ((sec & SEC_MSK) << SEC_OFF); + + regmap_write(rtc->regmap, RTC_CNT, counter); +} + +static int atcrtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + time64_t sys_time; + int ret; + + sys_time = rtc_tm_to_time64(tm); + + mutex_lock(&rtc->lock); + + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return ret; + } + atcrtc_set_rtc_time(rtc, sys_time); + + mutex_unlock(&rtc->lock); + + return 0; +} + +static int atcrtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + struct rtc_time *tm = &wkalrm->time; + unsigned int rtc_alarm; + + mutex_lock(&rtc->lock); + + regmap_read(rtc->regmap, RTC_ALM, &rtc_alarm); + wkalrm->enabled = regmap_test_bits(rtc->regmap, RTC_CR, ALARM_INT); + + mutex_unlock(&rtc->lock); + + tm->tm_hour = (rtc_alarm >> HOUR_OFF) & HOUR_MSK; + tm->tm_min = (rtc_alarm >> MIN_OFF) & MIN_MSK; + tm->tm_sec = (rtc_alarm >> SEC_OFF) & SEC_MSK; + + return 0; +} + +static int atcrtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + struct rtc_time *tm = &wkalrm->time; + unsigned int alm = 0; + int ret = rtc_valid_tm(tm); + + if (ret < 0) { + dev_err(dev, "Invalid alarm value: %d\n", ret); + return ret; + } + + mutex_lock(&rtc->lock); + + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return ret; + } + + /* Disable alarm interrupt and clear the alarm flag */ + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, 0); + rtc->alarm_en = 0; + + /* Set alarm time */ + alm |= ((tm->tm_sec & SEC_MSK) << SEC_OFF); + alm |= ((tm->tm_min & MIN_MSK) << MIN_OFF); + alm |= ((tm->tm_hour & HOUR_MSK) << HOUR_OFF); + regmap_write(rtc->regmap, RTC_ALM, alm); + + if (wkalrm->enabled) { + rtc->alarm_en = 1; + ret = atcrtc_check_write_done(rtc); + if (ret) { + mutex_unlock(&rtc->lock); + return ret; + } + + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, ALARM_INT); + } + + mutex_unlock(&rtc->lock); + return 0; +} + +static int atcrtc_hw_init(struct atcrtc_dev *rtc) +{ + unsigned int rtc_id; + int ret; + + regmap_read(rtc->regmap, RTC_ID, &rtc_id); + if ((rtc_id & ID_MSK) != ATCRTC100ID) + return -ENOENT; + + ret = atcrtc_check_write_done(rtc); + if (ret) + return ret; + regmap_update_bits(rtc->regmap, RTC_CR, RTC_EN, RTC_EN); + + return 0; +} + +static const struct rtc_class_ops rtc_ops = { + .read_time = atcrtc_read_time, + .set_time = atcrtc_set_time, + .read_alarm = atcrtc_read_alarm, + .set_alarm = atcrtc_set_alarm, + .alarm_irq_enable = atcrtc_alarm_irq_enable, +}; + +static int atcrtc_probe(struct platform_device *pdev) +{ + struct atcrtc_dev *atcrtc_dev; + void __iomem *reg_base; + int ret = 0; + + atcrtc_dev = devm_kzalloc(&pdev->dev, sizeof(*atcrtc_dev), GFP_KERNEL); + if (!atcrtc_dev) + return -ENOMEM; + platform_set_drvdata(pdev, atcrtc_dev); + mutex_init(&atcrtc_dev->lock); + + reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg_base)) { + dev_err(&pdev->dev, + "Failed to map I/O space: %ld\n", + PTR_ERR(reg_base)); + return PTR_ERR(atcrtc_dev->regmap); + } + + atcrtc_dev->regmap = devm_regmap_init_mmio(&pdev->dev, + reg_base, + &atcrtc_regmap_config); + if (IS_ERR(atcrtc_dev->regmap)) { + dev_err(&pdev->dev, + "Failed to initialize regmap: %ld\n", + PTR_ERR(atcrtc_dev->regmap)); + return PTR_ERR(atcrtc_dev->regmap); + } + + ret = atcrtc_hw_init(atcrtc_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize driver: %d\n", ret); + return ret; + } + + atcrtc_dev->alarm_irq = platform_get_irq(pdev, 1); + if (atcrtc_dev->alarm_irq < 0) { + dev_err(&pdev->dev, + "Failed to get IRQ for alarm: %d\n", + atcrtc_dev->alarm_irq); + return atcrtc_dev->alarm_irq; + } + atcrtc_dev->time_irq = platform_get_irq(pdev, 0); + if (atcrtc_dev->time_irq < 0) { + dev_err(&pdev->dev, + "Failed to get IRQ for periodic interrupt: %d\n", + atcrtc_dev->time_irq); + return atcrtc_dev->time_irq; + } + + ret = devm_request_irq(&pdev->dev, + atcrtc_dev->alarm_irq, + atcrtc_alarm_isr, + 0, + "atcrtc alarm", + atcrtc_dev); + if (ret) { + dev_err(&pdev->dev, + "Failed to request IRQ %d for alarm: %d\n", + atcrtc_dev->alarm_irq, + ret); + return ret; + } + + ret = devm_request_irq(&pdev->dev, + atcrtc_dev->time_irq, + atcrtc_periodic_isr, + 0, + "atcrtc time", + atcrtc_dev); + if (ret) { + dev_err(&pdev->dev, + "Failed to request IRQ %d for periodic interrupt: %d\n", + atcrtc_dev->time_irq, + ret); + return ret; + } + + atcrtc_dev->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(atcrtc_dev->rtc_dev)) { + dev_err(&pdev->dev, + "Failed to allocate RTC device: %ld\n", + PTR_ERR(atcrtc_dev->rtc_dev)); + return PTR_ERR(atcrtc_dev->rtc_dev); + } + + ret = atcrtc_alarm_enable(&pdev->dev, true); + if (ret) + return ret; + set_bit(RTC_FEATURE_ALARM, atcrtc_dev->rtc_dev->features); + + ret = device_init_wakeup(&pdev->dev, true); + if (ret) { + dev_err(&pdev->dev, + "Failed to initialize wake device: %d\n", + ret); + return ret; + } + + ret = dev_pm_set_wake_irq(&pdev->dev, atcrtc_dev->alarm_irq); + if (ret) { + dev_err(&pdev->dev, "Failed to set wake IRQ: %d\n", ret); + device_init_wakeup(&pdev->dev, false); + return ret; + } + + atcrtc_dev->rtc_dev->ops = &rtc_ops; + /* + * There are 15 bits in the Day field of the Counter register. + * It can count up to 32,767 days, about 89.8 years. + */ + atcrtc_dev->rtc_dev->range_max = mktime64(2089, 12, 31, 23, 59, 59); + atcrtc_dev->rtc_dev->range_min = RTC_TIMESTAMP_BEGIN_2000; + + INIT_DELAYED_WORK(&atcrtc_dev->rtc_work, atcrtc_alarm_clear); + return devm_rtc_register_device(atcrtc_dev->rtc_dev); +} + +static int atcrtc_resume(struct device *dev) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(rtc->alarm_irq); + + return 0; +} + +static int atcrtc_suspend(struct device *dev) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(rtc->alarm_irq); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(atcrtc_pm_ops, atcrtc_suspend, atcrtc_resume); + +static const struct of_device_id atcrtc_dt_match[] = { + { .compatible = "andestech,atcrtc100" }, + { } +}; +MODULE_DEVICE_TABLE(of, atcrtc_dt_match); + +static struct platform_driver atcrtc_platform_driver = { + .driver = { + .name = "atcrtc100", + .of_match_table = atcrtc_dt_match, + .pm = pm_sleep_ptr(&atcrtc_pm_ops), + }, + .probe = atcrtc_probe, +}; + +module_platform_driver(atcrtc_platform_driver); + +MODULE_AUTHOR("CL Wang <cl634@andestech.com>"); +MODULE_DESCRIPTION("Andes ATCRTC100 driver"); +MODULE_LICENSE("GPL"); -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH V5 1/3] rtc: atcrtc100: Add ATCRTC100 RTC driver 2025-01-10 9:27 ` [PATCH V5 1/3] rtc: atcrtc100: Add " CL Wang @ 2025-03-31 22:15 ` Alexandre Belloni 2025-04-11 8:39 ` CL Wang 0 siblings, 1 reply; 6+ messages in thread From: Alexandre Belloni @ 2025-03-31 22:15 UTC (permalink / raw) To: CL Wang Cc: robh, krzk+dt, conor+dt, devicetree, linux-kernel, linux-rtc, tim609, ycliang Hello, I'm sorry for the late review, I was pretty sure I reviewed v4. On 10/01/2025 17:27:00+0800, CL Wang wrote: > +#define RTC_SECOND(x) ((x >> SEC_OFF) & SEC_MSK) /* RTC sec */ > +#define RTC_MINUTE(x) ((x >> MIN_OFF) & MIN_MSK) /* RTC min */ > +#define RTC_HOUR(x) ((x >> HOUR_OFF) & HOUR_MSK) /* RTC hour */ > +#define RTC_DAYS(x) ((x >> DAY_OFF) & DAY_MSK) /* RTC day */ FIELD_PREP can probably replace those. > + > +#define RTC_CR 0x18 /* Control */ > +#define RTC_EN (0x1UL << 0) > +#define ALARM_WAKEUP (0x1UL << 1) > +#define ALARM_INT (0x1UL << 2) > +#define DAY_INT (0x1UL << 3) > +#define HOUR_INT (0x1UL << 4) > +#define MIN_INT (0x1UL << 5) > +#define SEC_INT (0x1UL << 6) > +#define HSEC_INT (0x1UL << 7) > +#define RTC_STA 0x1C /* Status */ > +#define WRITE_DONE (0x1UL << 16) > +#define RTC_TRIM 0x20 /* Digital Trimming */ > + > +#define ATCRTC_TIME_TO_SEC(D, H, M, S) (D * 86400LL + H * 3600 + M * 60 + S) > + > +#define ATCRTC_TIMEOUT_US 1000000 > +#define ATCRTC_TIMEOUT_USLEEP_MIN 20 > +#define ATCRTC_TIMEOUT_USLEEP_MAX 30 > + > +struct atcrtc_dev { > + struct rtc_device *rtc_dev; > + struct regmap *regmap; > + struct delayed_work rtc_work; > + struct mutex lock; This mutex is not necessary, simply use rtc_lock() in you interrupt handler, the rtc core is already locking before calling the rtc_ops. > + unsigned int alarm_irq; > + unsigned int time_irq; > + unsigned char alarm_en; > +}; > + > +/** > + * atcrtc_check_write_done - Check whether the ATCRTC100 is ready or not. > + * @rtc: Pointer of atcrtc_dev. > + * > + * The WriteDone bit in the status register indicates the synchronization > + * progress of RTC register updates. This bit is cleared to zero whenever > + * any RTC control register such as the Counter, Alarm, Control, or Digital > + * Trimming registers is updated. It returns to one only after all previous > + * updates to these registers have been fully synchronized to the RTC clock > + * domain. If a register update is in the process of being synchronized, a > + * second update to the same register may be ignored. > + */ > +static int atcrtc_check_write_done(struct atcrtc_dev *rtc) > +{ > + int loop; > + int timeout; > + > + might_sleep(); > + timeout = ATCRTC_TIMEOUT_US / ATCRTC_TIMEOUT_USLEEP_MIN; > + > + for (loop = 0; loop < timeout; loop++) { > + if (regmap_test_bits(rtc->regmap, RTC_STA, WRITE_DONE)) > + return 0; > + > + usleep_range(ATCRTC_TIMEOUT_USLEEP_MIN, > + ATCRTC_TIMEOUT_USLEEP_MAX); > + } > + dev_err(&rtc->rtc_dev->dev, "Device is busy too long\n"); Is this error message useful, what would be the user action after seeing this? > + return -EBUSY; > +} > + + > +static time64_t atcrtc_read_rtc_time(struct atcrtc_dev *rtc) Does this have to be in a separate function? > +{ > + time64_t time; > + unsigned int rtc_cnt; > + > + regmap_read(rtc->regmap, RTC_CNT, &rtc_cnt); > + time = ATCRTC_TIME_TO_SEC(RTC_DAYS(rtc_cnt), > + RTC_HOUR(rtc_cnt), > + RTC_MINUTE(rtc_cnt), > + RTC_SECOND(rtc_cnt)); > + return time; > +} > + > +static int atcrtc_read_time(struct device *dev, struct rtc_time *tm) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + time64_t time; > + > + mutex_lock(&rtc->lock); > + time = atcrtc_read_rtc_time(rtc); > + mutex_unlock(&rtc->lock); > + > + rtc_time64_to_tm(time, tm); > + if (rtc_valid_tm(tm) < 0) { This is not necessary, the core always checks whether the tm is valid. > + dev_err(dev, "Invalid date: %lld\n", time); > + rtc_time64_to_tm(0, tm); > + } > + return 0; > +} > + > +static void atcrtc_set_rtc_time(struct atcrtc_dev *rtc, time64_t time) > +{ > + int rem; > + unsigned int counter; > + unsigned int day; > + unsigned int hour; > + unsigned int min; > + unsigned int sec; > + > + day = div_s64_rem(time, 86400, &rem); > + hour = rem / 3600; > + rem -= hour * 3600; > + min = rem / 60; > + sec = rem - min * 60; You already had the broken down hour, min and sec, it is not necessary to compute that again here, just fold this function in atcrtc_set_time > + counter = ((day & DAY_MSK) << DAY_OFF) > + | ((hour & HOUR_MSK) << HOUR_OFF) > + | ((min & MIN_MSK) << MIN_OFF) > + | ((sec & SEC_MSK) << SEC_OFF); > + > + regmap_write(rtc->regmap, RTC_CNT, counter); > +} > + > +static int atcrtc_set_time(struct device *dev, struct rtc_time *tm) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + time64_t sys_time; > + int ret; > + > + sys_time = rtc_tm_to_time64(tm); > + > + mutex_lock(&rtc->lock); > + > + ret = atcrtc_check_write_done(rtc); > + if (ret) { > + mutex_unlock(&rtc->lock); > + return ret; > + } > + atcrtc_set_rtc_time(rtc, sys_time); > + > + mutex_unlock(&rtc->lock); > + > + return 0; > +} > + > +static int atcrtc_read_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + struct rtc_time *tm = &wkalrm->time; > + unsigned int rtc_alarm; > + > + mutex_lock(&rtc->lock); > + > + regmap_read(rtc->regmap, RTC_ALM, &rtc_alarm); > + wkalrm->enabled = regmap_test_bits(rtc->regmap, RTC_CR, ALARM_INT); > + > + mutex_unlock(&rtc->lock); > + > + tm->tm_hour = (rtc_alarm >> HOUR_OFF) & HOUR_MSK; > + tm->tm_min = (rtc_alarm >> MIN_OFF) & MIN_MSK; > + tm->tm_sec = (rtc_alarm >> SEC_OFF) & SEC_MSK; > + > + return 0; > +} > + > +static int atcrtc_set_alarm(struct device *dev, struct rtc_wkalrm *wkalrm) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + struct rtc_time *tm = &wkalrm->time; > + unsigned int alm = 0; > + int ret = rtc_valid_tm(tm); > + > + if (ret < 0) { > + dev_err(dev, "Invalid alarm value: %d\n", ret); > + return ret; > + } > + > + mutex_lock(&rtc->lock); > + > + ret = atcrtc_check_write_done(rtc); > + if (ret) { > + mutex_unlock(&rtc->lock); > + return ret; > + } > + > + /* Disable alarm interrupt and clear the alarm flag */ > + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, 0); > + rtc->alarm_en = 0; > + > + /* Set alarm time */ > + alm |= ((tm->tm_sec & SEC_MSK) << SEC_OFF); > + alm |= ((tm->tm_min & MIN_MSK) << MIN_OFF); > + alm |= ((tm->tm_hour & HOUR_MSK) << HOUR_OFF); > + regmap_write(rtc->regmap, RTC_ALM, alm); > + > + if (wkalrm->enabled) { > + rtc->alarm_en = 1; > + ret = atcrtc_check_write_done(rtc); > + if (ret) { > + mutex_unlock(&rtc->lock); > + return ret; > + } > + > + regmap_update_bits(rtc->regmap, RTC_CR, ALARM_INT, ALARM_INT); > + } > + > + mutex_unlock(&rtc->lock); > + return 0; > +} > + > +static int atcrtc_hw_init(struct atcrtc_dev *rtc) > +{ > + unsigned int rtc_id; > + int ret; > + > + regmap_read(rtc->regmap, RTC_ID, &rtc_id); > + if ((rtc_id & ID_MSK) != ATCRTC100ID) > + return -ENOENT; > + > + ret = atcrtc_check_write_done(rtc); > + if (ret) > + return ret; > + regmap_update_bits(rtc->regmap, RTC_CR, RTC_EN, RTC_EN); This is losing some important information, the RTC must only be enabled once the time has been correctly set, then you can check RTC_EN in atcrtc_read_time() to know whether the time is actually valid or not. > + > + return 0; > +} > + > +static const struct rtc_class_ops rtc_ops = { > + .read_time = atcrtc_read_time, > + .set_time = atcrtc_set_time, > + .read_alarm = atcrtc_read_alarm, > + .set_alarm = atcrtc_set_alarm, > + .alarm_irq_enable = atcrtc_alarm_irq_enable, > +}; > + > +static int atcrtc_probe(struct platform_device *pdev) > +{ > + struct atcrtc_dev *atcrtc_dev; > + void __iomem *reg_base; > + int ret = 0; > + > + atcrtc_dev = devm_kzalloc(&pdev->dev, sizeof(*atcrtc_dev), GFP_KERNEL); > + if (!atcrtc_dev) > + return -ENOMEM; > + platform_set_drvdata(pdev, atcrtc_dev); > + mutex_init(&atcrtc_dev->lock); > + > + reg_base = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(reg_base)) { > + dev_err(&pdev->dev, > + "Failed to map I/O space: %ld\n", > + PTR_ERR(reg_base)); > + return PTR_ERR(atcrtc_dev->regmap); > + } > + > + atcrtc_dev->regmap = devm_regmap_init_mmio(&pdev->dev, > + reg_base, > + &atcrtc_regmap_config); > + if (IS_ERR(atcrtc_dev->regmap)) { > + dev_err(&pdev->dev, > + "Failed to initialize regmap: %ld\n", > + PTR_ERR(atcrtc_dev->regmap)); > + return PTR_ERR(atcrtc_dev->regmap); > + } > + > + ret = atcrtc_hw_init(atcrtc_dev); > + if (ret) { > + dev_err(&pdev->dev, "Failed to initialize driver: %d\n", ret); > + return ret; > + } > + > + atcrtc_dev->alarm_irq = platform_get_irq(pdev, 1); > + if (atcrtc_dev->alarm_irq < 0) { > + dev_err(&pdev->dev, > + "Failed to get IRQ for alarm: %d\n", > + atcrtc_dev->alarm_irq); > + return atcrtc_dev->alarm_irq; > + } > + atcrtc_dev->time_irq = platform_get_irq(pdev, 0); > + if (atcrtc_dev->time_irq < 0) { > + dev_err(&pdev->dev, > + "Failed to get IRQ for periodic interrupt: %d\n", > + atcrtc_dev->time_irq); > + return atcrtc_dev->time_irq; > + } > + > + ret = devm_request_irq(&pdev->dev, > + atcrtc_dev->alarm_irq, > + atcrtc_alarm_isr, > + 0, > + "atcrtc alarm", > + atcrtc_dev); > + if (ret) { > + dev_err(&pdev->dev, > + "Failed to request IRQ %d for alarm: %d\n", > + atcrtc_dev->alarm_irq, > + ret); > + return ret; > + } > + > + ret = devm_request_irq(&pdev->dev, > + atcrtc_dev->time_irq, > + atcrtc_periodic_isr, > + 0, > + "atcrtc time", > + atcrtc_dev); > + if (ret) { > + dev_err(&pdev->dev, > + "Failed to request IRQ %d for periodic interrupt: %d\n", > + atcrtc_dev->time_irq, > + ret); > + return ret; > + } > + > + atcrtc_dev->rtc_dev = devm_rtc_allocate_device(&pdev->dev); > + if (IS_ERR(atcrtc_dev->rtc_dev)) { > + dev_err(&pdev->dev, > + "Failed to allocate RTC device: %ld\n", > + PTR_ERR(atcrtc_dev->rtc_dev)); > + return PTR_ERR(atcrtc_dev->rtc_dev); > + } > + > + ret = atcrtc_alarm_enable(&pdev->dev, true); Can't atcrtc_alarm_enable be part of atcrtc_hw_init so you don't have to wait twice? > + if (ret) > + return ret; > + set_bit(RTC_FEATURE_ALARM, atcrtc_dev->rtc_dev->features); > + > + ret = device_init_wakeup(&pdev->dev, true); > + if (ret) { > + dev_err(&pdev->dev, > + "Failed to initialize wake device: %d\n", > + ret); > + return ret; > + } > + > + ret = dev_pm_set_wake_irq(&pdev->dev, atcrtc_dev->alarm_irq); > + if (ret) { > + dev_err(&pdev->dev, "Failed to set wake IRQ: %d\n", ret); > + device_init_wakeup(&pdev->dev, false); > + return ret; > + } > + > + atcrtc_dev->rtc_dev->ops = &rtc_ops; > + /* > + * There are 15 bits in the Day field of the Counter register. > + * It can count up to 32,767 days, about 89.8 years. > + */ > + atcrtc_dev->rtc_dev->range_max = mktime64(2089, 12, 31, 23, 59, 59); > + atcrtc_dev->rtc_dev->range_min = RTC_TIMESTAMP_BEGIN_2000; > + > + INIT_DELAYED_WORK(&atcrtc_dev->rtc_work, atcrtc_alarm_clear); > + return devm_rtc_register_device(atcrtc_dev->rtc_dev); > +} > + > +static int atcrtc_resume(struct device *dev) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + > + if (device_may_wakeup(dev)) > + disable_irq_wake(rtc->alarm_irq); > + > + return 0; > +} > + > +static int atcrtc_suspend(struct device *dev) > +{ > + struct atcrtc_dev *rtc = dev_get_drvdata(dev); > + > + if (device_may_wakeup(dev)) > + enable_irq_wake(rtc->alarm_irq); > + > + return 0; > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(atcrtc_pm_ops, atcrtc_suspend, atcrtc_resume); > + > +static const struct of_device_id atcrtc_dt_match[] = { > + { .compatible = "andestech,atcrtc100" }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, atcrtc_dt_match); > + > +static struct platform_driver atcrtc_platform_driver = { > + .driver = { > + .name = "atcrtc100", > + .of_match_table = atcrtc_dt_match, > + .pm = pm_sleep_ptr(&atcrtc_pm_ops), > + }, > + .probe = atcrtc_probe, > +}; > + > +module_platform_driver(atcrtc_platform_driver); > + > +MODULE_AUTHOR("CL Wang <cl634@andestech.com>"); > +MODULE_DESCRIPTION("Andes ATCRTC100 driver"); > +MODULE_LICENSE("GPL"); > -- > 2.34.1 > -- Alexandre Belloni, co-owner and COO, Bootlin Embedded Linux and Kernel engineering https://bootlin.com ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH V5 1/3] rtc: atcrtc100: Add ATCRTC100 RTC driver 2025-03-31 22:15 ` Alexandre Belloni @ 2025-04-11 8:39 ` CL Wang 0 siblings, 0 replies; 6+ messages in thread From: CL Wang @ 2025-04-11 8:39 UTC (permalink / raw) To: Alexandre Belloni Cc: robh, krzk+dt, conor+dt, devicetree, linux-kernel, linux-rtc, tim609, ycliang, cl634 Hi Alexandre, Thank you very much for your feedback on the patch, and sorry for the delayed response. Below are my replies to your comments and questions. I will prepare and send the next version of the patch as soon as possible. > +#define RTC_MINUTE(x) ((x >> MIN_OFF) & MIN_MSK) /* RTC min */ > +#define RTC_HOUR(x) ((x >> HOUR_OFF) & HOUR_MSK) /* RTC hour */ > +#define RTC_DAYS(x) ((x >> DAY_OFF) & DAY_MSK) /* RTC day */ FIELD_PREP can probably replace those. => That's a good suggestion. I will update this to use bitfield-related macros instead. > +struct atcrtc_dev { > + struct rtc_device *rtc_dev; > + struct regmap *regmap; > + struct delayed_work rtc_work; > + struct mutex lock; This mutex is not necessary, simply use rtc_lock() in you interrupt handler, the rtc core is already locking before calling the rtc_ops. => You're absolutely right. I will remove the mutex and clean up this part accordingly. > + usleep_range(ATCRTC_TIMEOUT_USLEEP_MIN, > + ATCRTC_TIMEOUT_USLEEP_MAX); > + } > + dev_err(&rtc->rtc_dev->dev, "Device is busy too long\n"); Is this error message useful, what would be the user action after seeing this? ==> This message indicates that the RTC hardware might be stuck in a busy state. If this occurs, it suggests a potential hardware issue. During development, it can serve as a hint to review the RTC module's design. In production, a system reset might be required to recover. Based on that, I would prefer to keep this error message for diagnostic purposes. > +static time64_t atcrtc_read_rtc_time(struct atcrtc_dev *rtc) Does this have to be in a separate function? => Not necessarily. It can be merged into atcrtc_read_time(). I will make this adjustment. > + rtc_time64_to_tm(time, tm); > + if (rtc_valid_tm(tm) < 0) { This is not necessary, the core always checks whether the tm is valid. => Thanks for pointing that out. I’ll remove this check. > + rem -= hour * 3600; > + min = rem / 60; > + sec = rem - min * 60; You already had the broken down hour, min and sec, it is not necessary to compute that again here, just fold this function in atcrtc_set_time => You're right, I will simplify this part by integrating it directly into atcrtc_set_time(). > + ret = atcrtc_check_write_done(rtc); > + if (ret) > + return ret; > + regmap_update_bits(rtc->regmap, RTC_CR, RTC_EN, RTC_EN); This is losing some important information, the RTC must only be enabled once the time has been correctly set, then you can check RTC_EN in atcrtc_read_time() to know whether the time is actually valid or not. => I will move the RTC_EN setting to atcrtc_set_time() and add a check for this bit in atcrtc_read_time() to ensure the time from RTC is valid. > + if (IS_ERR(atcrtc_dev->rtc_dev)) { > + dev_err(&pdev->dev, > + "Failed to allocate RTC device: %ld\n", > + PTR_ERR(atcrtc_dev->rtc_dev)); > + return PTR_ERR(atcrtc_dev->rtc_dev); > + } > + > + ret = atcrtc_alarm_enable(&pdev->dev, true); Can't atcrtc_alarm_enable be part of atcrtc_hw_init so you don't have to wait twice? => After reviewing your comment, I agree. I think atcrtc_alarm_enable() should instead be integrated into atcrtc_set_alarm() and removed from here. Thanks again for your detailed feedback. I'll revise the patch accordingly and send out the updated version soon. Best regards, CL ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH V5 2/3] dt-bindings: rtc: Add support for ATCRTC100 RTC 2025-01-10 9:26 [PATCH V5 0/3] rtc: atcrtc100: Add Andes ATCRTC100 RTC driver CL Wang 2025-01-10 9:27 ` [PATCH V5 1/3] rtc: atcrtc100: Add " CL Wang @ 2025-01-10 9:27 ` CL Wang 2025-01-10 9:27 ` [PATCH V5 3/3] MAINTAINERS: Add entry for ATCRTC100 RTC driver CL Wang 2 siblings, 0 replies; 6+ messages in thread From: CL Wang @ 2025-01-10 9:27 UTC (permalink / raw) To: cl634, alexandre.belloni, robh, krzk+dt, conor+dt Cc: devicetree, linux-kernel, linux-rtc, tim609, ycliang, Conor Dooley Document Device Tree bindings for the Andes ATCRTC100 Real-Time Clock. Signed-off-by: CL Wang <cl634@andestech.com> Reviewed-by: Conor Dooley <conor.dooley@microchip.com> --- Changes for v2: - First version of devicetree bindings for the Andes ATCRTC100 Real-Time Clock. Changes for v3: - Used compatible as the filename. - Placed allOf after maintainers. - Replaced additionalProperties: false with unevaluatedProperties: false. - Added descriptions for interrupts. Changes for v4: - Removed wakeup-source attribute. Changes for v5: - Rebased onto the latest commit in rtc-next. No changes to the content itself. - Add Conor's tag --- .../bindings/rtc/andestech,atcrtc100.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml diff --git a/Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml b/Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml new file mode 100644 index 000000000000..ec0a736793c7 --- /dev/null +++ b/Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/rtc/andestech,atcrtc100.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Andes ATCRTC100 Real-Time Clock + +maintainers: + - CL Wang <cl634@andestech.com> + +allOf: + - $ref: rtc.yaml# + +properties: + compatible: + enum: + - andestech,atcrtc100 + + reg: + maxItems: 1 + + interrupts: + items: + - description: Periodic timekeeping interrupt + - description: RTC alarm interrupt + +required: + - compatible + - reg + - interrupts + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + + rtc@f0300000 { + compatible = "andestech,atcrtc100"; + reg = <0xf0300000 0x100>; + interrupts = <1 IRQ_TYPE_LEVEL_HIGH>, <2 IRQ_TYPE_LEVEL_HIGH>; + }; -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH V5 3/3] MAINTAINERS: Add entry for ATCRTC100 RTC driver 2025-01-10 9:26 [PATCH V5 0/3] rtc: atcrtc100: Add Andes ATCRTC100 RTC driver CL Wang 2025-01-10 9:27 ` [PATCH V5 1/3] rtc: atcrtc100: Add " CL Wang 2025-01-10 9:27 ` [PATCH V5 2/3] dt-bindings: rtc: Add support for ATCRTC100 RTC CL Wang @ 2025-01-10 9:27 ` CL Wang 2 siblings, 0 replies; 6+ messages in thread From: CL Wang @ 2025-01-10 9:27 UTC (permalink / raw) To: cl634, alexandre.belloni, robh, krzk+dt, conor+dt Cc: devicetree, linux-kernel, linux-rtc, tim609, ycliang Add support entry for the Andes ATCRTC100 RTC driver in the MAINTAINERS file. Signed-off-by: CL Wang <cl634@andestech.com> --- Changes for v3: - Initial entry for the ATCRTC100 RTC driver. Changes for v4: - No changes Changes for v5: - Rebased onto the latest commit in rtc-next. No changes to the content itself. --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 1e930c7a58b1..b1c4143a3366 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3588,6 +3588,12 @@ F: drivers/power/reset/atc260x-poweroff.c F: drivers/regulator/atc260x-regulator.c F: include/linux/mfd/atc260x/* +ATCRTC100 RTC DRIVER +M: CL Wang <cl634@andestech.com> +S: Supported +F: Documentation/devicetree/bindings/rtc/andestech,atcrtc100.yaml +F: drivers/rtc/rtc-atcrtc100.c + ATHEROS 71XX/9XXX GPIO DRIVER M: Alban Bedel <albeu@free.fr> S: Maintained -- 2.34.1 ^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-04-11 8:39 UTC | newest] Thread overview: 6+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-01-10 9:26 [PATCH V5 0/3] rtc: atcrtc100: Add Andes ATCRTC100 RTC driver CL Wang 2025-01-10 9:27 ` [PATCH V5 1/3] rtc: atcrtc100: Add " CL Wang 2025-03-31 22:15 ` Alexandre Belloni 2025-04-11 8:39 ` CL Wang 2025-01-10 9:27 ` [PATCH V5 2/3] dt-bindings: rtc: Add support for ATCRTC100 RTC CL Wang 2025-01-10 9:27 ` [PATCH V5 3/3] MAINTAINERS: Add entry for ATCRTC100 RTC driver CL Wang
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.