f1c160fe7d37e55a566a782498928a90431baed6
[sfrench/cifs-2.6.git] / drivers / rtc / rtc-rp5c01.c
1 /*
2  *  Ricoh RP5C01 RTC Driver
3  *
4  *  Copyright 2009 Geert Uytterhoeven
5  *
6  *  Based on the A3000 TOD code in arch/m68k/amiga/config.c
7  *  Copyright (C) 1993 Hamish Macdonald
8  */
9
10 #include <linux/io.h>
11 #include <linux/kernel.h>
12 #include <linux/module.h>
13 #include <linux/platform_device.h>
14 #include <linux/rtc.h>
15 #include <linux/slab.h>
16
17
18 enum {
19         RP5C01_1_SECOND         = 0x0,  /* MODE 00 */
20         RP5C01_10_SECOND        = 0x1,  /* MODE 00 */
21         RP5C01_1_MINUTE         = 0x2,  /* MODE 00 and MODE 01 */
22         RP5C01_10_MINUTE        = 0x3,  /* MODE 00 and MODE 01 */
23         RP5C01_1_HOUR           = 0x4,  /* MODE 00 and MODE 01 */
24         RP5C01_10_HOUR          = 0x5,  /* MODE 00 and MODE 01 */
25         RP5C01_DAY_OF_WEEK      = 0x6,  /* MODE 00 and MODE 01 */
26         RP5C01_1_DAY            = 0x7,  /* MODE 00 and MODE 01 */
27         RP5C01_10_DAY           = 0x8,  /* MODE 00 and MODE 01 */
28         RP5C01_1_MONTH          = 0x9,  /* MODE 00 */
29         RP5C01_10_MONTH         = 0xa,  /* MODE 00 */
30         RP5C01_1_YEAR           = 0xb,  /* MODE 00 */
31         RP5C01_10_YEAR          = 0xc,  /* MODE 00 */
32
33         RP5C01_12_24_SELECT     = 0xa,  /* MODE 01 */
34         RP5C01_LEAP_YEAR        = 0xb,  /* MODE 01 */
35
36         RP5C01_MODE             = 0xd,  /* all modes */
37         RP5C01_TEST             = 0xe,  /* all modes */
38         RP5C01_RESET            = 0xf,  /* all modes */
39 };
40
41 #define RP5C01_12_24_SELECT_12  (0 << 0)
42 #define RP5C01_12_24_SELECT_24  (1 << 0)
43
44 #define RP5C01_10_HOUR_AM       (0 << 1)
45 #define RP5C01_10_HOUR_PM       (1 << 1)
46
47 #define RP5C01_MODE_TIMER_EN    (1 << 3)        /* timer enable */
48 #define RP5C01_MODE_ALARM_EN    (1 << 2)        /* alarm enable */
49
50 #define RP5C01_MODE_MODE_MASK   (3 << 0)
51 #define RP5C01_MODE_MODE00      (0 << 0)        /* time */
52 #define RP5C01_MODE_MODE01      (1 << 0)        /* alarm, 12h/24h, leap year */
53 #define RP5C01_MODE_RAM_BLOCK10 (2 << 0)        /* RAM 4 bits x 13 */
54 #define RP5C01_MODE_RAM_BLOCK11 (3 << 0)        /* RAM 4 bits x 13 */
55
56 #define RP5C01_RESET_1HZ_PULSE  (1 << 3)
57 #define RP5C01_RESET_16HZ_PULSE (1 << 2)
58 #define RP5C01_RESET_SECOND     (1 << 1)        /* reset divider stages for */
59                                                 /* seconds or smaller units */
60 #define RP5C01_RESET_ALARM      (1 << 0)        /* reset all alarm registers */
61
62
63 struct rp5c01_priv {
64         u32 __iomem *regs;
65         struct rtc_device *rtc;
66         spinlock_t lock;        /* against concurrent RTC/NVRAM access */
67 };
68
69 static inline unsigned int rp5c01_read(struct rp5c01_priv *priv,
70                                        unsigned int reg)
71 {
72         return __raw_readl(&priv->regs[reg]) & 0xf;
73 }
74
75 static inline void rp5c01_write(struct rp5c01_priv *priv, unsigned int val,
76                                 unsigned int reg)
77 {
78         __raw_writel(val, &priv->regs[reg]);
79 }
80
81 static void rp5c01_lock(struct rp5c01_priv *priv)
82 {
83         rp5c01_write(priv, RP5C01_MODE_MODE00, RP5C01_MODE);
84 }
85
86 static void rp5c01_unlock(struct rp5c01_priv *priv)
87 {
88         rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
89                      RP5C01_MODE);
90 }
91
92 static int rp5c01_read_time(struct device *dev, struct rtc_time *tm)
93 {
94         struct rp5c01_priv *priv = dev_get_drvdata(dev);
95
96         spin_lock_irq(&priv->lock);
97         rp5c01_lock(priv);
98
99         tm->tm_sec  = rp5c01_read(priv, RP5C01_10_SECOND) * 10 +
100                       rp5c01_read(priv, RP5C01_1_SECOND);
101         tm->tm_min  = rp5c01_read(priv, RP5C01_10_MINUTE) * 10 +
102                       rp5c01_read(priv, RP5C01_1_MINUTE);
103         tm->tm_hour = rp5c01_read(priv, RP5C01_10_HOUR) * 10 +
104                       rp5c01_read(priv, RP5C01_1_HOUR);
105         tm->tm_mday = rp5c01_read(priv, RP5C01_10_DAY) * 10 +
106                       rp5c01_read(priv, RP5C01_1_DAY);
107         tm->tm_wday = rp5c01_read(priv, RP5C01_DAY_OF_WEEK);
108         tm->tm_mon  = rp5c01_read(priv, RP5C01_10_MONTH) * 10 +
109                       rp5c01_read(priv, RP5C01_1_MONTH) - 1;
110         tm->tm_year = rp5c01_read(priv, RP5C01_10_YEAR) * 10 +
111                       rp5c01_read(priv, RP5C01_1_YEAR);
112         if (tm->tm_year <= 69)
113                 tm->tm_year += 100;
114
115         rp5c01_unlock(priv);
116         spin_unlock_irq(&priv->lock);
117
118         return 0;
119 }
120
121 static int rp5c01_set_time(struct device *dev, struct rtc_time *tm)
122 {
123         struct rp5c01_priv *priv = dev_get_drvdata(dev);
124
125         spin_lock_irq(&priv->lock);
126         rp5c01_lock(priv);
127
128         rp5c01_write(priv, tm->tm_sec / 10, RP5C01_10_SECOND);
129         rp5c01_write(priv, tm->tm_sec % 10, RP5C01_1_SECOND);
130         rp5c01_write(priv, tm->tm_min / 10, RP5C01_10_MINUTE);
131         rp5c01_write(priv, tm->tm_min % 10, RP5C01_1_MINUTE);
132         rp5c01_write(priv, tm->tm_hour / 10, RP5C01_10_HOUR);
133         rp5c01_write(priv, tm->tm_hour % 10, RP5C01_1_HOUR);
134         rp5c01_write(priv, tm->tm_mday / 10, RP5C01_10_DAY);
135         rp5c01_write(priv, tm->tm_mday % 10, RP5C01_1_DAY);
136         if (tm->tm_wday != -1)
137                 rp5c01_write(priv, tm->tm_wday, RP5C01_DAY_OF_WEEK);
138         rp5c01_write(priv, (tm->tm_mon + 1) / 10, RP5C01_10_MONTH);
139         rp5c01_write(priv, (tm->tm_mon + 1) % 10, RP5C01_1_MONTH);
140         if (tm->tm_year >= 100)
141                 tm->tm_year -= 100;
142         rp5c01_write(priv, tm->tm_year / 10, RP5C01_10_YEAR);
143         rp5c01_write(priv, tm->tm_year % 10, RP5C01_1_YEAR);
144
145         rp5c01_unlock(priv);
146         spin_unlock_irq(&priv->lock);
147         return 0;
148 }
149
150 static const struct rtc_class_ops rp5c01_rtc_ops = {
151         .read_time      = rp5c01_read_time,
152         .set_time       = rp5c01_set_time,
153 };
154
155
156 /*
157  * The NVRAM is organized as 2 blocks of 13 nibbles of 4 bits.
158  * We provide access to them like AmigaOS does: the high nibble of each 8-bit
159  * byte is stored in BLOCK10, the low nibble in BLOCK11.
160  */
161
162 static int rp5c01_nvram_read(void *_priv, unsigned int pos, void *val,
163                              size_t bytes)
164 {
165         struct rp5c01_priv *priv = _priv;
166         u8 *buf = val;
167
168         spin_lock_irq(&priv->lock);
169
170         for (; bytes; bytes--) {
171                 u8 data;
172
173                 rp5c01_write(priv,
174                              RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10,
175                              RP5C01_MODE);
176                 data = rp5c01_read(priv, pos) << 4;
177                 rp5c01_write(priv,
178                              RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11,
179                              RP5C01_MODE);
180                 data |= rp5c01_read(priv, pos++);
181                 rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
182                              RP5C01_MODE);
183                 *buf++ = data;
184         }
185
186         spin_unlock_irq(&priv->lock);
187         return 0;
188 }
189
190 static int rp5c01_nvram_write(void *_priv, unsigned int pos, void *val,
191                               size_t bytes)
192 {
193         struct rp5c01_priv *priv = _priv;
194         u8 *buf = val;
195
196         spin_lock_irq(&priv->lock);
197
198         for (; bytes; bytes--) {
199                 u8 data = *buf++;
200
201                 rp5c01_write(priv,
202                              RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK10,
203                              RP5C01_MODE);
204                 rp5c01_write(priv, data >> 4, pos);
205                 rp5c01_write(priv,
206                              RP5C01_MODE_TIMER_EN | RP5C01_MODE_RAM_BLOCK11,
207                              RP5C01_MODE);
208                 rp5c01_write(priv, data & 0xf, pos++);
209                 rp5c01_write(priv, RP5C01_MODE_TIMER_EN | RP5C01_MODE_MODE01,
210                              RP5C01_MODE);
211         }
212
213         spin_unlock_irq(&priv->lock);
214         return 0;
215 }
216
217 static int __init rp5c01_rtc_probe(struct platform_device *dev)
218 {
219         struct resource *res;
220         struct rp5c01_priv *priv;
221         struct rtc_device *rtc;
222         int error;
223         struct nvmem_config nvmem_cfg = {
224                 .name = "rp5c01_nvram",
225                 .word_size = 1,
226                 .stride = 1,
227                 .size = RP5C01_MODE,
228                 .reg_read = rp5c01_nvram_read,
229                 .reg_write = rp5c01_nvram_write,
230         };
231
232         res = platform_get_resource(dev, IORESOURCE_MEM, 0);
233         if (!res)
234                 return -ENODEV;
235
236         priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL);
237         if (!priv)
238                 return -ENOMEM;
239
240         priv->regs = devm_ioremap(&dev->dev, res->start, resource_size(res));
241         if (!priv->regs)
242                 return -ENOMEM;
243
244         spin_lock_init(&priv->lock);
245
246         platform_set_drvdata(dev, priv);
247
248         rtc = devm_rtc_allocate_device(&dev->dev);
249         if (IS_ERR(rtc))
250                 return PTR_ERR(rtc);
251
252         rtc->ops = &rp5c01_rtc_ops;
253         rtc->nvram_old_abi = true;
254
255         priv->rtc = rtc;
256
257         nvmem_cfg.priv = priv;
258         error = rtc_nvmem_register(rtc, &nvmem_cfg);
259         if (error)
260                 return error;
261
262         return rtc_register_device(rtc);
263 }
264
265 static struct platform_driver rp5c01_rtc_driver = {
266         .driver = {
267                 .name   = "rtc-rp5c01",
268         },
269 };
270
271 module_platform_driver_probe(rp5c01_rtc_driver, rp5c01_rtc_probe);
272
273 MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
274 MODULE_LICENSE("GPL");
275 MODULE_DESCRIPTION("Ricoh RP5C01 RTC driver");
276 MODULE_ALIAS("platform:rtc-rp5c01");