summaryrefslogtreecommitdiff
path: root/drivers/rtc/rtc-msc313.c
blob: 8d7737e0e2e02cfd303187ade872922cc3993458 (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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Real time clocks driver for MStar/SigmaStar ARMv7 SoCs.
 * Based on "Real Time Clock driver for msb252x." that was contained
 * in various MStar kernels.
 *
 * (C) 2019 Daniel Palmer
 * (C) 2021 Romain Perier
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>

/* Registers */
#define REG_RTC_CTRL		0x00
#define REG_RTC_FREQ_CW_L	0x04
#define REG_RTC_FREQ_CW_H	0x08
#define REG_RTC_LOAD_VAL_L	0x0C
#define REG_RTC_LOAD_VAL_H	0x10
#define REG_RTC_MATCH_VAL_L	0x14
#define REG_RTC_MATCH_VAL_H	0x18
#define REG_RTC_STATUS_INT	0x1C
#define REG_RTC_CNT_VAL_L	0x20
#define REG_RTC_CNT_VAL_H	0x24

/* Control bits for REG_RTC_CTRL */
#define SOFT_RSTZ_BIT		BIT(0)
#define CNT_EN_BIT		BIT(1)
#define WRAP_EN_BIT		BIT(2)
#define LOAD_EN_BIT		BIT(3)
#define READ_EN_BIT		BIT(4)
#define INT_MASK_BIT		BIT(5)
#define INT_FORCE_BIT		BIT(6)
#define INT_CLEAR_BIT		BIT(7)

/* Control bits for REG_RTC_STATUS_INT */
#define RAW_INT_BIT		BIT(0)
#define ALM_INT_BIT		BIT(1)

struct msc313_rtc {
	struct rtc_device *rtc_dev;
	void __iomem *rtc_base;
};

static int msc313_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev);
	unsigned long seconds;

	seconds = readw(priv->rtc_base + REG_RTC_MATCH_VAL_L)
			| ((unsigned long)readw(priv->rtc_base + REG_RTC_MATCH_VAL_H) << 16);

	rtc_time64_to_tm(seconds, &alarm->time);

	if (!(readw(priv->rtc_base + REG_RTC_CTRL) & INT_MASK_BIT))
		alarm->enabled = 1;

	return 0;
}

static int msc313_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev);
	u16 reg;

	reg = readw(priv->rtc_base + REG_RTC_CTRL);
	if (enabled)
		reg &= ~INT_MASK_BIT;
	else
		reg |= INT_MASK_BIT;
	writew(reg, priv->rtc_base + REG_RTC_CTRL);
	return 0;
}

static int msc313_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev);
	unsigned long seconds;

	seconds = rtc_tm_to_time64(&alarm->time);
	writew((seconds & 0xFFFF), priv->rtc_base + REG_RTC_MATCH_VAL_L);
	writew((seconds >> 16) & 0xFFFF, priv->rtc_base + REG_RTC_MATCH_VAL_H);

	msc313_rtc_alarm_irq_enable(dev, alarm->enabled);

	return 0;
}

static bool msc313_rtc_get_enabled(struct msc313_rtc *priv)
{
	return readw(priv->rtc_base + REG_RTC_CTRL) & CNT_EN_BIT;
}

static void msc313_rtc_set_enabled(struct msc313_rtc *priv)
{
	u16 reg;

	reg = readw(priv->rtc_base + REG_RTC_CTRL);
	reg |= CNT_EN_BIT;
	writew(reg, priv->rtc_base + REG_RTC_CTRL);
}

static int msc313_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev);
	u32 seconds;
	u16 reg;

	if (!msc313_rtc_get_enabled(priv))
		return -EINVAL;

	reg = readw(priv->rtc_base + REG_RTC_CTRL);
	writew(reg | READ_EN_BIT, priv->rtc_base + REG_RTC_CTRL);

	/* Wait for HW latch done */
	while (readw(priv->rtc_base + REG_RTC_CTRL) & READ_EN_BIT)
		udelay(1);

	seconds = readw(priv->rtc_base + REG_RTC_CNT_VAL_L)
			| ((unsigned long)readw(priv->rtc_base + REG_RTC_CNT_VAL_H) << 16);

	rtc_time64_to_tm(seconds, tm);

	return 0;
}

