diff options
Diffstat (limited to 'drivers/rtc/rtc-bq32k.c')
-rw-r--r-- | drivers/rtc/rtc-bq32k.c | 204 |
1 files changed, 204 insertions, 0 deletions
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"); | ||