rtc: atcrtc100: Add ATCRTC100 RTC driver
RTC driver for Andes ATCRTC100 Real-Time Clock. Signed-off-by: CL Wang <cl634@andestech.com> Link: https://patch.msgid.link/20250915031439.2680364-4-cl634@andestech.com Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
This commit is contained in:
committed by
Alexandre Belloni
parent
a603092d5b
commit
7adca706fe
@@ -1063,6 +1063,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 ARCH_ANDES || COMPILE_TEST
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
379
drivers/rtc/rtc-atcrtc100.c
Normal file
379
drivers/rtc/rtc-atcrtc100.c
Normal file
@@ -0,0 +1,379 @@
|
||||
// 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);
|
||||
|
||||
atcrtc_dev->alarm_irq = platform_get_irq(pdev, 1);
|
||||
if (atcrtc_dev->alarm_irq < 0)
|
||||
return dev_err_probe(&pdev->dev, atcrtc_dev->alarm_irq,
|
||||
"Failed to get IRQ for alarm\n");
|
||||
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");
|
||||
Reference in New Issue
Block a user