1/*-
2 * Copyright (c) 2010, Oleksandr Tymoshenko <gonzo@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__FBSDID("$FreeBSD$");
30
31#include <sys/param.h>
32#include <sys/bus.h>
33#include <sys/lock.h>
34#include <sys/time.h>
35#include <sys/clock.h>
36#include <sys/resource.h>
37#include <sys/systm.h>
38#include <sys/rman.h>
39#include <sys/kernel.h>
40#include <sys/module.h>
41
42#include <mips/atheros/pcf2123reg.h>
43
44#include <dev/spibus/spi.h>
45#include "spibus_if.h"
46
47#include "clock_if.h"
48
49#define	YEAR_BASE	1970
50#define	PCF2123_DELAY	50
51
52struct pcf2123_rtc_softc {
53	device_t dev;
54};
55
56static int pcf2123_rtc_probe(device_t dev);
57static int pcf2123_rtc_attach(device_t dev);
58
59static int pcf2123_rtc_gettime(device_t dev, struct timespec *ts);
60static int pcf2123_rtc_settime(device_t dev, struct timespec *ts);
61
62static int
63pcf2123_rtc_probe(device_t dev)
64{
65
66	device_set_desc(dev, "PCF2123 SPI RTC");
67	return (0);
68}
69
70static int
71pcf2123_rtc_attach(device_t dev)
72{
73	struct pcf2123_rtc_softc *sc;
74	struct spi_command cmd;
75	unsigned char rxBuf[3];
76	unsigned char txBuf[3];
77	int err;
78
79	sc = device_get_softc(dev);
80	sc->dev = dev;
81
82	clock_register(dev, 1000000);
83
84	memset(&cmd, 0, sizeof(cmd));
85	memset(rxBuf, 0, sizeof(rxBuf));
86	memset(txBuf, 0, sizeof(txBuf));
87
88	/* Make sure Ctrl1 and Ctrl2 are zeroes */
89	txBuf[0] = PCF2123_WRITE(PCF2123_REG_CTRL1);
90	cmd.rx_cmd = rxBuf;
91	cmd.tx_cmd = txBuf;
92	cmd.rx_cmd_sz = sizeof(rxBuf);
93	cmd.tx_cmd_sz = sizeof(txBuf);
94	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
95	DELAY(PCF2123_DELAY);
96
97	return (0);
98}
99
100static int
101pcf2123_rtc_gettime(device_t dev, struct timespec *ts)
102{
103	struct clocktime ct;
104	struct spi_command cmd;
105	unsigned char rxTimedate[8];
106	unsigned char txTimedate[8];
107	int err;
108
109	memset(&cmd, 0, sizeof(cmd));
110	memset(rxTimedate, 0, sizeof(rxTimedate));
111	memset(txTimedate, 0, sizeof(txTimedate));
112
113	/*
114	 * Counter is stopped when access to time registers is in progress
115	 * So there is no need to stop/start counter
116	 */
117	/* Start reading from seconds */
118	txTimedate[0] = PCF2123_READ(PCF2123_REG_SECONDS);
119	cmd.rx_cmd = rxTimedate;
120	cmd.tx_cmd = txTimedate;
121	cmd.rx_cmd_sz = sizeof(rxTimedate);
122	cmd.tx_cmd_sz = sizeof(txTimedate);
123	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
124	DELAY(PCF2123_DELAY);
125
126	ct.nsec = 0;
127	ct.sec = FROMBCD(rxTimedate[1] & 0x7f);
128	ct.min = FROMBCD(rxTimedate[2] & 0x7f);
129	ct.hour = FROMBCD(rxTimedate[3] & 0x3f);
130
131	ct.dow = FROMBCD(rxTimedate[5] & 0x3f);
132
133	ct.day = FROMBCD(rxTimedate[4] & 0x3f);
134	ct.mon = FROMBCD(rxTimedate[6] & 0x1f);
135	ct.year = YEAR_BASE + FROMBCD(rxTimedate[7]);
136
137	return (clock_ct_to_ts(&ct, ts));
138}
139
140static int
141pcf2123_rtc_settime(device_t dev, struct timespec *ts)
142{
143	struct clocktime ct;
144	struct pcf2123_rtc_softc *sc;
145	struct spi_command cmd;
146	unsigned char rxTimedate[8];
147	unsigned char txTimedate[8];
148	int err;
149
150	sc = device_get_softc(dev);
151
152	/* Resolution: 1 sec */
153	if (ts->tv_nsec >= 500000000)
154		ts->tv_sec++;
155	ts->tv_nsec = 0;
156	clock_ts_to_ct(ts, &ct);
157
158	memset(&cmd, 0, sizeof(cmd));
159	memset(rxTimedate, 0, sizeof(rxTimedate));
160	memset(txTimedate, 0, sizeof(txTimedate));
161
162	/* Start reading from seconds */
163	cmd.rx_cmd = rxTimedate;
164	cmd.tx_cmd = txTimedate;
165	cmd.rx_cmd_sz = sizeof(rxTimedate);
166	cmd.tx_cmd_sz = sizeof(txTimedate);
167
168	/*
169	 * Counter is stopped when access to time registers is in progress
170	 * So there is no need to stop/start counter
171	 */
172	txTimedate[0] = PCF2123_WRITE(PCF2123_REG_SECONDS);
173	txTimedate[1] = TOBCD(ct.sec);
174	txTimedate[2] = TOBCD(ct.min);
175	txTimedate[3] = TOBCD(ct.hour);
176	txTimedate[4] = TOBCD(ct.day);
177	txTimedate[5] = TOBCD(ct.dow);
178	txTimedate[6] = TOBCD(ct.mon);
179	txTimedate[7] = TOBCD(ct.year - YEAR_BASE);
180
181	err = SPIBUS_TRANSFER(device_get_parent(dev), dev, &cmd);
182	DELAY(PCF2123_DELAY);
183
184	return (err);
185}
186
187static device_method_t pcf2123_rtc_methods[] = {
188	DEVMETHOD(device_probe,		pcf2123_rtc_probe),
189	DEVMETHOD(device_attach,	pcf2123_rtc_attach),
190
191	DEVMETHOD(clock_gettime,	pcf2123_rtc_gettime),
192	DEVMETHOD(clock_settime,	pcf2123_rtc_settime),
193
194	{ 0, 0 },
195};
196
197static driver_t pcf2123_rtc_driver = {
198	"rtc",
199	pcf2123_rtc_methods,
200	sizeof(struct pcf2123_rtc_softc),
201};
202static devclass_t pcf2123_rtc_devclass;
203
204DRIVER_MODULE(pcf2123_rtc, spibus, pcf2123_rtc_driver, pcf2123_rtc_devclass, 0, 0);
205