mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-03-16 04:04:06 +00:00
driver: rtc: Add StarFive JH7110 rtc driver
Add RTC driver and support for StarFive JH7110 SoC. Signed-off-by: ziv.xu <ziv.xu@starfivetech.com> Signed-off-by: Hal Feng <hal.feng@starfivetech.com>
This commit is contained in:
parent
ad6c5a4872
commit
1a3fc138b6
3 changed files with 752 additions and 0 deletions
|
@ -1327,6 +1327,14 @@ config RTC_DRV_NTXEC
|
|||
embedded controller found in certain e-book readers designed by the
|
||||
original design manufacturer Netronix.
|
||||
|
||||
config RTC_DRV_STARFIVE
|
||||
tristate "StarFive 32.768k-RTC"
|
||||
depends on ARCH_STARFIVE
|
||||
depends on OF
|
||||
help
|
||||
If you say Y here you will get support for the RTC found on
|
||||
StarFive SOCS.
|
||||
|
||||
comment "on-CPU RTC drivers"
|
||||
|
||||
config RTC_DRV_ASM9260
|
||||
|
|
|
@ -163,6 +163,7 @@ obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o
|
|||
obj-$(CONFIG_RTC_DRV_SNVS) += rtc-snvs.o
|
||||
obj-$(CONFIG_RTC_DRV_SPEAR) += rtc-spear.o
|
||||
obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o
|
||||
obj-$(CONFIG_RTC_DRV_STARFIVE) += rtc-starfive.o
|
||||
obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o
|
||||
obj-$(CONFIG_RTC_DRV_ST_LPC) += rtc-st-lpc.o
|
||||
obj-$(CONFIG_RTC_DRV_STM32) += rtc-stm32.o
|
||||
|
|
743
drivers/rtc/rtc-starfive.c
Normal file
743
drivers/rtc/rtc-starfive.c
Normal file
|
@ -0,0 +1,743 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* RTC driver for the StarFive JH7110 SoC
|
||||
*
|
||||
* Copyright (C) 2021 StarFive Technology Co., Ltd.
|
||||
*/
|
||||
|
||||
#include <asm/delay.h>
|
||||
#include <linux/bcd.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/completion.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/rtc.h>
|
||||
|
||||
/* Registers */
|
||||
#define SFT_RTC_CFG 0x00
|
||||
#define SFT_RTC_SW_CAL_VALUE 0x04
|
||||
#define SFT_RTC_HW_CAL_CFG 0x08
|
||||
#define SFT_RTC_CMP_CFG 0x0C
|
||||
#define SFT_RTC_IRQ_EN 0x10
|
||||
#define SFT_RTC_IRQ_EVEVT 0x14
|
||||
#define SFT_RTC_IRQ_STATUS 0x18
|
||||
#define SFT_RTC_CAL_VALUE 0x24
|
||||
#define SFT_RTC_CFG_TIME 0x28
|
||||
#define SFT_RTC_CFG_DATE 0x2C
|
||||
#define SFT_RTC_ACT_TIME 0x34
|
||||
#define SFT_RTC_ACT_DATE 0x38
|
||||
#define SFT_RTC_TIME 0x3C
|
||||
#define SFT_RTC_DATE 0x40
|
||||
#define SFT_RTC_TIME_LATCH 0x44
|
||||
#define SFT_RTC_DATE_LATCH 0x48
|
||||
|
||||
/* RTC_CFG */
|
||||
#define RTC_CFG_ENABLE_SHIFT 0 /* RW: RTC Enable. */
|
||||
#define RTC_CFG_CAL_EN_HW_SHIFT 1 /* RW: Enable of hardware calibretion. */
|
||||
#define RTC_CFG_CAL_SEL_SHIFT 2 /* RW: select the hw/sw calibretion mode.*/
|
||||
#define RTC_CFG_HOUR_MODE_SHIFT 3 /* RW: time hour mode. 24h|12h */
|
||||
|
||||
/* RTC_SW_CAL_VALUE */
|
||||
#define RTC_SW_CAL_VALUE_MASK GENMASK(15, 0)
|
||||
#define RTC_SW_CAL_MAX RTC_SW_CAL_VALUE_MASK
|
||||
#define RTC_SW_CAL_MIN 0
|
||||
#define RTC_TICKS_PER_SEC 32768 /* Number of ticks per second */
|
||||
#define RTC_PPB_MULT 1000000000LL /* Multiplier for ppb conversions */
|
||||
|
||||
/* RTC_HW_CAL_CFG */
|
||||
#define RTC_HW_CAL_REF_SEL_SHIFT 0
|
||||
#define RTC_HW_CAL_FRQ_SEL_SHIFT 1
|
||||
|
||||
/* IRQ_EN/IRQ_EVEVT/IRQ_STATUS */
|
||||
#define RTC_IRQ_CAL_START BIT(0)
|
||||
#define RTC_IRQ_CAL_FINISH BIT(1)
|
||||
#define RTC_IRQ_CMP BIT(2)
|
||||
#define RTC_IRQ_1SEC BIT(3)
|
||||
#define RTC_IRQ_ALAEM BIT(4)
|
||||
#define RTC_IRQ_EVT_UPDATE_PSE BIT(31) /* WO: Enable of update time&&date, IRQ_EVEVT only */
|
||||
#define RTC_IRQ_ALL (RTC_IRQ_CAL_START \
|
||||
| RTC_IRQ_CAL_FINISH \
|
||||
| RTC_IRQ_CMP \
|
||||
| RTC_IRQ_1SEC \
|
||||
| RTC_IRQ_ALAEM)
|
||||
|
||||
/* CAL_VALUE */
|
||||
#define RTC_CAL_VALUE_MASK GENMASK(15, 0)
|
||||
|
||||
/* CFG_TIME/ACT_TIME/RTC_TIME */
|
||||
#define TIME_SEC_MASK GENMASK(6, 0)
|
||||
#define TIME_MIN_MASK GENMASK(13, 7)
|
||||
#define TIME_HOUR_MASK GENMASK(20, 14)
|
||||
|
||||
/* CFG_DATE/ACT_DATE/RTC_DATE */
|
||||
#define DATE_DAY_MASK GENMASK(5, 0)
|
||||
#define DATE_MON_MASK GENMASK(10, 6)
|
||||
#define DATE_YEAR_MASK GENMASK(18, 11)
|
||||
|
||||
#define INT_TIMEOUT_US 180
|
||||
|
||||
enum RTC_HOUR_MODE {
|
||||
RTC_HOUR_MODE_12H = 0,
|
||||
RTC_HOUR_MODE_24H = 1
|
||||
};
|
||||
|
||||
enum RTC_CAL_MODE {
|
||||
RTC_CAL_MODE_SW = 0,
|
||||
RTC_CAL_MODE_HW = 1
|
||||
};
|
||||
|
||||
enum RTC_HW_CAL_REF_MODE {
|
||||
RTC_CAL_CLK_REF = 0,
|
||||
RTC_CAL_CLK_MARK = 1
|
||||
};
|
||||
|
||||
static const unsigned long refclk_list[] = {
|
||||
1000000,
|
||||
2000000,
|
||||
4000000,
|
||||
5927000,
|
||||
6000000,
|
||||
7200000,
|
||||
8000000,
|
||||
10250000,
|
||||
11059200,
|
||||
12000000,
|
||||
12288000,
|
||||
13560000,
|
||||
16000000,
|
||||
19200000,
|
||||
20000000,
|
||||
22118000,
|
||||
24000000,
|
||||
24567000,
|
||||
25000000,
|
||||
26000000,
|
||||
27000000,
|
||||
30000000,
|
||||
32000000,
|
||||
33868800,
|
||||
36000000,
|
||||
36860000,
|
||||
40000000,
|
||||
44000000,
|
||||
50000000,
|
||||
54000000,
|
||||
28224000,
|
||||
28000000,
|
||||
};
|
||||
|
||||
struct sft_rtc {
|
||||
struct rtc_device *rtc_dev;
|
||||
struct completion cal_done;
|
||||
struct completion onesec_done;
|
||||
struct clk *pclk;
|
||||
struct clk *cal_clk;
|
||||
struct reset_control *rst_array;
|
||||
int hw_cal_map;
|
||||
void __iomem *regs;
|
||||
int rtc_irq;
|
||||
int ms_pulse_irq;
|
||||
int one_sec_pulse_irq;
|
||||
};
|
||||
|
||||
static inline void sft_rtc_set_enabled(struct sft_rtc *srtc, bool enabled)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (enabled) {
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val |= BIT(RTC_CFG_ENABLE_SHIFT);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
} else {
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val &= ~BIT(RTC_CFG_ENABLE_SHIFT);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool sft_rtc_get_enabled(struct sft_rtc *srtc)
|
||||
{
|
||||
return !!(readl(srtc->regs + SFT_RTC_CFG) & BIT(RTC_CFG_ENABLE_SHIFT));
|
||||
}
|
||||
|
||||
static inline void sft_rtc_set_mode(struct sft_rtc *srtc, enum RTC_HOUR_MODE mode)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val |= mode << RTC_CFG_HOUR_MODE_SHIFT;
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
}
|
||||
|
||||
static inline int sft_rtc_irq_enable(struct sft_rtc *srtc, u32 irq, bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (!(irq & RTC_IRQ_ALL))
|
||||
return -EINVAL;
|
||||
|
||||
if (enable) {
|
||||
val = readl(srtc->regs + SFT_RTC_IRQ_EN);
|
||||
val |= irq;
|
||||
writel(val, srtc->regs + SFT_RTC_IRQ_EN);
|
||||
} else {
|
||||
val = readl(srtc->regs + SFT_RTC_IRQ_EN);
|
||||
val &= ~irq;
|
||||
writel(val, srtc->regs + SFT_RTC_IRQ_EN);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
sft_rtc_set_cal_hw_enable(struct sft_rtc *srtc, bool enable)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
if (enable) {
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val |= BIT(RTC_CFG_CAL_EN_HW_SHIFT);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
} else {
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val &= ~BIT(RTC_CFG_CAL_EN_HW_SHIFT);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
sft_rtc_set_cal_mode(struct sft_rtc *srtc, enum RTC_CAL_MODE mode)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_CFG);
|
||||
val |= mode << RTC_CFG_CAL_SEL_SHIFT;
|
||||
writel(val, srtc->regs + SFT_RTC_CFG);
|
||||
}
|
||||
|
||||
static int sft_rtc_get_hw_calclk(struct device *dev, unsigned long freq)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(refclk_list); i++)
|
||||
if (refclk_list[i] == freq)
|
||||
return i;
|
||||
|
||||
dev_err(dev, "refclk: %ldHz do not support.\n", freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static inline void sft_rtc_reg2time(struct rtc_time *tm, u32 reg)
|
||||
{
|
||||
tm->tm_hour = bcd2bin(FIELD_GET(TIME_HOUR_MASK, reg));
|
||||
tm->tm_min = bcd2bin(FIELD_GET(TIME_MIN_MASK, reg));
|
||||
tm->tm_sec = bcd2bin(FIELD_GET(TIME_SEC_MASK, reg));
|
||||
}
|
||||
|
||||
static inline void sft_rtc_reg2date(struct rtc_time *tm, u32 reg)
|
||||
{
|
||||
tm->tm_year = bcd2bin(FIELD_GET(DATE_YEAR_MASK, reg)) + 100;
|
||||
tm->tm_mon = bcd2bin(FIELD_GET(DATE_MON_MASK, reg)) - 1;
|
||||
tm->tm_mday = bcd2bin(FIELD_GET(DATE_DAY_MASK, reg));
|
||||
}
|
||||
|
||||
static inline u32 sft_rtc_time2reg(struct rtc_time *tm)
|
||||
{
|
||||
return FIELD_PREP(TIME_HOUR_MASK, bin2bcd(tm->tm_hour)) |
|
||||
FIELD_PREP(TIME_MIN_MASK, bin2bcd(tm->tm_min)) |
|
||||
FIELD_PREP(TIME_SEC_MASK, bin2bcd(tm->tm_sec));
|
||||
}
|
||||
|
||||
static inline u32 sft_rtc_date2reg(struct rtc_time *tm)
|
||||
{
|
||||
return FIELD_PREP(DATE_YEAR_MASK, bin2bcd(tm->tm_year - 100)) |
|
||||
FIELD_PREP(DATE_MON_MASK, bin2bcd(tm->tm_mon + 1)) |
|
||||
FIELD_PREP(DATE_DAY_MASK, bin2bcd(tm->tm_mday));
|
||||
}
|
||||
|
||||
static inline void sft_rtc_update_pulse(struct sft_rtc *srtc)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT);
|
||||
val |= RTC_IRQ_EVT_UPDATE_PSE;
|
||||
writel(val, srtc->regs + SFT_RTC_IRQ_EVEVT);
|
||||
}
|
||||
|
||||
static irqreturn_t sft_rtc_irq_handler(int irq, void *data)
|
||||
{
|
||||
struct sft_rtc *srtc = data;
|
||||
struct timerqueue_node *next;
|
||||
u32 irq_flags = 0;
|
||||
u32 irq_mask = 0;
|
||||
u32 val;
|
||||
int ret = 0;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_IRQ_EVEVT);
|
||||
if (val & RTC_IRQ_CAL_START)
|
||||
irq_mask |= RTC_IRQ_CAL_START;
|
||||
|
||||
if (val & RTC_IRQ_CAL_FINISH) {
|
||||
irq_mask |= RTC_IRQ_CAL_FINISH;
|
||||
complete(&srtc->cal_done);
|
||||
}
|
||||
|
||||
if (val & RTC_IRQ_CMP)
|
||||
irq_mask |= RTC_IRQ_CMP;
|
||||
|
||||
if (val & RTC_IRQ_1SEC) {
|
||||
irq_flags |= RTC_PF;
|
||||
irq_mask |= RTC_IRQ_1SEC;
|
||||
complete(&srtc->onesec_done);
|
||||
}
|
||||
|
||||
if (val & RTC_IRQ_ALAEM) {
|
||||
irq_flags |= RTC_AF;
|
||||
irq_mask |= RTC_IRQ_ALAEM;
|
||||
|
||||
next = timerqueue_getnext(&srtc->rtc_dev->timerqueue);
|
||||
if (next == &srtc->rtc_dev->aie_timer.node)
|
||||
dev_info(&srtc->rtc_dev->dev, "alarm expires");
|
||||
}
|
||||
|
||||
writel(irq_mask, srtc->regs + SFT_RTC_IRQ_EVEVT);
|
||||
|
||||
/* Wait interrupt flag clear */
|
||||
ret = readl_poll_timeout_atomic(srtc->regs + SFT_RTC_IRQ_EVEVT, val,
|
||||
(val & irq_mask) == 0, 0, INT_TIMEOUT_US);
|
||||
if (ret)
|
||||
dev_warn(&srtc->rtc_dev->dev, "fail to clear rtc interrupt flag\n");
|
||||
|
||||
if (irq_flags)
|
||||
rtc_update_irq(srtc->rtc_dev, 1, irq_flags | RTC_IRQF);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int sft_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
int irq_1sec_state_start, irq_1sec_state_end;
|
||||
|
||||
/* If the RTC is disabled, assume the values are invalid */
|
||||
if (!sft_rtc_get_enabled(srtc))
|
||||
return -EINVAL;
|
||||
|
||||
irq_1sec_state_start =
|
||||
(readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1;
|
||||
|
||||
read_again:
|
||||
val = readl(srtc->regs + SFT_RTC_TIME);
|
||||
sft_rtc_reg2time(tm, val);
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_DATE);
|
||||
sft_rtc_reg2date(tm, val);
|
||||
|
||||
if (irq_1sec_state_start == 0) {
|
||||
irq_1sec_state_end =
|
||||
(readl(srtc->regs + SFT_RTC_IRQ_STATUS) & RTC_IRQ_1SEC) == 0 ? 0 : 1;
|
||||
if (irq_1sec_state_end == 1) {
|
||||
irq_1sec_state_start = 1;
|
||||
goto read_again;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
val = sft_rtc_time2reg(tm);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG_TIME);
|
||||
|
||||
val = sft_rtc_date2reg(tm);
|
||||
writel(val, srtc->regs + SFT_RTC_CFG_DATE);
|
||||
|
||||
/* Update pulse */
|
||||
sft_rtc_update_pulse(srtc);
|
||||
|
||||
/* Ensure that data is fully written */
|
||||
ret = wait_for_completion_interruptible_timeout(&srtc->onesec_done,
|
||||
usecs_to_jiffies(120));
|
||||
if (ret) {
|
||||
dev_warn(dev,
|
||||
"rtc wait for completion interruptible timeout.\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
|
||||
return sft_rtc_irq_enable(srtc, RTC_IRQ_ALAEM, enabled);
|
||||
}
|
||||
|
||||
static int sft_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_ACT_TIME);
|
||||
sft_rtc_reg2time(&alarm->time, val);
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_ACT_DATE);
|
||||
sft_rtc_reg2date(&alarm->time, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
|
||||
sft_rtc_alarm_irq_enable(dev, 0);
|
||||
|
||||
val = sft_rtc_time2reg(&alarm->time);
|
||||
writel(val, srtc->regs + SFT_RTC_ACT_TIME);
|
||||
|
||||
val = sft_rtc_date2reg(&alarm->time);
|
||||
writel(val, srtc->regs + SFT_RTC_ACT_DATE);
|
||||
|
||||
sft_rtc_alarm_irq_enable(dev, alarm->enabled);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_get_offset(struct device *dev, long *offset)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
s64 tmp;
|
||||
u32 val;
|
||||
|
||||
val = readl(srtc->regs + SFT_RTC_CAL_VALUE)
|
||||
& RTC_SW_CAL_VALUE_MASK;
|
||||
val += 1;
|
||||
/*
|
||||
* the adjust val range is [0x0000-0xffff],
|
||||
* the default val is 0x7fff (32768-1),mapping offset=0 ;
|
||||
*/
|
||||
tmp = (s64)val - RTC_TICKS_PER_SEC;
|
||||
tmp *= RTC_PPB_MULT;
|
||||
tmp = div_s64(tmp, RTC_TICKS_PER_SEC);
|
||||
|
||||
/* Offset value operates in negative way, so swap sign */
|
||||
*offset = -tmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_set_offset(struct device *dev, long offset)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
s64 tmp;
|
||||
u32 val;
|
||||
|
||||
tmp = offset * RTC_TICKS_PER_SEC;
|
||||
tmp = div_s64(tmp, RTC_PPB_MULT);
|
||||
|
||||
tmp = RTC_TICKS_PER_SEC - tmp;
|
||||
tmp -= 1;
|
||||
if (tmp > RTC_SW_CAL_MAX || tmp < RTC_SW_CAL_MIN) {
|
||||
dev_err(dev, "offset is out of range.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
val = tmp & RTC_SW_CAL_VALUE_MASK;
|
||||
/* set software calibration value */
|
||||
writel(val, srtc->regs + SFT_RTC_SW_CAL_VALUE);
|
||||
|
||||
/* set CFG_RTC-cal_sel to select calibretion by software. */
|
||||
sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __maybe_unused int
|
||||
sft_rtc_hw_adjustment(struct device *dev, unsigned int enable)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
u32 val;
|
||||
|
||||
if (srtc->hw_cal_map <= 0) {
|
||||
dev_err(dev, "fail to get cal-clock-freq.\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, true);
|
||||
|
||||
/* Set reference clock frequency value */
|
||||
val = readl(srtc->regs + SFT_RTC_HW_CAL_CFG);
|
||||
val |= (srtc->hw_cal_map << RTC_HW_CAL_FRQ_SEL_SHIFT);
|
||||
writel(val, srtc->regs + SFT_RTC_HW_CAL_CFG);
|
||||
|
||||
/* Set CFG_RTC-cal_sel to select calibretion by hardware. */
|
||||
sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_HW);
|
||||
|
||||
/* Set CFG_RTC-cal_en_hw to launch hardware calibretion.*/
|
||||
sft_rtc_set_cal_hw_enable(srtc, true);
|
||||
|
||||
wait_for_completion_interruptible_timeout(&srtc->cal_done,
|
||||
usecs_to_jiffies(100));
|
||||
|
||||
sft_rtc_irq_enable(srtc, RTC_IRQ_CAL_FINISH, false);
|
||||
} else {
|
||||
sft_rtc_set_cal_mode(srtc, RTC_CAL_MODE_SW);
|
||||
sft_rtc_set_cal_hw_enable(srtc, false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_get_cal_clk(struct device *dev, struct sft_rtc *srtc)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
unsigned long cal_clk_freq;
|
||||
u32 freq;
|
||||
int ret;
|
||||
|
||||
srtc->cal_clk = devm_clk_get(dev, "cal_clk");
|
||||
if (IS_ERR(srtc->cal_clk))
|
||||
return PTR_ERR(srtc->cal_clk);
|
||||
|
||||
clk_prepare_enable(srtc->cal_clk);
|
||||
|
||||
cal_clk_freq = clk_get_rate(srtc->cal_clk);
|
||||
if (!cal_clk_freq) {
|
||||
dev_warn(dev,
|
||||
"get rate failed, next try to get from dts.\n");
|
||||
ret = of_property_read_u32(np, "rtc,cal-clock-freq", &freq);
|
||||
if (!ret) {
|
||||
cal_clk_freq = (u64)freq;
|
||||
} else {
|
||||
dev_err(dev,
|
||||
"Need rtc,cal-clock-freq define in dts.\n");
|
||||
goto err_disable_cal_clk;
|
||||
}
|
||||
}
|
||||
|
||||
srtc->hw_cal_map = sft_rtc_get_hw_calclk(dev, cal_clk_freq);
|
||||
if (srtc->hw_cal_map < 0) {
|
||||
ret = srtc->hw_cal_map;
|
||||
goto err_disable_cal_clk;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_cal_clk:
|
||||
clk_disable_unprepare(srtc->cal_clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sft_rtc_get_irq(struct platform_device *pdev, struct sft_rtc *srtc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
srtc->rtc_irq = platform_get_irq_byname(pdev, "rtc");
|
||||
if (srtc->rtc_irq < 0)
|
||||
return -EINVAL;
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, srtc->rtc_irq,
|
||||
sft_rtc_irq_handler, 0,
|
||||
KBUILD_MODNAME, srtc);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to request interrupt, %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct rtc_class_ops starfive_rtc_ops = {
|
||||
.read_time = sft_rtc_read_time,
|
||||
.set_time = sft_rtc_set_time,
|
||||
.read_alarm = sft_rtc_read_alarm,
|
||||
.set_alarm = sft_rtc_set_alarm,
|
||||
.alarm_irq_enable = sft_rtc_alarm_irq_enable,
|
||||
.set_offset = sft_rtc_set_offset,
|
||||
.read_offset = sft_rtc_get_offset,
|
||||
};
|
||||
|
||||
static int sft_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct sft_rtc *srtc;
|
||||
struct rtc_time tm;
|
||||
struct irq_desc *desc;
|
||||
int ret;
|
||||
|
||||
srtc = devm_kzalloc(dev, sizeof(*srtc), GFP_KERNEL);
|
||||
if (!srtc)
|
||||
return -ENOMEM;
|
||||
|
||||
srtc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(srtc->regs))
|
||||
return PTR_ERR(srtc->regs);
|
||||
|
||||
srtc->pclk = devm_clk_get(dev, "pclk");
|
||||
if (IS_ERR(srtc->pclk)) {
|
||||
ret = PTR_ERR(srtc->pclk);
|
||||
dev_err(dev,
|
||||
"Failed to retrieve the peripheral clock, %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
srtc->rst_array = devm_reset_control_array_get_exclusive(dev);
|
||||
if (IS_ERR(srtc->rst_array)) {
|
||||
ret = PTR_ERR(srtc->rst_array);
|
||||
dev_err(dev,
|
||||
"Failed to retrieve the rtc reset, %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
init_completion(&srtc->cal_done);
|
||||
init_completion(&srtc->onesec_done);
|
||||
|
||||
ret = clk_prepare_enable(srtc->pclk);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to enable the peripheral clock, %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sft_rtc_get_cal_clk(dev, srtc);
|
||||
if (ret)
|
||||
goto err_disable_pclk;
|
||||
|
||||
ret = reset_control_deassert(srtc->rst_array);
|
||||
if (ret) {
|
||||
dev_err(dev,
|
||||
"Failed to deassert rtc resets, %d\n", ret);
|
||||
goto err_disable_cal_clk;
|
||||
}
|
||||
|
||||
ret = sft_rtc_get_irq(pdev, srtc);
|
||||
if (ret)
|
||||
goto err_disable_cal_clk;
|
||||
|
||||
srtc->rtc_dev = devm_rtc_allocate_device(dev);
|
||||
if (IS_ERR(srtc->rtc_dev))
|
||||
return PTR_ERR(srtc->rtc_dev);
|
||||
|
||||
platform_set_drvdata(pdev, srtc);
|
||||
|
||||
/* The RTC supports 01.01.2001 - 31.12.2099 */
|
||||
srtc->rtc_dev->range_min = mktime64(2001, 1, 1, 0, 0, 0);
|
||||
srtc->rtc_dev->range_max = mktime64(2099, 12, 31, 23, 59, 59);
|
||||
|
||||
srtc->rtc_dev->ops = &starfive_rtc_ops;
|
||||
device_init_wakeup(dev, true);
|
||||
|
||||
desc = irq_to_desc(srtc->rtc_irq);
|
||||
irq_desc_get_chip(desc)->flags = IRQCHIP_SKIP_SET_WAKE;
|
||||
|
||||
/* Always use 24-hour mode and keep the RTC values */
|
||||
sft_rtc_set_mode(srtc, RTC_HOUR_MODE_24H);
|
||||
|
||||
sft_rtc_set_enabled(srtc, true);
|
||||
|
||||
if (device_property_read_bool(dev, "rtc,hw-adjustment"))
|
||||
sft_rtc_hw_adjustment(dev, true);
|
||||
|
||||
/*
|
||||
* If rtc time is out of supported range, reset it to the minimum time.
|
||||
* notice that, actual year = 1900 + tm.tm_year
|
||||
* actual month = 1 + tm.tm_mon
|
||||
*/
|
||||
sft_rtc_read_time(dev, &tm);
|
||||
if (tm.tm_year < 101 || tm.tm_year > 199 || tm.tm_mon < 0 || tm.tm_mon > 11 ||
|
||||
tm.tm_mday < 1 || tm.tm_mday > 31 || tm.tm_hour < 0 || tm.tm_hour > 23 ||
|
||||
tm.tm_min < 0 || tm.tm_min > 59 || tm.tm_sec < 0 || tm.tm_sec > 59) {
|
||||
rtc_time64_to_tm(srtc->rtc_dev->range_min, &tm);
|
||||
sft_rtc_set_time(dev, &tm);
|
||||
}
|
||||
|
||||
ret = devm_rtc_register_device(srtc->rtc_dev);
|
||||
if (ret)
|
||||
goto err_disable_wakeup;
|
||||
|
||||
return 0;
|
||||
|
||||
err_disable_wakeup:
|
||||
device_init_wakeup(dev, false);
|
||||
|
||||
err_disable_cal_clk:
|
||||
clk_disable_unprepare(srtc->cal_clk);
|
||||
|
||||
err_disable_pclk:
|
||||
clk_disable_unprepare(srtc->pclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sft_rtc_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sft_rtc *srtc = platform_get_drvdata(pdev);
|
||||
|
||||
sft_rtc_alarm_irq_enable(&pdev->dev, 0);
|
||||
device_init_wakeup(&pdev->dev, 0);
|
||||
|
||||
clk_disable_unprepare(srtc->pclk);
|
||||
clk_disable_unprepare(srtc->cal_clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int sft_rtc_suspend(struct device *dev)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
enable_irq_wake(srtc->rtc_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sft_rtc_resume(struct device *dev)
|
||||
{
|
||||
struct sft_rtc *srtc = dev_get_drvdata(dev);
|
||||
|
||||
if (device_may_wakeup(dev))
|
||||
disable_irq_wake(srtc->rtc_irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(sft_rtc_pm_ops, sft_rtc_suspend, sft_rtc_resume);
|
||||
|
||||
static const struct of_device_id sft_rtc_of_match[] = {
|
||||
{ .compatible = "starfive,jh7110-rtc" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, sft_rtc_of_match);
|
||||
|
||||
static struct platform_driver starfive_rtc_driver = {
|
||||
.driver = {
|
||||
.name = "starfive-rtc",
|
||||
.of_match_table = sft_rtc_of_match,
|
||||
.pm = &sft_rtc_pm_ops,
|
||||
},
|
||||
.probe = sft_rtc_probe,
|
||||
.remove = sft_rtc_remove,
|
||||
};
|
||||
module_platform_driver(starfive_rtc_driver);
|
||||
|
||||
MODULE_AUTHOR("Samin Guo <samin.guo@starfivetech.com>");
|
||||
MODULE_AUTHOR("Hal Feng <hal.feng@starfivetech.com>");
|
||||
MODULE_DESCRIPTION("StarFive RTC driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:starfive-rtc");
|
Loading…
Add table
Reference in a new issue