aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMark A. Greer <mgreer@mvista.com>2006-04-25 07:04:54 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2006-06-22 14:10:32 -0400
commit5e9f4f2e5a02bb6908278a819952aa31fffefaa2 (patch)
tree6f000080ee15597f9523de0e6d31b192bdb0aaec
parente931b8d8a428f87e6ea488d2fd80007bb66b3ea8 (diff)
[PATCH] I2C: m41t00: Add support for the ST M41T81 and M41T85
This patch adds support for the ST m41t81 and m41t85 i2c rtc chips to the existing m41t00 driver. Since there is no way to reliably determine what type of rtc chip is in use, the chip type is passed in via platform_data. The i2c address and square wave frequency are passed in via platform_data as well. To accommodate the use of platform_data, a new header file include/linux/m41t00.h has been added. The m41t81 and m41t85 chips halt the updating of their time registers while they are being accessed. They resume when a stop condition exists on the i2c bus or when non-time related regs are accessed. To make the best use of that facility and to make more efficient use of the i2c bus, this patch replaces multiple i2c_smbus_xxx calls with a single i2c_transfer call. Signed-off-by: Mark A. Greer <mgreer@mvista.com> Signed-off-by: Jean Delvare <khali@linux-fr.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/i2c/chips/m41t00.c313
-rw-r--r--include/linux/m41t00.h50
2 files changed, 291 insertions, 72 deletions
diff --git a/drivers/i2c/chips/m41t00.c b/drivers/i2c/chips/m41t00.c
index eb2be3e4416c..2dd0a34d9472 100644
--- a/drivers/i2c/chips/m41t00.c
+++ b/drivers/i2c/chips/m41t00.c
@@ -1,9 +1,9 @@
1/* 1/*
2 * I2C client/driver for the ST M41T00 Real-Time Clock chip. 2 * I2C client/driver for the ST M41T00 family of i2c rtc chips.
3 * 3 *
4 * Author: Mark A. Greer <mgreer@mvista.com> 4 * Author: Mark A. Greer <mgreer@mvista.com>
5 * 5 *
6 * 2005 (c) MontaVista Software, Inc. This file is licensed under 6 * 2005, 2006 (c) MontaVista Software, Inc. This file is licensed under
7 * the terms of the GNU General Public License version 2. This program 7 * the terms of the GNU General Public License version 2. This program
8 * is licensed "as is" without any warranty of any kind, whether express 8 * is licensed "as is" without any warranty of any kind, whether express
9 * or implied. 9 * or implied.
@@ -19,21 +19,17 @@
19#include <linux/i2c.h> 19#include <linux/i2c.h>
20#include <linux/rtc.h> 20#include <linux/rtc.h>
21#include <linux/bcd.h> 21#include <linux/bcd.h>
22#include <linux/mutex.h>
23#include <linux/workqueue.h> 22#include <linux/workqueue.h>
24 23#include <linux/platform_device.h>
24#include <linux/m41t00.h>
25#include <asm/time.h> 25#include <asm/time.h>
26#include <asm/rtc.h> 26#include <asm/rtc.h>
27 27
28#define M41T00_DRV_NAME "m41t00"
29
30static DEFINE_MUTEX(m41t00_mutex);
31
32static struct i2c_driver m41t00_driver; 28static struct i2c_driver m41t00_driver;
33static struct i2c_client *save_client; 29static struct i2c_client *save_client;
34 30
35static unsigned short ignore[] = { I2C_CLIENT_END }; 31static unsigned short ignore[] = { I2C_CLIENT_END };
36static unsigned short normal_addr[] = { 0x68, I2C_CLIENT_END }; 32static unsigned short normal_addr[] = { I2C_CLIENT_END, I2C_CLIENT_END };
37 33
38static struct i2c_client_address_data addr_data = { 34static struct i2c_client_address_data addr_data = {
39 .normal_i2c = normal_addr, 35 .normal_i2c = normal_addr,
@@ -41,34 +37,92 @@ static struct i2c_client_address_data addr_data = {
41 .ignore = ignore, 37 .ignore = ignore,
42}; 38};
43 39
40struct m41t00_chip_info {
41 u8 type;
42 char *name;
43 u8 read_limit;
44 u8 sec; /* Offsets for chip regs */
45 u8 min;
46 u8 hour;
47 u8 day;
48 u8 mon;
49 u8 year;
50 u8 alarm_mon;
51 u8 alarm_hour;
52 u8 sqw;
53 u8 sqw_freq;
54};
55
56static struct m41t00_chip_info m41t00_chip_info_tbl[] = {
57 {
58 .type = M41T00_TYPE_M41T00,
59 .name = "m41t00",
60 .read_limit = 5,
61 .sec = 0,
62 .min = 1,
63 .hour = 2,
64 .day = 4,
65 .mon = 5,
66 .year = 6,
67 },
68 {
69 .type = M41T00_TYPE_M41T81,
70 .name = "m41t81",
71 .read_limit = 1,
72 .sec = 1,
73 .min = 2,
74 .hour = 3,
75 .day = 5,
76 .mon = 6,
77 .year = 7,
78 .alarm_mon = 0xa,
79 .alarm_hour = 0xc,
80 .sqw = 0x13,
81 },
82 {
83 .type = M41T00_TYPE_M41T85,
84 .name = "m41t85",
85 .read_limit = 1,
86 .sec = 1,
87 .min = 2,
88 .hour = 3,
89 .day = 5,
90 .mon = 6,
91 .year = 7,
92 .alarm_mon = 0xa,
93 .alarm_hour = 0xc,
94 .sqw = 0x13,
95 },
96};
97static struct m41t00_chip_info *m41t00_chip;
98
44ulong 99ulong
45m41t00_get_rtc_time(void) 100m41t00_get_rtc_time(void)
46{ 101{
47 s32 sec, min, hour, day, mon, year; 102 s32 sec, min, hour, day, mon, year;
48 s32 sec1, min1, hour1, day1, mon1, year1; 103 s32 sec1, min1, hour1, day1, mon1, year1;
49 ulong limit = 10; 104 u8 reads = 0;
105 u8 buf[8], msgbuf[1] = { 0 }; /* offset into rtc's regs */
106 struct i2c_msg msgs[] = {
107 {
108 .addr = save_client->addr,
109 .flags = 0,
110 .len = 1,
111 .buf = msgbuf,
112 },
113 {
114 .addr = save_client->addr,
115 .flags = I2C_M_RD,
116 .len = 8,
117 .buf = buf,
118 },
119 };
50 120
51 sec = min = hour = day = mon = year = 0; 121 sec = min = hour = day = mon = year = 0;
52 sec1 = min1 = hour1 = day1 = mon1 = year1 = 0;
53 122
54 mutex_lock(&m41t00_mutex);
55 do { 123 do {
56 if (((sec = i2c_smbus_read_byte_data(save_client, 0)) >= 0) 124 if (i2c_transfer(save_client->adapter, msgs, 2) < 0)
57 && ((min = i2c_smbus_read_byte_data(save_client, 1)) 125 goto read_err;
58 >= 0)
59 && ((hour = i2c_smbus_read_byte_data(save_client, 2))
60 >= 0)
61 && ((day = i2c_smbus_read_byte_data(save_client, 4))
62 >= 0)
63 && ((mon = i2c_smbus_read_byte_data(save_client, 5))
64 >= 0)
65 && ((year = i2c_smbus_read_byte_data(save_client, 6))
66 >= 0)
67 && ((sec == sec1) && (min == min1) && (hour == hour1)
68 && (day == day1) && (mon == mon1)
69 && (year == year1)))
70
71 break;
72 126
73 sec1 = sec; 127 sec1 = sec;
74 min1 = min; 128 min1 = min;
@@ -76,21 +130,21 @@ m41t00_get_rtc_time(void)
76 day1 = day; 130 day1 = day;
77 mon1 = mon; 131 mon1 = mon;
78 year1 = year; 132 year1 = year;
79 } while (--limit > 0);
80 mutex_unlock(&m41t00_mutex);
81
82 if (limit == 0) {
83 dev_warn(&save_client->dev,
84 "m41t00: can't read rtc chip\n");
85 sec = min = hour = day = mon = year = 0;
86 }
87 133
88 sec &= 0x7f; 134 sec = buf[m41t00_chip->sec] & 0x7f;
89 min &= 0x7f; 135 min = buf[m41t00_chip->min] & 0x7f;
90 hour &= 0x3f; 136 hour = buf[m41t00_chip->hour] & 0x3f;
91 day &= 0x3f; 137 day = buf[m41t00_chip->day] & 0x3f;
92 mon &= 0x1f; 138 mon = buf[m41t00_chip->mon] & 0x1f;
93 year &= 0xff; 139 year = buf[m41t00_chip->year];
140 } while ((++reads < m41t00_chip->read_limit) && ((sec != sec1)
141 || (min != min1) || (hour != hour1) || (day != day1)
142 || (mon != mon1) || (year != year1)));
143
144 if ((m41t00_chip->read_limit > 1) && ((sec != sec1) || (min != min1)
145 || (hour != hour1) || (day != day1) || (mon != mon1)
146 || (year != year1)))
147 goto read_err;
94 148
95 sec = BCD2BIN(sec); 149 sec = BCD2BIN(sec);
96 min = BCD2BIN(min); 150 min = BCD2BIN(min);
@@ -104,40 +158,60 @@ m41t00_get_rtc_time(void)
104 year += 100; 158 year += 100;
105 159
106 return mktime(year, mon, day, hour, min, sec); 160 return mktime(year, mon, day, hour, min, sec);
161
162read_err:
163 dev_err(&save_client->dev, "m41t00_get_rtc_time: Read error\n");
164 return 0;
107} 165}
166EXPORT_SYMBOL_GPL(m41t00_get_rtc_time);
108 167
109static void 168static void
110m41t00_set(void *arg) 169m41t00_set(void *arg)
111{ 170{
112 struct rtc_time tm; 171 struct rtc_time tm;
113 ulong nowtime = *(ulong *)arg; 172 int nowtime = *(int *)arg;
173 s32 sec, min, hour, day, mon, year;
174 u8 wbuf[9], *buf = &wbuf[1], msgbuf[1] = { 0 };
175 struct i2c_msg msgs[] = {
176 {
177 .addr = save_client->addr,
178 .flags = 0,
179 .len = 1,
180 .buf = msgbuf,
181 },
182 {
183 .addr = save_client->addr,
184 .flags = I2C_M_RD,
185 .len = 8,
186 .buf = buf,
187 },
188 };
114 189
115 to_tm(nowtime, &tm); 190 to_tm(nowtime, &tm);
116 tm.tm_year = (tm.tm_year - 1900) % 100; 191 tm.tm_year = (tm.tm_year - 1900) % 100;
117 192
118 tm.tm_sec = BIN2BCD(tm.tm_sec); 193 sec = BIN2BCD(tm.tm_sec);
119 tm.tm_min = BIN2BCD(tm.tm_min); 194 min = BIN2BCD(tm.tm_min);
120 tm.tm_hour = BIN2BCD(tm.tm_hour); 195 hour = BIN2BCD(tm.tm_hour);
121 tm.tm_mon = BIN2BCD(tm.tm_mon); 196 day = BIN2BCD(tm.tm_mday);
122 tm.tm_mday = BIN2BCD(tm.tm_mday); 197 mon = BIN2BCD(tm.tm_mon);
123 tm.tm_year = BIN2BCD(tm.tm_year); 198 year = BIN2BCD(tm.tm_year);
124 199
125 mutex_lock(&m41t00_mutex); 200 /* Read reg values into buf[0..7]/wbuf[1..8] */
126 if ((i2c_smbus_write_byte_data(save_client, 0, tm.tm_sec & 0x7f) < 0) 201 if (i2c_transfer(save_client->adapter, msgs, 2) < 0) {
127 || (i2c_smbus_write_byte_data(save_client, 1, tm.tm_min & 0x7f) 202 dev_err(&save_client->dev, "m41t00_set: Read error\n");
128 < 0) 203 return;
129 || (i2c_smbus_write_byte_data(save_client, 2, tm.tm_hour & 0x3f) 204 }
130 < 0) 205
131 || (i2c_smbus_write_byte_data(save_client, 4, tm.tm_mday & 0x3f) 206 wbuf[0] = 0; /* offset into rtc's regs */
132 < 0) 207 buf[m41t00_chip->sec] = (buf[m41t00_chip->sec] & ~0x7f) | (sec & 0x7f);
133 || (i2c_smbus_write_byte_data(save_client, 5, tm.tm_mon & 0x1f) 208 buf[m41t00_chip->min] = (buf[m41t00_chip->min] & ~0x7f) | (min & 0x7f);
134 < 0) 209 buf[m41t00_chip->hour] = (buf[m41t00_chip->hour] & ~0x3f) | (hour& 0x3f);
135 || (i2c_smbus_write_byte_data(save_client, 6, tm.tm_year & 0xff) 210 buf[m41t00_chip->day] = (buf[m41t00_chip->day] & ~0x3f) | (day & 0x3f);
136 < 0)) 211 buf[m41t00_chip->mon] = (buf[m41t00_chip->mon] & ~0x1f) | (mon & 0x1f);
137 212
138 dev_warn(&save_client->dev,"m41t00: can't write to rtc chip\n"); 213 if (i2c_master_send(save_client, wbuf, 9) < 0)
139 214 dev_err(&save_client->dev, "m41t00_set: Write error\n");
140 mutex_unlock(&m41t00_mutex);
141} 215}
142 216
143static ulong new_time; 217static ulong new_time;
@@ -156,6 +230,48 @@ m41t00_set_rtc_time(ulong nowtime)
156 230
157 return 0; 231 return 0;
158} 232}
233EXPORT_SYMBOL_GPL(m41t00_set_rtc_time);
234
235/*
236 *****************************************************************************
237 *
238 * platform_data Driver Interface
239 *
240 *****************************************************************************
241 */
242static int __init
243m41t00_platform_probe(struct platform_device *pdev)
244{
245 struct m41t00_platform_data *pdata;
246 int i;
247
248 if (pdev && (pdata = pdev->dev.platform_data)) {
249 normal_addr[0] = pdata->i2c_addr;
250
251 for (i=0; i<ARRAY_SIZE(m41t00_chip_info_tbl); i++)
252 if (m41t00_chip_info_tbl[i].type == pdata->type) {
253 m41t00_chip = &m41t00_chip_info_tbl[i];
254 m41t00_chip->sqw_freq = pdata->sqw_freq;
255 return 0;
256 }
257 }
258 return -ENODEV;
259}
260
261static int __exit
262m41t00_platform_remove(struct platform_device *pdev)
263{
264 return 0;
265}
266
267static struct platform_driver m41t00_platform_driver = {
268 .probe = m41t00_platform_probe,
269 .remove = m41t00_platform_remove,
270 .driver = {
271 .owner = THIS_MODULE,
272 .name = M41T00_DRV_NAME,
273 },
274};
159 275
160/* 276/*
161 ***************************************************************************** 277 *****************************************************************************
@@ -170,23 +286,71 @@ m41t00_probe(struct i2c_adapter *adap, int addr, int kind)
170 struct i2c_client *client; 286 struct i2c_client *client;
171 int rc; 287 int rc;
172 288
289 if (!i2c_check_functionality(adap, I2C_FUNC_I2C
290 | I2C_FUNC_SMBUS_BYTE_DATA))
291 return 0;
292
173 client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); 293 client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
174 if (!client) 294 if (!client)
175 return -ENOMEM; 295 return -ENOMEM;
176 296
177 strlcpy(client->name, M41T00_DRV_NAME, I2C_NAME_SIZE); 297 strlcpy(client->name, m41t00_chip->name, I2C_NAME_SIZE);
178 client->addr = addr; 298 client->addr = addr;
179 client->adapter = adap; 299 client->adapter = adap;
180 client->driver = &m41t00_driver; 300 client->driver = &m41t00_driver;
181 301
182 if ((rc = i2c_attach_client(client)) != 0) { 302 if ((rc = i2c_attach_client(client)))
183 kfree(client); 303 goto attach_err;
184 return rc; 304
305 if (m41t00_chip->type != M41T00_TYPE_M41T00) {
306 /* If asked, disable SQW, set SQW frequency & re-enable */
307 if (m41t00_chip->sqw_freq)
308 if (((rc = i2c_smbus_read_byte_data(client,
309 m41t00_chip->alarm_mon)) < 0)
310 || ((rc = i2c_smbus_write_byte_data(client,
311 m41t00_chip->alarm_mon, rc & ~0x40)) <0)
312 || ((rc = i2c_smbus_write_byte_data(client,
313 m41t00_chip->sqw,
314 m41t00_chip->sqw_freq)) < 0)
315 || ((rc = i2c_smbus_write_byte_data(client,
316 m41t00_chip->alarm_mon, rc | 0x40)) <0))
317 goto sqw_err;
318
319 /* Make sure HT (Halt Update) bit is cleared */
320 if ((rc = i2c_smbus_read_byte_data(client,
321 m41t00_chip->alarm_hour)) < 0)
322 goto ht_err;
323
324 if (rc & 0x40)
325 if ((rc = i2c_smbus_write_byte_data(client,
326 m41t00_chip->alarm_hour, rc & ~0x40))<0)
327 goto ht_err;
185 } 328 }
186 329
187 m41t00_wq = create_singlethread_workqueue("m41t00"); 330 /* Make sure ST (stop) bit is cleared */
331 if ((rc = i2c_smbus_read_byte_data(client, m41t00_chip->sec)) < 0)
332 goto st_err;
333
334 if (rc & 0x80)
335 if ((rc = i2c_smbus_write_byte_data(client, m41t00_chip->sec,
336 rc & ~0x80)) < 0)
337 goto st_err;
338
339 m41t00_wq = create_singlethread_workqueue(m41t00_chip->name);
188 save_client = client; 340 save_client = client;
189 return 0; 341 return 0;
342
343st_err:
344 dev_err(&client->dev, "m41t00_probe: Can't clear ST bit\n");
345 goto attach_err;
346ht_err:
347 dev_err(&client->dev, "m41t00_probe: Can't clear HT bit\n");
348 goto attach_err;
349sqw_err:
350 dev_err(&client->dev, "m41t00_probe: Can't set SQW Frequency\n");
351attach_err:
352 kfree(client);
353 return rc;
190} 354}
191 355
192static int 356static int
@@ -219,13 +383,18 @@ static struct i2c_driver m41t00_driver = {
219static int __init 383static int __init
220m41t00_init(void) 384m41t00_init(void)
221{ 385{
222 return i2c_add_driver(&m41t00_driver); 386 int rc;
387
388 if (!(rc = platform_driver_register(&m41t00_platform_driver)))
389 rc = i2c_add_driver(&m41t00_driver);
390 return rc;
223} 391}
224 392
225static void __exit 393static void __exit
226m41t00_exit(void) 394m41t00_exit(void)
227{ 395{
228 i2c_del_driver(&m41t00_driver); 396 i2c_del_driver(&m41t00_driver);
397 platform_driver_unregister(&m41t00_platform_driver);
229} 398}
230 399
231module_init(m41t00_init); 400module_init(m41t00_init);
diff --git a/include/linux/m41t00.h b/include/linux/m41t00.h
new file mode 100644
index 000000000000..b423360ca38e
--- /dev/null
+++ b/include/linux/m41t00.h
@@ -0,0 +1,50 @@
1/*
2 * Definitions for the ST M41T00 family of i2c rtc chips.
3 *
4 * Author: Mark A. Greer <mgreer@mvista.com>
5 *
6 * 2005, 2006 (c) MontaVista Software, Inc. This file is licensed under
7 * the terms of the GNU General Public License version 2. This program
8 * is licensed "as is" without any warranty of any kind, whether express
9 * or implied.
10 */
11
12#ifndef _M41T00_H
13#define _M41T00_H
14
15#define M41T00_DRV_NAME "m41t00"
16#define M41T00_I2C_ADDR 0x68
17
18#define M41T00_TYPE_M41T00 0
19#define M41T00_TYPE_M41T81 81
20#define M41T00_TYPE_M41T85 85
21
22struct m41t00_platform_data {
23 u8 type;
24 u8 i2c_addr;
25 u8 sqw_freq;
26};
27
28/* SQW output disabled, this is default value by power on */
29#define M41T00_SQW_DISABLE (0)
30
31#define M41T00_SQW_32KHZ (1<<4) /* 32.768 KHz */
32#define M41T00_SQW_8KHZ (2<<4) /* 8.192 KHz */
33#define M41T00_SQW_4KHZ (3<<4) /* 4.096 KHz */
34#define M41T00_SQW_2KHZ (4<<4) /* 2.048 KHz */
35#define M41T00_SQW_1KHZ (5<<4) /* 1.024 KHz */
36#define M41T00_SQW_512HZ (6<<4) /* 512 Hz */
37#define M41T00_SQW_256HZ (7<<4) /* 256 Hz */
38#define M41T00_SQW_128HZ (8<<4) /* 128 Hz */
39#define M41T00_SQW_64HZ (9<<4) /* 64 Hz */
40#define M41T00_SQW_32HZ (10<<4) /* 32 Hz */
41#define M41T00_SQW_16HZ (11<<4) /* 16 Hz */
42#define M41T00_SQW_8HZ (12<<4) /* 8 Hz */
43#define M41T00_SQW_4HZ (13<<4) /* 4 Hz */
44#define M41T00_SQW_2HZ (14<<4) /* 2 Hz */
45#define M41T00_SQW_1HZ (15<<4) /* 1 Hz */
46
47extern ulong m41t00_get_rtc_time(void);
48extern int m41t00_set_rtc_time(ulong nowtime);
49
50#endif /* _M41T00_H */