Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v2.6.22 413 lines 9.4 kB view raw
1/* 2 * I2C client/driver 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 * This i2c client/driver wedges between the drivers/char/genrtc.c RTC 13 * interface and the SMBus interface of the i2c subsystem. 14 */ 15 16#include <linux/kernel.h> 17#include <linux/module.h> 18#include <linux/interrupt.h> 19#include <linux/i2c.h> 20#include <linux/rtc.h> 21#include <linux/bcd.h> 22#include <linux/workqueue.h> 23#include <linux/platform_device.h> 24#include <linux/m41t00.h> 25#include <asm/time.h> 26#include <asm/rtc.h> 27 28static struct i2c_driver m41t00_driver; 29static struct i2c_client *save_client; 30 31static unsigned short ignore[] = { I2C_CLIENT_END }; 32static unsigned short normal_addr[] = { I2C_CLIENT_END, I2C_CLIENT_END }; 33 34static struct i2c_client_address_data addr_data = { 35 .normal_i2c = normal_addr, 36 .probe = ignore, 37 .ignore = ignore, 38}; 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 99ulong 100m41t00_get_rtc_time(void) 101{ 102 s32 sec, min, hour, day, mon, year; 103 s32 sec1, min1, hour1, day1, mon1, year1; 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 }; 120 121 sec = min = hour = day = mon = year = 0; 122 123 do { 124 if (i2c_transfer(save_client->adapter, msgs, 2) < 0) 125 goto read_err; 126 127 sec1 = sec; 128 min1 = min; 129 hour1 = hour; 130 day1 = day; 131 mon1 = mon; 132 year1 = year; 133 134 sec = buf[m41t00_chip->sec] & 0x7f; 135 min = buf[m41t00_chip->min] & 0x7f; 136 hour = buf[m41t00_chip->hour] & 0x3f; 137 day = buf[m41t00_chip->day] & 0x3f; 138 mon = buf[m41t00_chip->mon] & 0x1f; 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; 148 149 sec = BCD2BIN(sec); 150 min = BCD2BIN(min); 151 hour = BCD2BIN(hour); 152 day = BCD2BIN(day); 153 mon = BCD2BIN(mon); 154 year = BCD2BIN(year); 155 156 year += 1900; 157 if (year < 1970) 158 year += 100; 159 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; 165} 166EXPORT_SYMBOL_GPL(m41t00_get_rtc_time); 167 168static void 169m41t00_set(void *arg) 170{ 171 struct rtc_time tm; 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 }; 189 190 to_tm(nowtime, &tm); 191 tm.tm_year = (tm.tm_year - 1900) % 100; 192 193 sec = BIN2BCD(tm.tm_sec); 194 min = BIN2BCD(tm.tm_min); 195 hour = BIN2BCD(tm.tm_hour); 196 day = BIN2BCD(tm.tm_mday); 197 mon = BIN2BCD(tm.tm_mon); 198 year = BIN2BCD(tm.tm_year); 199 200 /* Read reg values into buf[0..7]/wbuf[1..8] */ 201 if (i2c_transfer(save_client->adapter, msgs, 2) < 0) { 202 dev_err(&save_client->dev, "m41t00_set: Read error\n"); 203 return; 204 } 205 206 wbuf[0] = 0; /* offset into rtc's regs */ 207 buf[m41t00_chip->sec] = (buf[m41t00_chip->sec] & ~0x7f) | (sec & 0x7f); 208 buf[m41t00_chip->min] = (buf[m41t00_chip->min] & ~0x7f) | (min & 0x7f); 209 buf[m41t00_chip->hour] = (buf[m41t00_chip->hour] & ~0x3f) | (hour& 0x3f); 210 buf[m41t00_chip->day] = (buf[m41t00_chip->day] & ~0x3f) | (day & 0x3f); 211 buf[m41t00_chip->mon] = (buf[m41t00_chip->mon] & ~0x1f) | (mon & 0x1f); 212 buf[m41t00_chip->year] = year; 213 214 if (i2c_master_send(save_client, wbuf, 9) < 0) 215 dev_err(&save_client->dev, "m41t00_set: Write error\n"); 216} 217 218static ulong new_time; 219/* well, isn't this API just _lovely_? */ 220static void 221m41t00_barf(struct work_struct *unusable) 222{ 223 m41t00_set(&new_time); 224} 225 226static struct workqueue_struct *m41t00_wq; 227static DECLARE_WORK(m41t00_work, m41t00_barf); 228 229int 230m41t00_set_rtc_time(ulong nowtime) 231{ 232 new_time = nowtime; 233 234 if (in_interrupt()) 235 queue_work(m41t00_wq, &m41t00_work); 236 else 237 m41t00_set(&new_time); 238 239 return 0; 240} 241EXPORT_SYMBOL_GPL(m41t00_set_rtc_time); 242 243/* 244 ***************************************************************************** 245 * 246 * platform_data Driver Interface 247 * 248 ***************************************************************************** 249 */ 250static int __init 251m41t00_platform_probe(struct platform_device *pdev) 252{ 253 struct m41t00_platform_data *pdata; 254 int i; 255 256 if (pdev && (pdata = pdev->dev.platform_data)) { 257 normal_addr[0] = pdata->i2c_addr; 258 259 for (i=0; i<ARRAY_SIZE(m41t00_chip_info_tbl); i++) 260 if (m41t00_chip_info_tbl[i].type == pdata->type) { 261 m41t00_chip = &m41t00_chip_info_tbl[i]; 262 m41t00_chip->sqw_freq = pdata->sqw_freq; 263 return 0; 264 } 265 } 266 return -ENODEV; 267} 268 269static int __exit 270m41t00_platform_remove(struct platform_device *pdev) 271{ 272 return 0; 273} 274 275static struct platform_driver m41t00_platform_driver = { 276 .probe = m41t00_platform_probe, 277 .remove = m41t00_platform_remove, 278 .driver = { 279 .owner = THIS_MODULE, 280 .name = M41T00_DRV_NAME, 281 }, 282}; 283 284/* 285 ***************************************************************************** 286 * 287 * Driver Interface 288 * 289 ***************************************************************************** 290 */ 291static int 292m41t00_probe(struct i2c_adapter *adap, int addr, int kind) 293{ 294 struct i2c_client *client; 295 int rc; 296 297 if (!i2c_check_functionality(adap, I2C_FUNC_I2C 298 | I2C_FUNC_SMBUS_BYTE_DATA)) 299 return 0; 300 301 client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); 302 if (!client) 303 return -ENOMEM; 304 305 strlcpy(client->name, m41t00_chip->name, I2C_NAME_SIZE); 306 client->addr = addr; 307 client->adapter = adap; 308 client->driver = &m41t00_driver; 309 310 if ((rc = i2c_attach_client(client))) 311 goto attach_err; 312 313 if (m41t00_chip->type != M41T00_TYPE_M41T00) { 314 /* If asked, disable SQW, set SQW frequency & re-enable */ 315 if (m41t00_chip->sqw_freq) 316 if (((rc = i2c_smbus_read_byte_data(client, 317 m41t00_chip->alarm_mon)) < 0) 318 || ((rc = i2c_smbus_write_byte_data(client, 319 m41t00_chip->alarm_mon, rc & ~0x40)) <0) 320 || ((rc = i2c_smbus_write_byte_data(client, 321 m41t00_chip->sqw, 322 m41t00_chip->sqw_freq)) < 0) 323 || ((rc = i2c_smbus_write_byte_data(client, 324 m41t00_chip->alarm_mon, rc | 0x40)) <0)) 325 goto sqw_err; 326 327 /* Make sure HT (Halt Update) bit is cleared */ 328 if ((rc = i2c_smbus_read_byte_data(client, 329 m41t00_chip->alarm_hour)) < 0) 330 goto ht_err; 331 332 if (rc & 0x40) 333 if ((rc = i2c_smbus_write_byte_data(client, 334 m41t00_chip->alarm_hour, rc & ~0x40))<0) 335 goto ht_err; 336 } 337 338 /* Make sure ST (stop) bit is cleared */ 339 if ((rc = i2c_smbus_read_byte_data(client, m41t00_chip->sec)) < 0) 340 goto st_err; 341 342 if (rc & 0x80) 343 if ((rc = i2c_smbus_write_byte_data(client, m41t00_chip->sec, 344 rc & ~0x80)) < 0) 345 goto st_err; 346 347 m41t00_wq = create_singlethread_workqueue(m41t00_chip->name); 348 save_client = client; 349 return 0; 350 351st_err: 352 dev_err(&client->dev, "m41t00_probe: Can't clear ST bit\n"); 353 goto attach_err; 354ht_err: 355 dev_err(&client->dev, "m41t00_probe: Can't clear HT bit\n"); 356 goto attach_err; 357sqw_err: 358 dev_err(&client->dev, "m41t00_probe: Can't set SQW Frequency\n"); 359attach_err: 360 kfree(client); 361 return rc; 362} 363 364static int 365m41t00_attach(struct i2c_adapter *adap) 366{ 367 return i2c_probe(adap, &addr_data, m41t00_probe); 368} 369 370static int 371m41t00_detach(struct i2c_client *client) 372{ 373 int rc; 374 375 if ((rc = i2c_detach_client(client)) == 0) { 376 kfree(client); 377 destroy_workqueue(m41t00_wq); 378 } 379 return rc; 380} 381 382static struct i2c_driver m41t00_driver = { 383 .driver = { 384 .name = M41T00_DRV_NAME, 385 }, 386 .id = I2C_DRIVERID_STM41T00, 387 .attach_adapter = m41t00_attach, 388 .detach_client = m41t00_detach, 389}; 390 391static int __init 392m41t00_init(void) 393{ 394 int rc; 395 396 if (!(rc = platform_driver_register(&m41t00_platform_driver))) 397 rc = i2c_add_driver(&m41t00_driver); 398 return rc; 399} 400 401static void __exit 402m41t00_exit(void) 403{ 404 i2c_del_driver(&m41t00_driver); 405 platform_driver_unregister(&m41t00_platform_driver); 406} 407 408module_init(m41t00_init); 409module_exit(m41t00_exit); 410 411MODULE_AUTHOR("Mark A. Greer <mgreer@mvista.com>"); 412MODULE_DESCRIPTION("ST Microelectronics M41T00 RTC I2C Client Driver"); 413MODULE_LICENSE("GPL");