Blame view

drivers/rtc/rtc-rp5c01.c 7.36 KB
09c434b8a   Thomas Gleixner   treewide: Add SPD...
1
  // SPDX-License-Identifier: GPL-2.0-only
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  /*
   *  Ricoh RP5C01 RTC Driver
   *
   *  Copyright 2009 Geert Uytterhoeven
   *
   *  Based on the A3000 TOD code in arch/m68k/amiga/config.c
   *  Copyright (C) 1993 Hamish Macdonald
   */
  
  #include <linux/io.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/platform_device.h>
  #include <linux/rtc.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
16
  #include <linux/slab.h>
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
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
  
  
  enum {
  	RP5C01_1_SECOND		= 0x0,	/* MODE 00 */
  	RP5C01_10_SECOND	= 0x1,	/* MODE 00 */
  	RP5C01_1_MINUTE		= 0x2,	/* MODE 00 and MODE 01 */
  	RP5C01_10_MINUTE	= 0x3,	/* MODE 00 and MODE 01 */
  	RP5C01_1_HOUR		= 0x4,	/* MODE 00 and MODE 01 */
  	RP5C01_10_HOUR		= 0x5,	/* MODE 00 and MODE 01 */
  	RP5C01_DAY_OF_WEEK	= 0x6,	/* MODE 00 and MODE 01 */
  	RP5C01_1_DAY		= 0x7,	/* MODE 00 and MODE 01 */
  	RP5C01_10_DAY		= 0x8,	/* MODE 00 and MODE 01 */
  	RP5C01_1_MONTH		= 0x9,	/* MODE 00 */
  	RP5C01_10_MONTH		= 0xa,	/* MODE 00 */
  	RP5C01_1_YEAR		= 0xb,	/* MODE 00 */
  	RP5C01_10_YEAR		= 0xc,	/* MODE 00 */
  
  	RP5C01_12_24_SELECT	= 0xa,	/* MODE 01 */
  	RP5C01_LEAP_YEAR	= 0xb,	/* MODE 01 */
  
  	RP5C01_MODE		= 0xd,	/* all modes */
  	RP5C01_TEST		= 0xe,	/* all modes */
  	RP5C01_RESET		= 0xf,	/* all modes */
  };
  
  #define RP5C01_12_24_SELECT_12	(0 << 0)
  #define RP5C01_12_24_SELECT_24	(1 << 0)
  
  #define RP5C01_10_HOUR_AM	(0 << 1)
  #define RP5C01_10_HOUR_PM	(1 << 1)
  
  #define RP5C01_MODE_TIMER_EN	(1 << 3)	/* timer enable */
  #define RP5C01_MODE_ALARM_EN	(1 << 2)	/* alarm enable */
  
  #define RP5C01_MODE_MODE_MASK	(3 << 0)
  #define RP5C01_MODE_MODE00	(0 << 0)	/* time */
  #define RP5C01_MODE_MODE01	(1 << 0)	/* alarm, 12h/24h, leap year */
  #define RP5C01_MODE_RAM_BLOCK10	(2 << 0)	/* RAM 4 bits x 13 */
  #define RP5C01_MODE_RAM_BLOCK11	(3 << 0)	/* RAM 4 bits x 13 */
  
  #define RP5C01_RESET_1HZ_PULSE	(1 << 3)
  #define RP5C01_RESET_16HZ_PULSE	(1 << 2)
  #define RP5C01_RESET_SECOND	(1 << 1)	/* reset divider stages for */
  						/* seconds or smaller units */
  #define RP5C01_RESET_ALARM	(1 << 0)	/* reset all alarm registers */
  
  
  struct rp5c01_priv {
  	u32 __iomem *regs;
  	struct rtc_device *rtc;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
67
  	spinlock_t lock;	/* against concurrent RTC/NVRAM access */
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
68
69
70
71
72
73
74
75
76
77
78
  };
  
  static inline unsigned int rp5c01_read(struct rp5c01_priv *priv,
  				       unsigned int reg)
  {
  	return __raw_readl(&priv->regs[reg]) & 0xf;
  }
  
  static inline void rp5c01_write(struct rp5c01_priv *priv, unsigned int val,
  				unsigned int reg)
  {
d8ce1481e   John Stultz   RTC: Fix minor co...
79
  	__raw_writel(val, &priv->regs[reg]);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
  }
  
  static void rp5c01_lock(struct rp5c01_priv *priv)
  {
  	rp5c01_write(priv, RP5C01_MODE_MODE00, RP5C01_MODE);
  }
  
  static void rp5c01_unlock(struct rp5c01_priv *priv)
  {
  	rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
  		     RP5C01_MODE);
  }
  
  static int rp5c01_read_time(struct device *dev, struct rtc_time *tm)
  {
  	struct rp5c01_priv *priv = dev_get_drvdata(dev);
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
96
  	spin_lock_irq(&priv->lock);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
  	rp5c01_lock(priv);
  
  	tm->tm_sec  = rp5c01_read(priv, RP5C01_10_SECOND) * 10 +
  		      rp5c01_read(priv, RP5C01_1_SECOND);
  	tm->tm_min  = rp5c01_read(priv, RP5C01_10_MINUTE) * 10 +
  		      rp5c01_read(priv, RP5C01_1_MINUTE);
  	tm->tm_hour = rp5c01_read(priv, RP5C01_10_HOUR) * 10 +
  		      rp5c01_read(priv, RP5C01_1_HOUR);
  	tm->tm_mday = rp5c01_read(priv, RP5C01_10_DAY) * 10 +
  		      rp5c01_read(priv, RP5C01_1_DAY);
  	tm->tm_wday = rp5c01_read(priv, RP5C01_DAY_OF_WEEK);
  	tm->tm_mon  = rp5c01_read(priv, RP5C01_10_MONTH) * 10 +
  		      rp5c01_read(priv, RP5C01_1_MONTH) - 1;
  	tm->tm_year = rp5c01_read(priv, RP5C01_10_YEAR) * 10 +
  		      rp5c01_read(priv, RP5C01_1_YEAR);
  	if (tm->tm_year <= 69)
  		tm->tm_year += 100;
  
  	rp5c01_unlock(priv);
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
116
  	spin_unlock_irq(&priv->lock);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
117

22652ba72   Alexandre Belloni   rtc: stop validat...
118
  	return 0;
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
119
120
121
122
123
  }
  
  static int rp5c01_set_time(struct device *dev, struct rtc_time *tm)
  {
  	struct rp5c01_priv *priv = dev_get_drvdata(dev);
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
124
  	spin_lock_irq(&priv->lock);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  	rp5c01_lock(priv);
  
  	rp5c01_write(priv, tm->tm_sec / 10, RP5C01_10_SECOND);
  	rp5c01_write(priv, tm->tm_sec % 10, RP5C01_1_SECOND);
  	rp5c01_write(priv, tm->tm_min / 10, RP5C01_10_MINUTE);
  	rp5c01_write(priv, tm->tm_min % 10, RP5C01_1_MINUTE);
  	rp5c01_write(priv, tm->tm_hour / 10, RP5C01_10_HOUR);
  	rp5c01_write(priv, tm->tm_hour % 10, RP5C01_1_HOUR);
  	rp5c01_write(priv, tm->tm_mday / 10, RP5C01_10_DAY);
  	rp5c01_write(priv, tm->tm_mday % 10, RP5C01_1_DAY);
  	if (tm->tm_wday != -1)
  		rp5c01_write(priv, tm->tm_wday, RP5C01_DAY_OF_WEEK);
  	rp5c01_write(priv, (tm->tm_mon + 1) / 10, RP5C01_10_MONTH);
  	rp5c01_write(priv, (tm->tm_mon + 1) % 10, RP5C01_1_MONTH);
  	if (tm->tm_year >= 100)
  		tm->tm_year -= 100;
  	rp5c01_write(priv, tm->tm_year / 10, RP5C01_10_YEAR);
  	rp5c01_write(priv, tm->tm_year % 10, RP5C01_1_YEAR);
  
  	rp5c01_unlock(priv);
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
145
  	spin_unlock_irq(&priv->lock);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
146
147
148
149
150
151
152
  	return 0;
  }
  
  static const struct rtc_class_ops rp5c01_rtc_ops = {
  	.read_time	= rp5c01_read_time,
  	.set_time	= rp5c01_set_time,
  };
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
153
154
155
156
157
158
  
  /*
   * The NVRAM is organized as 2 blocks of 13 nibbles of 4 bits.
   * We provide access to them like AmigaOS does: the high nibble of each 8-bit
   * byte is stored in BLOCK10, the low nibble in BLOCK11.
   */
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
159
160
  static int rp5c01_nvram_read(void *_priv, unsigned int pos, void *val,
  			     size_t bytes)
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
161
  {
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
162
163
  	struct rp5c01_priv *priv = _priv;
  	u8 *buf = val;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
164
165
  
  	spin_lock_irq(&priv->lock);
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
166
  	for (; bytes; bytes--) {
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
  		u8 data;
  
  		rp5c01_write(priv,
  			     RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10,
  			     RP5C01_MODE);
  		data = rp5c01_read(priv, pos) << 4;
  		rp5c01_write(priv,
  			     RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11,
  			     RP5C01_MODE);
  		data |= rp5c01_read(priv, pos++);
  		rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
  			     RP5C01_MODE);
  		*buf++ = data;
  	}
  
  	spin_unlock_irq(&priv->lock);
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
183
  	return 0;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
