1/*	$NetBSD: rs5c313.c,v 1.9 2010/04/06 15:29:19 nonaka Exp $	*/
2
3/*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: rs5c313.c,v 1.9 2010/04/06 15:29:19 nonaka Exp $");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/device.h>
35#include <sys/kernel.h>
36
37#include <dev/clock_subr.h>
38
39#include <dev/ic/rs5c313reg.h>
40#include <dev/ic/rs5c313var.h>
41
42
43/* todr(9) methods */
44static int rs5c313_todr_gettime_ymdhms(todr_chip_handle_t, struct clock_ymdhms *);
45static int rs5c313_todr_settime_ymdhms(todr_chip_handle_t, struct clock_ymdhms *);
46
47/* sugar for chip access */
48#define rtc_begin(sc)		((*sc->sc_ops->rs5c313_op_begin)(sc))
49#define rtc_ce(sc, onoff)	((*sc->sc_ops->rs5c313_op_ce)(sc, onoff))
50#define rtc_clk(sc, onoff)	((*sc->sc_ops->rs5c313_op_clk)(sc, onoff))
51#define rtc_dir(sc, output)	((*sc->sc_ops->rs5c313_op_dir)(sc, output))
52#define rtc_di(sc)		((*sc->sc_ops->rs5c313_op_read)(sc))
53#define rtc_do(sc, bit)		((*sc->sc_ops->rs5c313_op_write)(sc, bit))
54
55static int rs5c313_init(struct rs5c313_softc *);
56static int rs5c313_read_reg(struct rs5c313_softc *, int);
57static void rs5c313_write_reg(struct rs5c313_softc *, int, int);
58
59
60void
61rs5c313_attach(struct rs5c313_softc *sc)
62{
63	device_t self = sc->sc_dev;
64	const char *model;
65
66	switch (sc->sc_model) {
67	case MODEL_5C313:
68		model = "5C313";
69		sc->sc_ctrl[0] = CTRL_24H;
70		sc->sc_ctrl[1] = CTRL2_NTEST;
71		break;
72
73	case MODEL_5C316:
74		model = "5C316";
75		sc->sc_ctrl[0] = 0;
76		sc->sc_ctrl[1] = CTRL2_24H|CTRL2_NTEST;
77		break;
78
79	default:
80		aprint_error("unknown model (%d)\n", sc->sc_model);
81		return;
82	}
83
84	aprint_naive("\n");
85	aprint_normal(": RICOH %s real time clock\n", model);
86
87	sc->sc_todr.cookie = sc;
88	sc->sc_todr.todr_gettime_ymdhms = rs5c313_todr_gettime_ymdhms;
89	sc->sc_todr.todr_settime_ymdhms = rs5c313_todr_settime_ymdhms;
90
91	if (rs5c313_init(sc) != 0) {
92		aprint_error_dev(self, "init failed\n");
93		return;
94	}
95
96	todr_attach(&sc->sc_todr);
97}
98
99
100static int
101rs5c313_init(struct rs5c313_softc *sc)
102{
103	device_t self = sc->sc_dev;
104	int status = 0;
105	int retry;
106
107	rtc_ce(sc, 0);
108
109	rtc_begin(sc);
110	rtc_ce(sc, 1);
111
112	if ((rs5c313_read_reg(sc, RS5C313_CTRL) & CTRL_XSTP) == 0) {
113		sc->sc_valid = 1;
114		goto done;
115	}
116
117	sc->sc_valid = 0;
118	aprint_error_dev(self, "time not valid\n");
119
120	rs5c313_write_reg(sc, RS5C313_TINT, 0);
121	rs5c313_write_reg(sc, RS5C313_CTRL, (sc->sc_ctrl[0] | CTRL_ADJ));
122
123	for (retry = 1000; retry > 0; --retry) {
124		if (rs5c313_read_reg(sc, RS5C313_CTRL) & CTRL_BSY)
125			delay(1);
126		else
127			break;
128	}
129	if (retry == 0) {
130		status = EIO;
131		goto done;
132	}
133
134	rs5c313_write_reg(sc, RS5C313_CTRL, sc->sc_ctrl[0]);
135	rs5c313_write_reg(sc, RS5C313_CTRL2, sc->sc_ctrl[1]);
136
137  done:
138	rtc_ce(sc, 0);
139	return status;
140}
141
142
143static int
144rs5c313_todr_gettime_ymdhms(todr_chip_handle_t todr, struct clock_ymdhms *dt)
145{
146	struct rs5c313_softc *sc = todr->cookie;
147	int retry;
148	int s;
149
150	/*
151	 * If chip had invalid data on init, don't bother reading
152	 * bogus values, let todr(9) cope.
153	 */
154	if (sc->sc_valid == 0)
155		return EIO;
156
157	s = splhigh();
158
159	rtc_begin(sc);
160	for (retry = 10; retry > 0; --retry) {
161		rtc_ce(sc, 1);
162
163		rs5c313_write_reg(sc, RS5C313_CTRL, sc->sc_ctrl[0]);
164		if ((rs5c313_read_reg(sc, RS5C313_CTRL) & CTRL_BSY) == 0)
165			break;
166
167		rtc_ce(sc, 0);
168		delay(1);
169	}
170	if (retry == 0) {
171		splx(s);
172		return EIO;
173	}
174
175#define RTCGET(x, y)							\
176	do {								\
177		int ones = rs5c313_read_reg(sc, RS5C313_ ## y ## 1);	\
178		int tens = rs5c313_read_reg(sc, RS5C313_ ## y ## 10);	\
179		dt->dt_ ## x = tens * 10 + ones;			\
180	} while (/* CONSTCOND */0)
181
182	RTCGET(sec, SEC);
183	RTCGET(min, MIN);
184	RTCGET(hour, HOUR);
185	RTCGET(day, DAY);
186	RTCGET(mon, MON);
187	RTCGET(year, YEAR);
188#undef	RTCGET
189	dt->dt_wday = rs5c313_read_reg(sc, RS5C313_WDAY);
190
191	rtc_ce(sc, 0);
192	splx(s);
193
194	dt->dt_year = (dt->dt_year % 100) + 1900;
195	if (dt->dt_year < POSIX_BASE_YEAR) {
196		dt->dt_year += 100;
197	}
198
199	return 0;
200}
201
202
203static int
204rs5c313_todr_settime_ymdhms(todr_chip_handle_t todr, struct clock_ymdhms *dt)
205{
206	struct rs5c313_softc *sc = todr->cookie;
207	int retry;
208	int t;
209	int s;
210
211	s = splhigh();
212
213	rtc_begin(sc);
214	for (retry = 10; retry > 0; --retry) {
215		rtc_ce(sc, 1);
216
217		rs5c313_write_reg(sc, RS5C313_CTRL, sc->sc_ctrl[0]);
218		if ((rs5c313_read_reg(sc, RS5C313_CTRL) & CTRL_BSY) == 0)
219			break;
220
221		rtc_ce(sc, 0);
222		delay(1);
223	}
224
225	if (retry == 0) {
226		splx(s);
227		return EIO;
228	}
229
230#define	RTCSET(x, y)							     \
231	do {								     \
232		t = bintobcd(dt->dt_ ## y) & 0xff;				     \
233		rs5c313_write_reg(sc, RS5C313_ ## x ## 1, t & 0x0f);	     \
234		rs5c313_write_reg(sc, RS5C313_ ## x ## 10, (t >> 4) & 0x0f); \
235	} while (/* CONSTCOND */0)
236
237	RTCSET(SEC, sec);
238	RTCSET(MIN, min);
239	RTCSET(HOUR, hour);
240	RTCSET(DAY, day);
241	RTCSET(MON, mon);
242
243#undef	RTCSET
244
245	t = dt->dt_year % 100;
246	t = bintobcd(t);
247	rs5c313_write_reg(sc, RS5C313_YEAR1, t & 0x0f);
248	rs5c313_write_reg(sc, RS5C313_YEAR10, (t >> 4) & 0x0f);
249
250	rs5c313_write_reg(sc, RS5C313_WDAY, dt->dt_wday);
251
252	rtc_ce(sc, 0);
253	splx(s);
254
255	sc->sc_valid = 1;
256	return 0;
257}
258
259
260static int
261rs5c313_read_reg(struct rs5c313_softc *sc, int addr)
262{
263	int data;
264
265	/* output */
266	rtc_dir(sc, 1);
267
268	/* control */
269	rtc_do(sc, 1);		/* ignored */
270	rtc_do(sc, 1);		/* R/#W = 1(READ) */
271	rtc_do(sc, 1);		/* AD = 1 */
272	rtc_do(sc, 0);		/* DT = 0 */
273
274	/* address */
275	rtc_do(sc, addr & 0x8);	/* A3 */
276	rtc_do(sc, addr & 0x4);	/* A2 */
277	rtc_do(sc, addr & 0x2);	/* A1 */
278	rtc_do(sc, addr & 0x1);	/* A0 */
279
280	/* input */
281	rtc_dir(sc, 0);
282
283	/* ignore */
284	(void)rtc_di(sc);
285	(void)rtc_di(sc);
286	(void)rtc_di(sc);
287	(void)rtc_di(sc);
288
289	/* data */
290	data = rtc_di(sc);	/* D3 */
291	data <<= 1;
292	data |= rtc_di(sc);	/* D2 */
293	data <<= 1;
294	data |= rtc_di(sc);	/* D1 */
295	data <<= 1;
296	data |= rtc_di(sc);	/* D0 */
297
298	return data;
299}
300
301
302static void
303rs5c313_write_reg(struct rs5c313_softc *sc, int addr, int data)
304{
305
306	/* output */
307	rtc_dir(sc, 1);
308
309	/* control */
310	rtc_do(sc, 1);		/* ignored */
311	rtc_do(sc, 0);		/* R/#W = 0 (WRITE) */
312	rtc_do(sc, 1);		/* AD = 1 */
313	rtc_do(sc, 0);		/* DT = 0 */
314
315	/* address */
316	rtc_do(sc, addr & 0x8);	/* A3 */
317	rtc_do(sc, addr & 0x4);	/* A2 */
318	rtc_do(sc, addr & 0x2);	/* A1 */
319	rtc_do(sc, addr & 0x1);	/* A0 */
320
321	/* control */
322	rtc_do(sc, 1);		/* ignored */
323	rtc_do(sc, 0);		/* R/#W = 0(WRITE) */
324	rtc_do(sc, 0);		/* AD = 0 */
325	rtc_do(sc, 1);		/* DT = 1 */
326
327	/* data */
328	rtc_do(sc, data & 0x8);	/* D3 */
329	rtc_do(sc, data & 0x4);	/* D2 */
330	rtc_do(sc, data & 0x2);	/* D1 */
331	rtc_do(sc, data & 0x1);	/* D0 */
332}
333