diff options
Diffstat (limited to 'drivers/i2c/chips/m41t00.c')
-rw-r--r-- | drivers/i2c/chips/m41t00.c | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c new file mode 100644 index 000000000000..e771566dffa8 --- /dev/null +++ b/drivers/i2c/chips/m41t00.c | |||
@@ -0,0 +1,246 @@ | |||
1 | /* | ||
2 | * drivers/i2c/chips/m41t00.c | ||
3 | * | ||
4 | * I2C client/driver for the ST M41T00 Real-Time Clock chip. | ||
5 | * | ||
6 | * Author: Mark A. Greer <mgreer@mvista.com> | ||
7 | * | ||
8 | * 2005 (c) MontaVista Software, Inc. This file is licensed under | ||
9 | * the terms of the GNU General Public License version 2. This program | ||
10 | * is licensed "as is" without any warranty of any kind, whether express | ||
11 | * or implied. | ||
12 | */ | ||
13 | /* | ||
14 | * This i2c client/driver wedges between the drivers/char/genrtc.c RTC | ||
15 | * interface and the SMBus interface of the i2c subsystem. | ||
16 | * It would be more efficient to use i2c msgs/i2c_transfer directly but, as | ||
17 | * recommened in .../Documentation/i2c/writing-clients section | ||
18 | * "Sending and receiving", using SMBus level communication is preferred. | ||
19 | */ | ||
20 | |||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/i2c.h> | ||
25 | #include <linux/rtc.h> | ||
26 | #include <linux/bcd.h> | ||
27 | |||
28 | #include <asm/time.h> | ||
29 | #include <asm/rtc.h> | ||
30 | |||
31 | #define M41T00_DRV_NAME "m41t00" | ||
32 | |||
33 | static DECLARE_MUTEX(m41t00_mutex); | ||
34 | |||
35 | static struct i2c_driver m41t00_driver; | ||
36 | static struct i2c_client *save_client; | ||
37 | |||
38 | static unsigned short ignore[] = { I2C_CLIENT_END }; | ||
39 | static unsigned short normal_addr[] = { 0x68, I2C_CLIENT_END }; | ||
40 | |||
41 | static struct i2c_client_address_data addr_data = { | ||
42 | .normal_i2c = normal_addr, | ||
43 | .normal_i2c_range = ignore, | ||
44 | .probe = ignore, | ||
45 | .probe_range = ignore, | ||
46 | .ignore = ignore, | ||
47 | .ignore_range = ignore, | ||
48 | .force = ignore, | ||
49 | }; | ||
50 | |||
51 | ulong | ||
52 | m41t00_get_rtc_time(void) | ||
53 | { | ||
54 | s32 sec, min, hour, day, mon, year; | ||
55 | s32 sec1, min1, hour1, day1, mon1, year1; | ||
56 | ulong limit = 10; | ||
57 | |||
58 | sec = min = hour = day = mon = year = 0; | ||
59 | sec1 = min1 = hour1 = day1 = mon1 = year1 = 0; | ||
60 | |||
61 | down(&m41t00_mutex); | ||
62 | do { | ||
63 | if (((sec = i2c_smbus_read_byte_data(save_client, 0)) >= 0) | ||
64 | && ((min = i2c_smbus_read_byte_data(save_client, 1)) | ||
65 | >= 0) | ||
66 | && ((hour = i2c_smbus_read_byte_data(save_client, 2)) | ||
67 | >= 0) | ||
68 | && ((day = i2c_smbus_read_byte_data(save_client, 4)) | ||
69 | >= 0) | ||
70 | && ((mon = i2c_smbus_read_byte_data(save_client, 5)) | ||
71 | >= 0) | ||
72 | && ((year = i2c_smbus_read_byte_data(save_client, 6)) | ||
73 | >= 0) | ||
74 | && ((sec == sec1) && (min == min1) && (hour == hour1) | ||
75 | && (day == day1) && (mon == mon1) | ||
76 | && (year == year1))) | ||
77 | |||
78 | break; | ||
79 | |||
80 | sec1 = sec; | ||
81 | min1 = min; | ||
82 | hour1 = hour; | ||
83 | day1 = day; | ||
84 | mon1 = mon; | ||
85 | year1 = year; | ||
86 | } while (--limit > 0); | ||
87 | up(&m41t00_mutex); | ||
88 | |||
89 | if (limit == 0) { | ||
90 | dev_warn(&save_client->dev, | ||
91 | "m41t00: can't read rtc chip\n"); | ||
92 | sec = min = hour = day = mon = year = 0; | ||
93 | } | ||
94 | |||
95 | sec &= 0x7f; | ||
96 | min &= 0x7f; | ||
97 | hour &= 0x3f; | ||
98 | day &= 0x3f; | ||
99 | mon &= 0x1f; | ||
100 | year &= 0xff; | ||
101 | |||
102 | BCD_TO_BIN(sec); | ||
103 | BCD_TO_BIN(min); | ||
104 | BCD_TO_BIN(hour); | ||
105 | BCD_TO_BIN(day); | ||
106 | BCD_TO_BIN(mon); | ||
107 | BCD_TO_BIN(year); | ||
108 | |||
109 | year += 1900; | ||
110 | if (year < 1970) | ||
111 | year += 100; | ||
112 | |||
113 | return mktime(year, mon, day, hour, min, sec); | ||
114 | } | ||
115 | |||
116 | static void | ||
117 | m41t00_set_tlet(ulong arg) | ||
118 | { | ||
119 | struct rtc_time tm; | ||
120 | ulong nowtime = *(ulong *)arg; | ||
121 | |||
122 | to_tm(nowtime, &tm); | ||
123 | tm.tm_year = (tm.tm_year - 1900) % 100; | ||
124 | |||
125 | BIN_TO_BCD(tm.tm_sec); | ||
126 | BIN_TO_BCD(tm.tm_min); | ||
127 | BIN_TO_BCD(tm.tm_hour); | ||
128 | BIN_TO_BCD(tm.tm_mon); | ||
129 | BIN_TO_BCD(tm.tm_mday); | ||
130 | BIN_TO_BCD(tm.tm_year); | ||
131 | |||
132 | down(&m41t00_mutex); | ||
133 | if ((i2c_smbus_write_byte_data(save_client, 0, tm.tm_sec & 0x7f) < 0) | ||
134 | || (i2c_smbus_write_byte_data(save_client, 1, tm.tm_min & 0x7f) | ||
135 | < 0) | ||
136 | || (i2c_smbus_write_byte_data(save_client, 2, tm.tm_hour & 0x7f) | ||
137 | < 0) | ||
138 | || (i2c_smbus_write_byte_data(save_client, 4, tm.tm_mday & 0x7f) | ||
139 | < 0) | ||
140 | || (i2c_smbus_write_byte_data(save_client, 5, tm.tm_mon & 0x7f) | ||
141 | < 0) | ||
142 | || (i2c_smbus_write_byte_data(save_client, 6, tm.tm_year & 0x7f) | ||
143 | < 0)) | ||
144 | |||
145 | dev_warn(&save_client->dev,"m41t00: can't write to rtc chip\n"); | ||
146 | |||
147 | up(&m41t00_mutex); | ||
148 | return; | ||
149 | } | ||
150 | |||
151 | ulong new_time; | ||
152 | |||
153 | DECLARE_TASKLET_DISABLED(m41t00_tasklet, m41t00_set_tlet, (ulong)&new_time); | ||
154 | |||
155 | int | ||
156 | m41t00_set_rtc_time(ulong nowtime) | ||
157 | { | ||
158 | new_time = nowtime; | ||
159 | |||
160 | if (in_interrupt()) | ||
161 | tasklet_schedule(&m41t00_tasklet); | ||
162 | else | ||
163 | m41t00_set_tlet((ulong)&new_time); | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | /* | ||
169 | ***************************************************************************** | ||
170 | * | ||
171 | * Driver Interface | ||
172 | * | ||
173 | ***************************************************************************** | ||
174 | */ | ||
175 | static int | ||
176 | m41t00_probe(struct i2c_adapter *adap, int addr, int kind) | ||
177 | { | ||
178 | struct i2c_client *client; | ||
179 | int rc; | ||
180 | |||
181 | client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); | ||
182 | if (!client) | ||
183 | return -ENOMEM; | ||
184 | |||
185 | memset(client, 0, sizeof(struct i2c_client)); | ||
186 | strncpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE); | ||
187 | client->flags = I2C_DF_NOTIFY; | ||
188 | client->addr = addr; | ||
189 | client->adapter = adap; | ||
190 | client->driver = &m41t00_driver; | ||
191 | |||
192 | if ((rc = i2c_attach_client(client)) != 0) { | ||
193 | kfree(client); | ||
194 | return rc; | ||
195 | } | ||
196 | |||
197 | save_client = client; | ||
198 | return 0; | ||
199 | } | ||
200 | |||
201 | static int | ||
202 | m41t00_attach(struct i2c_adapter *adap) | ||
203 | { | ||
204 | return i2c_probe(adap, &addr_data, m41t00_probe); | ||
205 | } | ||
206 | |||
207 | static int | ||
208 | m41t00_detach(struct i2c_client *client) | ||
209 | { | ||
210 | int rc; | ||
211 | |||
212 | if ((rc = i2c_detach_client(client)) == 0) { | ||
213 | kfree(i2c_get_clientdata(client)); | ||
214 | tasklet_kill(&m41t00_tasklet); | ||
215 | } | ||
216 | return rc; | ||
217 | } | ||
218 | |||
219 | static struct i2c_driver m41t00_driver = { | ||
220 | .owner = THIS_MODULE, | ||
221 | .name = M41T00_DRV_NAME, | ||
222 | .id = I2C_DRIVERID_STM41T00, | ||
223 | .flags = I2C_DF_NOTIFY, | ||
224 | .attach_adapter = m41t00_attach, | ||
225 | .detach_client = m41t00_detach, | ||
226 | }; | ||
227 | |||
228 | static int __init | ||
229 | m41t00_init(void) | ||
230 | { | ||
231 | return i2c_add_driver(&m41t00_driver); | ||
232 | } | ||
233 | |||
234 | static void __exit | ||
235 | m41t00_exit(void) | ||
236 | { | ||
237 | i2c_del_driver(&m41t00_driver); | ||
238 | return; | ||
239 | } | ||
240 | |||
241 | module_init(m41t00_init); | ||
242 | module_exit(m41t00_exit); | ||
243 | |||
244 | MODULE_AUTHOR("Mark A. Greer <mgreer@mvista.com>"); | ||
245 | MODULE_DESCRIPTION("ST Microelectronics M41T00 RTC I2C Client Driver"); | ||
246 | MODULE_LICENSE("GPL"); | ||