184
  }
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
185
186
  static int rp5c01_nvram_write(void *_priv, unsigned int pos, void *val,
  			      size_t bytes)
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
187
  {
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
188
189
  	struct rp5c01_priv *priv = _priv;
  	u8 *buf = val;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
190
191
  
  	spin_lock_irq(&priv->lock);
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
192
  	for (; bytes; bytes--) {
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
  		u8 data = *buf++;
  
  		rp5c01_write(priv,
  			     RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10,
  			     RP5C01_MODE);
  		rp5c01_write(priv, data >> 4, pos);
  		rp5c01_write(priv,
  			     RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11,
  			     RP5C01_MODE);
  		rp5c01_write(priv, data & 0xf, pos++);
  		rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
  			     RP5C01_MODE);
  	}
  
  	spin_unlock_irq(&priv->lock);
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
208
  	return 0;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
209
  }
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
210
211
212
213
214
215
  static int __init rp5c01_rtc_probe(struct platform_device *dev)
  {
  	struct resource *res;
  	struct rp5c01_priv *priv;
  	struct rtc_device *rtc;
  	int error;
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
216
217
218
219
220
221
222
223
  	struct nvmem_config nvmem_cfg = {
  		.name = "rp5c01_nvram",
  		.word_size = 1,
  		.stride = 1,
  		.size = RP5C01_MODE,
  		.reg_read = rp5c01_nvram_read,
  		.reg_write = rp5c01_nvram_write,
  	};
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
224
225
226
227
  
  	res = platform_get_resource(dev, IORESOURCE_MEM, 0);
  	if (!res)
  		return -ENODEV;
ddb396f12   Jingoo Han   rtc: rtc-rp5c01: ...
228
  	priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
229
230
  	if (!priv)
  		return -ENOMEM;
ddb396f12   Jingoo Han   rtc: rtc-rp5c01: ...
231
232
233
  	priv->regs = devm_ioremap(&dev->dev, res->start, resource_size(res));
  	if (!priv->regs)
  		return -ENOMEM;
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
234

22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
235
  	spin_lock_init(&priv->lock);
130107b27   John Stultz   rtc: rp5c01: Init...
236
  	platform_set_drvdata(dev, priv);
bcdd55926   Alexandre Belloni   rtc: rp5c01: fix ...
237
  	rtc = devm_rtc_allocate_device(&dev->dev);
a81de2076   Jingoo Han   rtc: rtc-rp5c01: ...
238
239
  	if (IS_ERR(rtc))
  		return PTR_ERR(rtc);
bcdd55926   Alexandre Belloni   rtc: rp5c01: fix ...
240
241
  
  	rtc->ops = &rp5c01_rtc_ops;
7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
242
  	rtc->nvram_old_abi = true;
bcdd55926   Alexandre Belloni   rtc: rp5c01: fix ...
243

4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
244
  	priv->rtc = rtc;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
245

7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
246
247
  	nvmem_cfg.priv = priv;
  	error = rtc_nvmem_register(rtc, &nvmem_cfg);
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
248
  	if (error)
a81de2076   Jingoo Han   rtc: rtc-rp5c01: ...
249
  		return error;
22e3d6314   Geert Uytterhoeven   rtc: rp5c01: add ...
250

7335fb9be   Alexandre Belloni   rtc: rp5c01: use ...
251
  	return rtc_register_device(rtc);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
252
253
254
255
256
  }
  
  static struct platform_driver rp5c01_rtc_driver = {
  	.driver	= {
  		.name	= "rtc-rp5c01",
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
257
  	},
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
258
  };
9842eaff0   Jingoo Han   rtc: rtc-rp5c01: ...
259
  module_platform_driver_probe(rp5c01_rtc_driver, rp5c01_rtc_probe);
4f672ce29   Geert Uytterhoeven   rtc: Add an RTC d...
260
261
262
263
264
  
  MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
  MODULE_LICENSE("GPL");
  MODULE_DESCRIPTION("Ricoh RP5C01 RTC driver");
  MODULE_ALIAS("platform:rtc-rp5c01");