diff options
Diffstat (limited to 'drivers/rtc/rtc-atcrtc100.c')
| -rw-r--r-- | drivers/rtc/rtc-atcrtc100.c | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-atcrtc100.c b/drivers/rtc/rtc-atcrtc100.c new file mode 100644 index 000000000000..9808fc2c5a49 --- /dev/null +++ b/drivers/rtc/rtc-atcrtc100.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Andes ATCRTC100 real time clock. + * + * Copyright (C) 2025 Andes Technology Corporation + */ + +#include <linux/bitfield.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/regmap.h> +#include <linux/rtc.h> +#include <linux/workqueue.h> + +/* Register Offsets */ +#define RTC_ID 0x00 /* ID and Revision Register */ +#define RTC_RSV 0x04 /* Reserved Register */ +#define RTC_CNT 0x10 /* Counter Register */ +#define RTC_ALM 0x14 /* Alarm Register */ +#define RTC_CR 0x18 /* Control Register */ +#define RTC_STA 0x1C /* Status Register */ +#define RTC_TRIM 0x20 /* Digital Trimming Register */ + +/* RTC_ID Register */ +#define ID_MSK GENMASK(31, 8) +#define ID_ATCRTC100 0x030110 + +/* RTC_CNT and RTC_ALM Register Fields */ +#define SEC_MSK GENMASK(5, 0) +#define MIN_MSK GENMASK(11, 6) +#define HOUR_MSK GENMASK(16, 12) +#define DAY_MSK GENMASK(31, 17) +#define RTC_SEC_GET(x) FIELD_GET(SEC_MSK, x) +#define RTC_MIN_GET(x) FIELD_GET(MIN_MSK, x) +#define RTC_HOUR_GET(x) FIELD_GET(HOUR_MSK, x) +#define RTC_DAY_GET(x) FIELD_GET(DAY_MSK, x) +#define RTC_SEC_SET(x) FIELD_PREP(SEC_MSK, x) +#define RTC_MIN_SET(x) FIELD_PREP(MIN_MSK, x) +#define RTC_HOUR_SET(x) FIELD_PREP(HOUR_MSK, x) +#define RTC_DAY_SET(x) FIELD_PREP(DAY_MSK, x) + +/* RTC_CR Register Bits */ +#define RTC_EN BIT(0) /* RTC Enable */ +#define ALARM_WAKEUP BIT(1) /* Alarm Wakeup Enable */ +#define ALARM_INT BIT(2) /* Alarm Interrupt Enable */ +#define DAY_INT BIT(3) /* Day Interrupt Enable */ +#define HOUR_INT BIT(4) /* Hour Interrupt Enable */ +#define MIN_INT BIT(5) /* Minute Interrupt Enable */ +#define SEC_INT BIT(6) /* Second Periodic Interrupt Enable */ +#define HSEC_INT BIT(7) /* Half-Second Periodic Interrupt Enable */ + +/* RTC_STA Register Bits */ +#define WRITE_DONE BIT(16) /* Register write completion status */ + +/* Time conversion macro */ +#define ATCRTC_TIME_TO_SEC(D, H, M, S) \ + ((time64_t)(D) * 86400 + (H) * 3600 + (M) * 60 + (S)) + +/* Timeout for waiting for the write_done bit */ +#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 work_struct rtc_work; + unsigned int alarm_irq; + bool 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 - Wait for RTC registers to be synchronized. + * @rtc: Pointer to the atcrtc_dev structure. + * + * 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 (Counter, Alarm, Control, etc.) is written. + * It returns to one only after all previous updates have been fully + * synchronized to the RTC clock domain. This function polls the WriteDone + * bit with a timeout to ensure the device is ready for the next operation. + * + * Return: 0 on success, or -EBUSY on timeout. + */ +static int atcrtc_check_write_done(struct atcrtc_dev *rtc) +{ + unsigned int val; + + /* + * Using read_poll_timeout is more efficient than a manual loop + * with usleep_range. + */ + return regmap_read_poll_timeout(rtc->regmap, RTC_STA, val, + val & WRITE_DONE, + ATCRTC_TIMEOUT_USLEEP_MIN, + ATCRTC_TIMEOUT_US); +} + +static irqreturn_t atcrtc_alarm_isr(int irq, void *dev) +{ + struct atcrtc_dev *rtc = dev; + unsigned int status; + + regmap_read(rtc->regmap, RTC_STA, &status); + if (status & ALARM_INT) { + regmap_write(rtc->regmap, RTC_STA, ALARM_INT); + rtc->alarm_en = false; + schedule_work(&rtc->rtc_work); + 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); + unsigned int mask; + int ret; + + ret = atcrtc_check_write_done(rtc); + if (ret) + return ret; + + mask = ALARM_WAKEUP | ALARM_INT; + regmap_update_bits(rtc->regmap, RTC_CR, mask, enable ? mask : 0); + + return 0; +} + +static void atcrtc_alarm_clear(struct work_struct *work) +{ + struct atcrtc_dev *rtc = + container_of(work, struct atcrtc_dev, rtc_work); + int ret; + + rtc_lock(rtc->rtc_dev); + + if (!rtc->alarm_en) { + ret = atcrtc_check_write_done(rtc); + if (ret) + dev_info(&rtc->rtc_dev->dev, + "failed to sync before clearing alarm: %d\n", + ret); + else + 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) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + time64_t time; + unsigned int rtc_cnt; + + if (!regmap_test_bits(rtc->regmap, RTC_CR, RTC_EN)) + return -EIO; + + regmap_read(rtc->regmap, RTC_CNT, &rtc_cnt); + time = ATCRTC_TIME_TO_SEC(RTC_DAY_GET(rtc_cnt), + RTC_HOUR_GET(rtc_cnt), + RTC_MIN_GET(rtc_cnt), + RTC_SEC_GET(rtc_cnt)); + rtc_time64_to_tm(time, tm); + + return 0; +} + +static int atcrtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct atcrtc_dev *rtc = dev_get_drvdata(dev); + time64_t time; + unsigned int counter; + unsigned int day; + int ret; + + time = rtc_tm_to_time64(tm); + day = div_s64(time, 86400); + counter = RTC_DAY_SET(day) | + RTC_HOUR_SET(tm->tm_hour) | + RTC_MIN_SET(tm->tm_min) | + RTC_SEC_SET(tm->tm_sec); + ret = atcrtc_check_write_done(rtc); + if (ret) + return ret; + regmap_write(rtc->regmap, RTC_CNT, counter); + + ret = atcrtc_check_write_done(rtc); + if (ret) + return ret; + regmap_update_bits(rtc->regmap, RTC_CR, RTC_EN, RTC_EN); + + 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; + + wkalrm->enabled = regmap_test_bits(rtc->regmap, RTC_CR, ALARM_INT); + regmap_read(rtc->regmap, RTC_ALM, &rtc_alarm); + tm->tm_hour = RTC_HOUR_GET(rtc_alarm); + tm->tm_min = RTC_MIN_GET(rtc_alarm); + tm->tm_sec = RTC_SEC_GET(rtc_alarm); + + /* The RTC alarm does not support day/month/year fields */ + tm->tm_mday = -1; + tm->tm_mon = -1; + tm->tm_year = -1; + + 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 rtc_alarm; + int ret; + + /* Disable alarm first before setting a new one */ + ret = atcrtc_alarm_irq_enable(dev, 0); + if (ret) + return ret; + + rtc->alarm_en = false; + + rtc_alarm = RTC_SEC_SET(tm->tm_sec) | + RTC_MIN_SET(tm->tm_min) | + RTC_HOUR_SET(tm->tm_hour); + + ret = atcrtc_check_write_done(rtc); + if (ret) + return ret; + + regmap_write(rtc->regmap, RTC_ALM, rtc_alarm); + + rtc->alarm_en = wkalrm->enabled; + ret = atcrtc_alarm_irq_enable(dev, wkalrm->enabled); + + return ret; +} + +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; + unsigned int rtc_id; + int ret; + + atcrtc_dev = devm_kzalloc(&pdev->dev, sizeof(*atcrtc_dev), GFP_KERNEL); + if (!atcrtc_dev) + return -ENOMEM; + platform_set_drvdata(pdev, atcrtc_dev); + + reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(reg_base)) + return dev_err_probe(&pdev->dev, PTR_ERR(reg_base), + "Failed to map I/O space\n"); + + atcrtc_dev->regmap = devm_regmap_init_mmio(&pdev->dev, + reg_base, + &atcrtc_regmap_config); + if (IS_ERR(atcrtc_dev->regmap)) + return dev_err_probe(&pdev->dev, PTR_ERR(atcrtc_dev->regmap), + "Failed to initialize regmap\n"); + + regmap_read(atcrtc_dev->regmap, RTC_ID, &rtc_id); + if (FIELD_GET(ID_MSK, rtc_id) != ID_ATCRTC100) + return dev_err_probe(&pdev->dev, -ENODEV, + "Failed to initialize RTC: unsupported hardware ID 0x%x\n", + rtc_id); + + ret = platform_get_irq(pdev, 1); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Failed to get IRQ for alarm\n"); + atcrtc_dev->alarm_irq = ret; + + ret = devm_request_irq(&pdev->dev, + atcrtc_dev->alarm_irq, + atcrtc_alarm_isr, + 0, + "atcrtc_alarm", + atcrtc_dev); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to request IRQ %d for alarm\n", + atcrtc_dev->alarm_irq); + + atcrtc_dev->rtc_dev = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(atcrtc_dev->rtc_dev)) + return dev_err_probe(&pdev->dev, PTR_ERR(atcrtc_dev->rtc_dev), + "Failed to allocate RTC device\n"); + + set_bit(RTC_FEATURE_ALARM, atcrtc_dev->rtc_dev->features); + ret = device_init_wakeup(&pdev->dev, true); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to initialize wake capability\n"); + + ret = dev_pm_set_wake_irq(&pdev->dev, atcrtc_dev->alarm_irq); + if (ret) { + device_init_wakeup(&pdev->dev, false); + return dev_err_probe(&pdev->dev, ret, + "Failed to set wake IRQ\n"); + } + + atcrtc_dev->rtc_dev->ops = &rtc_ops; + + INIT_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"); |