static int msc313_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev);
	unsigned long seconds;
	u16 reg;

	seconds = rtc_tm_to_time64(tm);
	writew(seconds & 0xFFFF, priv->rtc_base + REG_RTC_LOAD_VAL_L);
	writew((seconds >> 16) & 0xFFFF, priv->rtc_base + REG_RTC_LOAD_VAL_H);

	/* Enable load for loading value into internal RTC counter */
	reg = readw(priv->rtc_base + REG_RTC_CTRL);
	writew(reg | LOAD_EN_BIT, priv->rtc_base + REG_RTC_CTRL);

	/* Wait for HW latch done */
	while (readw(priv->rtc_base + REG_RTC_CTRL) & LOAD_EN_BIT)
		udelay(1);
	msc313_rtc_set_enabled(priv);
	return 0;
}

static const struct rtc_class_ops msc313_rtc_ops = {
	.read_time = msc313_rtc_read_time,
	.set_time = msc313_rtc_set_time,
	.read_alarm = msc313_rtc_read_alarm,
	.set_alarm = msc313_rtc_set_alarm,
	.alarm_irq_enable = msc313_rtc_alarm_irq_enable,
};

static irqreturn_t msc313_rtc_interrupt(s32 irq, void *dev_id)
{
	struct msc313_rtc *priv = dev_get_drvdata(dev_id);
	u16 reg;

	reg = readw(priv->rtc_base + REG_RTC_STATUS_INT);
	if (!(reg & ALM_INT_BIT))
		return IRQ_NONE;

	reg = readw(priv->rtc_base + REG_RTC_CTRL);
	reg |= INT_CLEAR_BIT;
	reg &= ~INT_FORCE_BIT;
	writew(reg, priv->rtc_base + REG_RTC_CTRL);

	rtc_update_irq(priv->rtc_dev, 1, RTC_IRQF | RTC_AF);

	return IRQ_HANDLED;
}

static int msc313_rtc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct msc313_rtc *priv;
	unsigned long rate;
	struct clk *clk;
	int ret;
	int irq;

	priv = devm_kzalloc(&pdev->dev, sizeof(struct msc313_rtc), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->rtc_base = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(priv->rtc_base))
		return PTR_ERR(priv->rtc_base);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0)
		return -EINVAL;

	priv->rtc_dev = devm_rtc_allocate_device(dev);
	if (IS_ERR(priv->rtc_dev))
		return PTR_ERR(priv->rtc_dev);

	priv->rtc_dev->ops = &msc313_rtc_ops;
	priv->rtc_dev->range_max = U32_MAX;

	ret = devm_request_irq(dev, irq, msc313_rtc_interrupt, IRQF_SHARED,
			       dev_name(&pdev->dev), &pdev->dev);
	if (ret) {
		dev_err(dev, "Could not request IRQ\n");
		return ret;
	}

	clk = devm_clk_get_enabled(dev, NULL);
	if (IS_ERR(clk)) {
		dev_err(dev, "No input reference clock\n");
		return PTR_ERR(clk);
	}

	rate = clk_get_rate(clk);
	writew(rate & 0xFFFF, priv->rtc_base + REG_RTC_FREQ_CW_L);
	writew((rate >> 16) & 0xFFFF, priv->rtc_base + REG_RTC_FREQ_CW_H);

	platform_set_drvdata(pdev, priv);

	return devm_rtc_register_device(priv->rtc_dev);
}

static const struct of_device_id msc313_rtc_of_match_table[] = {
	{ .compatible = "mstar,msc313-rtc" },
	{ }
};
MODULE_DEVICE_TABLE(of, msc313_rtc_of_match_table);

static struct platform_driver msc313_rtc_driver = {
	.probe = msc313_rtc_probe,
	.driver = {
		.name = "msc313-rtc",
		.of_match_table = msc313_rtc_of_match_table,
	},
};

module_platform_driver(msc313_rtc_driver);

MODULE_AUTHOR("Daniel Palmer <daniel@thingy.jp>");
MODULE_AUTHOR("Romain Perier <romain.perier@gmail.com>");
MODULE_DESCRIPTION("MStar RTC Driver");
MODULE_LICENSE("GPL v2");