diff options
author | Atsushi Nemoto <anemo@mba.ocn.ne.jp> | 2006-06-28 07:26:47 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-06-28 17:59:04 -0400 |
commit | e0ac4761fa52acda90f9f53819c81474b511e3af (patch) | |
tree | 72819aa9b075cd92f228fab57d9cbc05261acb2f /drivers/rtc | |
parent | 84860f9979804cfd97638ce0ec9d583daf338e0d (diff) |
[PATCH] RTC: add rtc-rs5c348 driver
Add an SPI driver for the Ricoh RS5C348 RTC chip.
Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp>
Acked-by: Alessandro Zummo <a.zummo@towertech.it>
Cc: David Brownell <david-b@pacbell.net>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-rs5c348.c | 246 |
3 files changed, 257 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index bccff400b198..f2fc81a9074d 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -162,6 +162,16 @@ config RTC_DRV_PCF8583 | |||
162 | This driver can also be built as a module. If so, the module | 162 | This driver can also be built as a module. If so, the module |
163 | will be called rtc-pcf8583. | 163 | will be called rtc-pcf8583. |
164 | 164 | ||
165 | config RTC_DRV_RS5C348 | ||
166 | tristate "Ricoh RS5C348A/B" | ||
167 | depends on RTC_CLASS && SPI | ||
168 | help | ||
169 | If you say yes here you get support for the | ||
170 | Ricoh RS5C348A and RS5C348B RTC chips. | ||
171 | |||
172 | This driver can also be built as a module. If so, the module | ||
173 | will be called rtc-rs5c348. | ||
174 | |||
165 | config RTC_DRV_RS5C372 | 175 | config RTC_DRV_RS5C372 |
166 | tristate "Ricoh RS5C372A/B" | 176 | tristate "Ricoh RS5C372A/B" |
167 | depends on RTC_CLASS && I2C | 177 | depends on RTC_CLASS && I2C |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 900d210dd1a2..da5e38774e13 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -19,6 +19,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o | |||
19 | obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o | 19 | obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o |
20 | obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o | 20 | obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o |
21 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o | 21 | obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o |
22 | obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o | ||
22 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o | 23 | obj-$(CONFIG_RTC_DRV_M48T86) += rtc-m48t86.o |
23 | obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o | 24 | obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o |
24 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o | 25 | obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o |
diff --git a/drivers/rtc/rtc-rs5c348.c b/drivers/rtc/rtc-rs5c348.c new file mode 100644 index 000000000000..0964d1dba925 --- /dev/null +++ b/drivers/rtc/rtc-rs5c348.c | |||
@@ -0,0 +1,246 @@ | |||
1 | /* | ||
2 | * A SPI driver for the Ricoh RS5C348 RTC | ||
3 | * | ||
4 | * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | * | ||
10 | * The board specific init code should provide characteristics of this | ||
11 | * device: | ||
12 | * Mode 1 (High-Active, Shift-Then-Sample), High Avtive CS | ||
13 | */ | ||
14 | |||
15 | #include <linux/bcd.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/device.h> | ||
18 | #include <linux/errno.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/kernel.h> | ||
21 | #include <linux/string.h> | ||
22 | #include <linux/rtc.h> | ||
23 | #include <linux/workqueue.h> | ||
24 | #include <linux/spi/spi.h> | ||
25 | |||
26 | #define DRV_VERSION "0.1" | ||
27 | |||
28 | #define RS5C348_REG_SECS 0 | ||
29 | #define RS5C348_REG_MINS 1 | ||
30 | #define RS5C348_REG_HOURS 2 | ||
31 | #define RS5C348_REG_WDAY 3 | ||
32 | #define RS5C348_REG_DAY 4 | ||
33 | #define RS5C348_REG_MONTH 5 | ||
34 | #define RS5C348_REG_YEAR 6 | ||
35 | #define RS5C348_REG_CTL1 14 | ||
36 | #define RS5C348_REG_CTL2 15 | ||
37 | |||
38 | #define RS5C348_SECS_MASK 0x7f | ||
39 | #define RS5C348_MINS_MASK 0x7f | ||
40 | #define RS5C348_HOURS_MASK 0x3f | ||
41 | #define RS5C348_WDAY_MASK 0x03 | ||
42 | #define RS5C348_DAY_MASK 0x3f | ||
43 | #define RS5C348_MONTH_MASK 0x1f | ||
44 | |||
45 | #define RS5C348_BIT_PM 0x20 /* REG_HOURS */ | ||
46 | #define RS5C348_BIT_Y2K 0x80 /* REG_MONTH */ | ||
47 | #define RS5C348_BIT_24H 0x20 /* REG_CTL1 */ | ||
48 | #define RS5C348_BIT_XSTP 0x10 /* REG_CTL2 */ | ||
49 | #define RS5C348_BIT_VDET 0x40 /* REG_CTL2 */ | ||
50 | |||
51 | #define RS5C348_CMD_W(addr) (((addr) << 4) | 0x08) /* single write */ | ||
52 | #define RS5C348_CMD_R(addr) (((addr) << 4) | 0x0c) /* single read */ | ||
53 | #define RS5C348_CMD_MW(addr) (((addr) << 4) | 0x00) /* burst write */ | ||
54 | #define RS5C348_CMD_MR(addr) (((addr) << 4) | 0x04) /* burst read */ | ||
55 | |||
56 | struct rs5c348_plat_data { | ||
57 | struct rtc_device *rtc; | ||
58 | int rtc_24h; | ||
59 | }; | ||
60 | |||
61 | static int | ||
62 | rs5c348_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
63 | { | ||
64 | struct spi_device *spi = to_spi_device(dev); | ||
65 | struct rs5c348_plat_data *pdata = spi->dev.platform_data; | ||
66 | u8 txbuf[5+7], *txp; | ||
67 | int ret; | ||
68 | |||
69 | /* Transfer 5 bytes before writing SEC. This gives 31us for carry. */ | ||
70 | txp = txbuf; | ||
71 | txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */ | ||
72 | txbuf[1] = 0; /* dummy */ | ||
73 | txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */ | ||
74 | txbuf[3] = 0; /* dummy */ | ||
75 | txbuf[4] = RS5C348_CMD_MW(RS5C348_REG_SECS); /* cmd, sec, ... */ | ||
76 | txp = &txbuf[5]; | ||
77 | txp[RS5C348_REG_SECS] = BIN2BCD(tm->tm_sec); | ||
78 | txp[RS5C348_REG_MINS] = BIN2BCD(tm->tm_min); | ||
79 | if (pdata->rtc_24h) { | ||
80 | txp[RS5C348_REG_HOURS] = BIN2BCD(tm->tm_hour); | ||
81 | } else { | ||
82 | /* hour 0 is AM12, noon is PM12 */ | ||
83 | txp[RS5C348_REG_HOURS] = BIN2BCD((tm->tm_hour + 11) % 12 + 1) | | ||
84 | (tm->tm_hour >= 12 ? RS5C348_BIT_PM : 0); | ||
85 | } | ||
86 | txp[RS5C348_REG_WDAY] = BIN2BCD(tm->tm_wday); | ||
87 | txp[RS5C348_REG_DAY] = BIN2BCD(tm->tm_mday); | ||
88 | txp[RS5C348_REG_MONTH] = BIN2BCD(tm->tm_mon + 1) | | ||
89 | (tm->tm_year >= 100 ? RS5C348_BIT_Y2K : 0); | ||
90 | txp[RS5C348_REG_YEAR] = BIN2BCD(tm->tm_year % 100); | ||
91 | /* write in one transfer to avoid data inconsistency */ | ||
92 | ret = spi_write_then_read(spi, txbuf, sizeof(txbuf), NULL, 0); | ||
93 | udelay(62); /* Tcsr 62us */ | ||
94 | return ret; | ||
95 | } | ||
96 | |||
97 | static int | ||
98 | rs5c348_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
99 | { | ||
100 | struct spi_device *spi = to_spi_device(dev); | ||
101 | struct rs5c348_plat_data *pdata = spi->dev.platform_data; | ||
102 | u8 txbuf[5], rxbuf[7]; | ||
103 | int ret; | ||
104 | |||
105 | /* Transfer 5 byte befores reading SEC. This gives 31us for carry. */ | ||
106 | txbuf[0] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */ | ||
107 | txbuf[1] = 0; /* dummy */ | ||
108 | txbuf[2] = RS5C348_CMD_R(RS5C348_REG_CTL2); /* cmd, ctl2 */ | ||
109 | txbuf[3] = 0; /* dummy */ | ||
110 | txbuf[4] = RS5C348_CMD_MR(RS5C348_REG_SECS); /* cmd, sec, ... */ | ||
111 | |||
112 | /* read in one transfer to avoid data inconsistency */ | ||
113 | ret = spi_write_then_read(spi, txbuf, sizeof(txbuf), | ||
114 | rxbuf, sizeof(rxbuf)); | ||
115 | udelay(62); /* Tcsr 62us */ | ||
116 | if (ret < 0) | ||
117 | return ret; | ||
118 | |||
119 | tm->tm_sec = BCD2BIN(rxbuf[RS5C348_REG_SECS] & RS5C348_SECS_MASK); | ||
120 | tm->tm_min = BCD2BIN(rxbuf[RS5C348_REG_MINS] & RS5C348_MINS_MASK); | ||
121 | tm->tm_hour = BCD2BIN(rxbuf[RS5C348_REG_HOURS] & RS5C348_HOURS_MASK); | ||
122 | if (!pdata->rtc_24h) { | ||
123 | tm->tm_hour %= 12; | ||
124 | if (rxbuf[RS5C348_REG_HOURS] & RS5C348_BIT_PM) | ||
125 | tm->tm_hour += 12; | ||
126 | } | ||
127 | tm->tm_wday = BCD2BIN(rxbuf[RS5C348_REG_WDAY] & RS5C348_WDAY_MASK); | ||
128 | tm->tm_mday = BCD2BIN(rxbuf[RS5C348_REG_DAY] & RS5C348_DAY_MASK); | ||
129 | tm->tm_mon = | ||
130 | BCD2BIN(rxbuf[RS5C348_REG_MONTH] & RS5C348_MONTH_MASK) - 1; | ||
131 | /* year is 1900 + tm->tm_year */ | ||
132 | tm->tm_year = BCD2BIN(rxbuf[RS5C348_REG_YEAR]) + | ||
133 | ((rxbuf[RS5C348_REG_MONTH] & RS5C348_BIT_Y2K) ? 100 : 0); | ||
134 | |||
135 | if (rtc_valid_tm(tm) < 0) { | ||
136 | dev_err(&spi->dev, "retrieved date/time is not valid.\n"); | ||
137 | rtc_time_to_tm(0, tm); | ||
138 | } | ||
139 | |||
140 | return 0; | ||
141 | } | ||
142 | |||
143 | static struct rtc_class_ops rs5c348_rtc_ops = { | ||
144 | .read_time = rs5c348_rtc_read_time, | ||
145 | .set_time = rs5c348_rtc_set_time, | ||
146 | }; | ||
147 | |||
148 | static struct spi_driver rs5c348_driver; | ||
149 | |||
150 | static int __devinit rs5c348_probe(struct spi_device *spi) | ||
151 | { | ||
152 | int ret; | ||
153 | struct rtc_device *rtc; | ||
154 | struct rs5c348_plat_data *pdata; | ||
155 | |||
156 | pdata = kzalloc(sizeof(struct rs5c348_plat_data), GFP_KERNEL); | ||
157 | if (!pdata) | ||
158 | return -ENOMEM; | ||
159 | spi->dev.platform_data = pdata; | ||
160 | |||
161 | /* Check D7 of SECOND register */ | ||
162 | ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_SECS)); | ||
163 | if (ret < 0 || (ret & 0x80)) { | ||
164 | dev_err(&spi->dev, "not found.\n"); | ||
165 | goto kfree_exit; | ||
166 | } | ||
167 | |||
168 | dev_info(&spi->dev, "chip found, driver version " DRV_VERSION "\n"); | ||
169 | dev_info(&spi->dev, "spiclk %u KHz.\n", | ||
170 | (spi->max_speed_hz + 500) / 1000); | ||
171 | |||
172 | /* turn RTC on if it was not on */ | ||
173 | ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL2)); | ||
174 | if (ret < 0) | ||
175 | goto kfree_exit; | ||
176 | if (ret & (RS5C348_BIT_XSTP | RS5C348_BIT_VDET)) { | ||
177 | u8 buf[2]; | ||
178 | if (ret & RS5C348_BIT_VDET) | ||
179 | dev_warn(&spi->dev, "voltage-low detected.\n"); | ||
180 | buf[0] = RS5C348_CMD_W(RS5C348_REG_CTL2); | ||
181 | buf[1] = 0; | ||
182 | ret = spi_write_then_read(spi, buf, sizeof(buf), NULL, 0); | ||
183 | if (ret < 0) | ||
184 | goto kfree_exit; | ||
185 | } | ||
186 | |||
187 | ret = spi_w8r8(spi, RS5C348_CMD_R(RS5C348_REG_CTL1)); | ||
188 | if (ret < 0) | ||
189 | goto kfree_exit; | ||
190 | if (ret & RS5C348_BIT_24H) | ||
191 | pdata->rtc_24h = 1; | ||
192 | |||
193 | rtc = rtc_device_register(rs5c348_driver.driver.name, &spi->dev, | ||
194 | &rs5c348_rtc_ops, THIS_MODULE); | ||
195 | |||
196 | if (IS_ERR(rtc)) { | ||
197 | ret = PTR_ERR(rtc); | ||
198 | goto kfree_exit; | ||
199 | } | ||
200 | |||
201 | pdata->rtc = rtc; | ||
202 | |||
203 | return 0; | ||
204 | kfree_exit: | ||
205 | kfree(pdata); | ||
206 | return ret; | ||
207 | } | ||
208 | |||
209 | static int __devexit rs5c348_remove(struct spi_device *spi) | ||
210 | { | ||
211 | struct rs5c348_plat_data *pdata = spi->dev.platform_data; | ||
212 | struct rtc_device *rtc = pdata->rtc; | ||
213 | |||
214 | if (rtc) | ||
215 | rtc_device_unregister(rtc); | ||
216 | kfree(pdata); | ||
217 | return 0; | ||
218 | } | ||
219 | |||
220 | static struct spi_driver rs5c348_driver = { | ||
221 | .driver = { | ||
222 | .name = "rs5c348", | ||
223 | .bus = &spi_bus_type, | ||
224 | .owner = THIS_MODULE, | ||
225 | }, | ||
226 | .probe = rs5c348_probe, | ||
227 | .remove = __devexit_p(rs5c348_remove), | ||
228 | }; | ||
229 | |||
230 | static __init int rs5c348_init(void) | ||
231 | { | ||
232 | return spi_register_driver(&rs5c348_driver); | ||
233 | } | ||
234 | |||
235 | static __exit void rs5c348_exit(void) | ||
236 | { | ||
237 | spi_unregister_driver(&rs5c348_driver); | ||
238 | } | ||
239 | |||
240 | module_init(rs5c348_init); | ||
241 | module_exit(rs5c348_exit); | ||
242 | |||
243 | MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); | ||
244 | MODULE_DESCRIPTION("Ricoh RS5C348 RTC driver"); | ||
245 | MODULE_LICENSE("GPL"); | ||
246 | MODULE_VERSION(DRV_VERSION); | ||