summaryrefslogtreecommitdiff
path: root/drivers/rtc/zynqmp_rtc.c
blob: 15122a0483825263ac06f01e441de3739874c75b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2021, Xilinx, Inc.
 */

#define LOG_CATEGORY UCLASS_RTC

#include <dm.h>
#include <rtc.h>
#include <asm/io.h>

/* RTC Registers */
#define RTC_SET_TM_WR		0x00
#define RTC_SET_TM_RD		0x04
#define RTC_CALIB_WR		0x08
#define RTC_CUR_TM		0x10
#define RTC_INT_STS		0x20
#define RTC_CTRL		0x40

#define RTC_INT_SEC		BIT(0)
#define RTC_BATT_EN		BIT(31)
#define RTC_CALIB_DEF		0x198233
#define RTC_CALIB_MASK		0x1FFFFF

struct zynqmp_rtc_priv {
	fdt_addr_t	base;
	unsigned int	calibval;
};

static int zynqmp_rtc_get(struct udevice *dev, struct rtc_time *tm)
{
	struct zynqmp_rtc_priv *priv = dev_get_priv(dev);
	u32 status;
	unsigned long read_time;

	status = readl(priv->base + RTC_INT_STS);

	if (status & RTC_INT_SEC) {
		/*
		 * RTC has updated the CURRENT_TIME with the time written into
		 * SET_TIME_WRITE register.
		 */
		read_time = readl(priv->base + RTC_CUR_TM);
	} else {
		/*
		 * Time written in SET_TIME_WRITE has not yet updated into
		 * the seconds read register, so read the time from the
		 * SET_TIME_WRITE instead of CURRENT_TIME register.
		 * Since we add +1 sec while writing, we need to -1 sec while
		 * reading.
		 */
		read_time = readl(priv->base + RTC_SET_TM_RD) - 1;
	}

	rtc_to_tm(read_time, tm);

	return 0;
}

static int zynqmp_rtc_set(struct udevice *dev, const struct rtc_time *tm)
{
	struct zynqmp_rtc_priv *priv = dev_get_priv(dev);
	unsigned long new_time = 0;

	if (tm)
		/*
		 * The value written will be updated after 1 sec into the
		 * seconds read register, so we need to program time +1 sec
		 * to get the correct time on read.
		 */
		new_time = rtc_mktime(tm) + 1;

	/*
	 * Writing into calibration register will clear the Tick Counter and
	 * force the next second to be signaled exactly in 1 second period
	 */
	priv->calibval &= RTC_CALIB_MASK;
	writel(priv->calibval, (priv->base + RTC_CALIB_WR));

	writel(new_time, priv->base + RTC_SET_TM_WR);

	/*
	 * Clear the rtc interrupt status register after setting the
	 * time. During a read_time function, the code should read the
	 * RTC_INT_STATUS register and if bit 0 is still 0, it means
	 * that one second has not elapsed yet since RTC was set and
	 * the current time should be read from SET_TIME_READ register;
	 * otherwise, CURRENT_TIME register is read to report the time
	 */
	writel(RTC_INT_SEC, priv->base + RTC_INT_STS);

	return 0;
}

static int zynqmp_rtc_reset(struct udevice *dev)
{
	return zynqmp_rtc_set(dev, NULL);
}

static int zynqmp_rtc_init(struct udevice *dev)
{
	struct zynqmp_rtc_priv *priv = dev_get_priv(dev);
	u32 rtc_ctrl;

	/* Enable RTC switch to battery when VCC_PSAUX is not available */
	rtc_ctrl = readl(priv->base + RTC_CTRL);
	rtc_ctrl |= RTC_BATT_EN;
	writel(rtc_ctrl, priv->base + RTC_CTRL);

	/*
	 * Based on crystal freq of 33.330 KHz
	 * set the seconds counter and enable, set fractions counter
	 * to default value suggested as per design spec
	 * to correct RTC delay in frequency over period of time.
	 */
	priv->calibval &= RTC_CALIB_MASK;
	writel(priv->calibval, (priv->base + RTC_CALIB_WR));

	return 0;
}

static int zynqmp_rtc_probe(struct udevice *dev)
{
	struct zynqmp_rtc_priv *priv = dev_get_priv(dev);
	int ret;

	priv->base = dev_read_addr(dev);
	if (priv->base == FDT_ADDR_T_NONE)
		return -EINVAL;

	priv->calibval = dev_read_u32_default(dev, "calibration",
					      RTC_CALIB_DEF);

	ret = zynqmp_rtc_init(dev);

	return ret;
}

static const struct rtc_ops zynqmp_rtc_ops = {
	.get = zynqmp_rtc_get,
	.set = zynqmp_rtc_set,
	.reset = zynqmp_rtc_reset,
};

static const struct udevice_id zynqmp_rtc_ids[] = {
	{ .compatible = "xlnx,zynqmp-rtc" },
	{ }
};

U_BOOT_DRIVER(rtc_zynqmp) = {
	.name = "rtc-zynqmp",
	.id = UCLASS_RTC,
	.probe = zynqmp_rtc_probe,
	.of_match = zynqmp_rtc_ids,
	.ops = &zynqmp_rtc_ops,
	.priv_auto = sizeof(struct zynqmp_rtc_priv),
};