diff options
author | Piotr Ziecik <kosmo@semihalf.com> | 2009-12-15 19:46:12 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2009-12-16 10:20:00 -0500 |
commit | 1ce7c83fa91d27bd0e195e8b2ff10d3a1caeb0d6 (patch) | |
tree | 45e656183500592f562d5c5b31687a69fc0f53ed /drivers/rtc | |
parent | 8cfde8c1df31724f881de1a37f878ccbba4f178f (diff) |
rtc: add driver for BQ32000 I2C RTC
This patch adds basic support for Texas Instruments BQ32000 I2C RTC. Only
time reading/writing is implemented. Advanced features, such as trickle
charger and crystal calibration are not supported.
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com>
Signed-off-by: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r-- | drivers/rtc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/rtc/Makefile | 1 | ||||
-rw-r--r-- | drivers/rtc/rtc-bq32k.c | 204 |
3 files changed, 214 insertions, 0 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 9930d79fa51f..40c168bf6984 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -242,6 +242,15 @@ config RTC_DRV_M41T80_WDT | |||
242 | If you say Y here you will get support for the | 242 | If you say Y here you will get support for the |
243 | watchdog timer in the ST M41T60 and M41T80 RTC chips series. | 243 | watchdog timer in the ST M41T60 and M41T80 RTC chips series. |
244 | 244 | ||
245 | config RTC_DRV_BQ32K | ||
246 | tristate "TI BQ32000" | ||
247 | help | ||
248 | If you say Y here you will get support for the TI | ||
249 | BQ32000 I2C RTC chip. | ||
250 | |||
251 | This driver can also be built as a module. If so, the module | ||
252 | will be called rtc-bq32k. | ||
253 | |||
245 | config RTC_DRV_DM355EVM | 254 | config RTC_DRV_DM355EVM |
246 | tristate "TI DaVinci DM355 EVM RTC" | 255 | tristate "TI DaVinci DM355 EVM RTC" |
247 | depends on MFD_DM355EVM_MSP | 256 | depends on MFD_DM355EVM_MSP |
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6fa20f4ac3f1..52f5f3c3b8e0 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile | |||
@@ -23,6 +23,7 @@ obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o | |||
23 | obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o | 23 | obj-$(CONFIG_RTC_DRV_AT91SAM9) += rtc-at91sam9.o |
24 | obj-$(CONFIG_RTC_DRV_AU1XXX) += rtc-au1xxx.o | 24 | obj-$(CONFIG_RTC_DRV_AU1XXX) += rtc-au1xxx.o |
25 | obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o | 25 | obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o |
26 | obj-$(CONFIG_RTC_DRV_BQ32K) += rtc-bq32k.o | ||
26 | obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o | 27 | obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o |
27 | obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o | 28 | obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o |
28 | obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o | 29 | obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o |
diff --git a/drivers/rtc/rtc-bq32k.c b/drivers/rtc/rtc-bq32k.c new file mode 100644 index 000000000000..408cc8f735be --- /dev/null +++ b/drivers/rtc/rtc-bq32k.c | |||
@@ -0,0 +1,204 @@ | |||
1 | /* | ||
2 | * Driver for TI BQ32000 RTC. | ||
3 | * | ||
4 | * Copyright (C) 2009 Semihalf. | ||
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 | |||
11 | #include <linux/module.h> | ||
12 | #include <linux/i2c.h> | ||
13 | #include <linux/rtc.h> | ||
14 | #include <linux/init.h> | ||
15 | #include <linux/errno.h> | ||
16 | #include <linux/bcd.h> | ||
17 | |||
18 | #define BQ32K_SECONDS 0x00 /* Seconds register address */ | ||
19 | #define BQ32K_SECONDS_MASK 0x7F /* Mask over seconds value */ | ||
20 | #define BQ32K_STOP 0x80 /* Oscillator Stop flat */ | ||
21 | |||
22 | #define BQ32K_MINUTES 0x01 /* Minutes register address */ | ||
23 | #define BQ32K_MINUTES_MASK 0x7F /* Mask over minutes value */ | ||
24 | #define BQ32K_OF 0x80 /* Oscillator Failure flag */ | ||
25 | |||
26 | #define BQ32K_HOURS_MASK 0x3F /* Mask over hours value */ | ||
27 | #define BQ32K_CENT 0x40 /* Century flag */ | ||
28 | #define BQ32K_CENT_EN 0x80 /* Century flag enable bit */ | ||
29 | |||
30 | struct bq32k_regs { | ||
31 | uint8_t seconds; | ||
32 | uint8_t minutes; | ||
33 | uint8_t cent_hours; | ||
34 | uint8_t day; | ||
35 | uint8_t date; | ||
36 | uint8_t month; | ||
37 | uint8_t years; | ||
38 | }; | ||
39 | |||
40 | static struct i2c_driver bq32k_driver; | ||
41 | |||
42 | static int bq32k_read(struct device *dev, void *data, uint8_t off, uint8_t len) | ||
43 | { | ||
44 | struct i2c_client *client = to_i2c_client(dev); | ||
45 | struct i2c_msg msgs[] = { | ||
46 | { | ||
47 | .addr = client->addr, | ||
48 | .flags = 0, | ||
49 | .len = 1, | ||
50 | .buf = &off, | ||
51 | }, { | ||
52 | .addr = client->addr, | ||
53 | .flags = I2C_M_RD, | ||
54 | .len = len, | ||
55 | .buf = data, | ||
56 | } | ||
57 | }; | ||
58 | |||
59 | if (i2c_transfer(client->adapter, msgs, 2) == 2) | ||
60 | return 0; | ||
61 | |||
62 | return -EIO; | ||
63 | } | ||
64 | |||
65 | static int bq32k_write(struct device *dev, void *data, uint8_t off, uint8_t len) | ||
66 | { | ||
67 | struct i2c_client *client = to_i2c_client(dev); | ||
68 | uint8_t buffer[len + 1]; | ||
69 | |||
70 | buffer[0] = off; | ||
71 | memcpy(&buffer[1], data, len); | ||
72 | |||
73 | if (i2c_master_send(client, buffer, len + 1) == len + 1) | ||
74 | return 0; | ||
75 | |||
76 | return -EIO; | ||
77 | } | ||
78 | |||
79 | static int bq32k_rtc_read_time(struct device *dev, struct rtc_time *tm) | ||
80 | { | ||
81 | struct bq32k_regs regs; | ||
82 | int error; | ||
83 | |||
84 | error = bq32k_read(dev, ®s, 0, sizeof(regs)); | ||
85 | if (error) | ||
86 | return error; | ||
87 | |||
88 | tm->tm_sec = bcd2bin(regs.seconds & BQ32K_SECONDS_MASK); | ||
89 | tm->tm_min = bcd2bin(regs.minutes & BQ32K_SECONDS_MASK); | ||
90 | tm->tm_hour = bcd2bin(regs.cent_hours & BQ32K_HOURS_MASK); | ||
91 | tm->tm_mday = bcd2bin(regs.date); | ||
92 | tm->tm_wday = bcd2bin(regs.day) - 1; | ||
93 | tm->tm_mon = bcd2bin(regs.month) - 1; | ||
94 | tm->tm_year = bcd2bin(regs.years) + | ||
95 | ((regs.cent_hours & BQ32K_CENT) ? 100 : 0); | ||
96 | |||
97 | return rtc_valid_tm(tm); | ||
98 | } | ||
99 | |||
100 | static int bq32k_rtc_set_time(struct device *dev, struct rtc_time *tm) | ||
101 | { | ||
102 | struct bq32k_regs regs; | ||
103 | |||
104 | regs.seconds = bin2bcd(tm->tm_sec); | ||
105 | regs.minutes = bin2bcd(tm->tm_min); | ||
106 | regs.cent_hours = bin2bcd(tm->tm_hour) | BQ32K_CENT_EN; | ||
107 | regs.day = bin2bcd(tm->tm_wday + 1); | ||
108 | regs.date = bin2bcd(tm->tm_mday); | ||
109 | regs.month = bin2bcd(tm->tm_mon + 1); | ||
110 | |||
111 | if (tm->tm_year >= 100) { | ||
112 | regs.cent_hours |= BQ32K_CENT; | ||
113 | regs.years = bin2bcd(tm->tm_year - 100); | ||
114 | } else | ||
115 | regs.years = bin2bcd(tm->tm_year); | ||
116 | |||
117 | return bq32k_write(dev, ®s, 0, sizeof(regs)); | ||
118 | } | ||
119 | |||
120 | static const struct rtc_class_ops bq32k_rtc_ops = { | ||
121 | .read_time = bq32k_rtc_read_time, | ||
122 | .set_time = bq32k_rtc_set_time, | ||
123 | }; | ||
124 | |||
125 | static int bq32k_probe(struct i2c_client *client, | ||
126 | const struct i2c_device_id *id) | ||
127 | { | ||
128 | struct device *dev = &client->dev; | ||
129 | struct rtc_device *rtc; | ||
130 | uint8_t reg; | ||
131 | int error; | ||
132 | |||
133 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) | ||
134 | return -ENODEV; | ||
135 | |||
136 | /* Check Oscillator Stop flag */ | ||
137 | error = bq32k_read(dev, ®, BQ32K_SECONDS, 1); | ||
138 | if (!error && (reg & BQ32K_STOP)) { | ||
139 | dev_warn(dev, "Oscillator was halted. Restarting...\n"); | ||
140 | reg &= ~BQ32K_STOP; | ||
141 | error = bq32k_write(dev, ®, BQ32K_SECONDS, 1); | ||
142 | } | ||
143 | if (error) | ||
144 | return error; | ||
145 | |||
146 | /* Check Oscillator Failure flag */ | ||
147 | error = bq32k_read(dev, ®, BQ32K_MINUTES, 1); | ||
148 | if (!error && (reg & BQ32K_OF)) { | ||
149 | dev_warn(dev, "Oscillator Failure. Check RTC battery.\n"); | ||
150 | reg &= ~BQ32K_OF; | ||
151 | error = bq32k_write(dev, ®, BQ32K_MINUTES, 1); | ||
152 | } | ||
153 | if (error) | ||
154 | return error; | ||
155 | |||
156 | rtc = rtc_device_register(bq32k_driver.driver.name, &client->dev, | ||
157 | &bq32k_rtc_ops, THIS_MODULE); | ||
158 | if (IS_ERR(rtc)) | ||
159 | return PTR_ERR(rtc); | ||
160 | |||
161 | i2c_set_clientdata(client, rtc); | ||
162 | |||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static int __devexit bq32k_remove(struct i2c_client *client) | ||
167 | { | ||
168 | struct rtc_device *rtc = i2c_get_clientdata(client); | ||
169 | |||
170 | rtc_device_unregister(rtc); | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | static const struct i2c_device_id bq32k_id[] = { | ||
175 | { "bq32000", 0 }, | ||
176 | { } | ||
177 | }; | ||
178 | MODULE_DEVICE_TABLE(i2c, bq32k_id); | ||
179 | |||
180 | static struct i2c_driver bq32k_driver = { | ||
181 | .driver = { | ||
182 | .name = "bq32k", | ||
183 | .owner = THIS_MODULE, | ||
184 | }, | ||
185 | .probe = bq32k_probe, | ||
186 | .remove = __devexit_p(bq32k_remove), | ||
187 | .id_table = bq32k_id, | ||
188 | }; | ||
189 | |||
190 | static __init int bq32k_init(void) | ||
191 | { | ||
192 | return i2c_add_driver(&bq32k_driver); | ||
193 | } | ||
194 | module_init(bq32k_init); | ||
195 | |||
196 | static __exit void bq32k_exit(void) | ||
197 | { | ||
198 | i2c_del_driver(&bq32k_driver); | ||
199 | } | ||
200 | module_exit(bq32k_exit); | ||
201 | |||
202 | MODULE_AUTHOR("Semihalf, Piotr Ziecik <kosmo@semihalf.com>"); | ||
203 | MODULE_DESCRIPTION("TI BQ32000 I2C RTC driver"); | ||
204 | MODULE_LICENSE("GPL"); | ||