/* * drivers/i2c/chips/ds1374.c * * I2C client/driver for the Maxim/Dallas DS1374 Real-Time Clock * * Author: Randy Vinson <rvinson@mvista.com> * * Based on the m41t00.c by Mark Greer <mgreer@mvista.com> * * 2005 (c) MontaVista Software, Inc. This file is licensed under * the terms of the GNU General Public License version 2. This program * is licensed "as is" without any warranty of any kind, whether express * or implied. */ /* * This i2c client/driver wedges between the drivers/char/genrtc.c RTC * interface and the SMBus interface of the i2c subsystem. * It would be more efficient to use i2c msgs/i2c_transfer directly but, as * recommened in .../Documentation/i2c/writing-clients section * "Sending and receiving", using SMBus level communication is preferred. */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/rtc.h> #include <linux/bcd.h> #include <linux/mutex.h> #include <linux/workqueue.h> #define DS1374_REG_TOD0 0x00 #define DS1374_REG_TOD1 0x01 #define DS1374_REG_TOD2 0x02 #define DS1374_REG_TOD3 0x03 #define DS1374_REG_WDALM0 0x04 #define DS1374_REG_WDALM1 0x05 #define DS1374_REG_WDALM2 0x06 #define DS1374_REG_CR 0x07 #define DS1374_REG_SR 0x08 #define DS1374_REG_SR_OSF 0x80 #define DS1374_REG_TCR 0x09 #define DS1374_DRV_NAME "ds1374" static DEFINE_MUTEX(ds1374_mutex); static struct i2c_driver ds1374_driver; static struct i2c_client *save_client; static unsigned short ignore[] = { I2C_CLIENT_END }; static unsigned short normal_addr[] = { 0x68, I2C_CLIENT_END }; static struct i2c_client_address_data addr_data = { .normal_i2c = normal_addr, .probe = ignore, .ignore = ignore, }; static ulong ds1374_read_rtc(void) { ulong time = 0; int reg = DS1374_REG_WDALM0; while (reg--) { s32 tmp; if ((tmp = i2c_smbus_read_byte_data(save_client, reg)) < 0) { dev_warn(&save_client->dev, "can't read from rtc chip\n"); return 0; } time = (time << 8) | (tmp & 0xff); } return time; } static void ds1374_write_rtc(ulong time) { int reg; for (reg = DS1374_REG_TOD0; reg < DS1374_REG_WDALM0; reg++) { if (i2c_smbus_write_byte_data(save_client, reg, time & 0xff) < 0) { dev_warn(&save_client->dev, "can't write to rtc chip\n"); break; } time = time >> 8; } } static void ds1374_check_rtc_status(void) { s32 tmp; tmp = i2c_smbus_read_byte_data(save_client, DS1374_REG_SR); if (tmp < 0) { dev_warn(&save_client->dev, "can't read status from rtc chip\n"); return; } if (tmp & DS1374_REG_SR_OSF) { dev_warn(&save_client->dev, "oscillator discontinuity flagged, time unreliable\n"); tmp &= ~DS1374_REG_SR_OSF; tmp = i2c_smbus_write_byte_data(save_client, DS1374_REG_SR, tmp & 0xff); if (tmp < 0) dev_warn(&save_client->dev, "can't clear discontinuity notification\n"); } } ulong ds1374_get_rtc_time(void) { ulong t1, t2; int limit = 10; /* arbitrary retry limit */ mutex_lock(&ds1374_mutex); /* * Since the reads are being performed one byte at a time using * the SMBus vs a 4-byte i2c transfer, there is a chance that a * carry will occur during the read. To detect this, 2 reads are * performed and compared. */ do { t1 = ds1374_read_rtc(); t2 = ds1374_read_rtc(); } while (t1 != t2 && limit--); mutex_unlock(&ds1374_mutex); if (t1 != t2) { dev_warn(&save_client->dev, "can't get consistent time from rtc chip\n"); t1 = 0; } return t1; } static void ds1374_set_work(void *arg) { ulong t1, t2; int limit = 10; /* arbitrary retry limit */ t1 = *(ulong *) arg; mutex_lock(&ds1374_mutex); /* * Since the writes are being performed one byte at a time using * the SMBus vs a 4-byte i2c transfer, there is a chance that a * carry will occur during the write. To detect this, the write * value is read back and compared. */ do { ds1374_write_rtc(t1); t2 = ds1374_read_rtc(); } while (t1 != t2 && limit--); mutex_unlock(&ds1374_mutex); if (t1 != t2) dev_warn(&save_client->dev, "can't confirm time set from rtc chip\n"); } static ulong new_time; static struct workqueue_struct *ds1374_workqueue; static DECLARE_WORK(ds1374_work, ds1374_set_work, &new_time); int ds1374_set_rtc_time(ulong nowtime) { new_time = nowtime; if (in_interrupt()) queue_work(ds1374_workqueue, &ds1374_work); else ds1374_set_work(&new_time); return 0; } /* ***************************************************************************** * * Driver Interface * ***************************************************************************** */ static int ds1374_probe(struct i2c_adapter *adap, int addr, int kind) { struct i2c_client *client; int rc; client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL); if (!client) return -ENOMEM; strncpy(client->name, DS1374_DRV_NAME, I2C_NAME_SIZE); client->addr = addr; client->adapter = adap; client->driver = &ds1374_driver; ds1374_workqueue = create_singlethread_workqueue("ds1374"); if ((rc = i2c_attach_client(client)) != 0) { kfree(client); return rc; } save_client = client; ds1374_check_rtc_status(); return 0; } static int ds1374_attach(struct i2c_adapter *adap) { return i2c_probe(adap, &addr_data, ds1374_probe); } static int ds1374_detach(struct i2c_client *client) { int rc; if ((rc = i2c_detach_client(client)) == 0) { kfree(i2c_get_clientdata(client)); destroy_workqueue(ds1374_workqueue); } return rc; } static struct i2c_driver ds1374_driver = { .driver = { .name = DS1374_DRV_NAME, }, .id = I2C_DRIVERID_DS1374, .attach_adapter = ds1374_attach, .detach_client = ds1374_detach, }; static int __init ds1374_init(void) { return i2c_add_driver(&ds1374_driver); } static void __exit ds1374_exit(void) { i2c_del_driver(&ds1374_driver); } module_init(ds1374_init); module_exit(ds1374_exit); MODULE_AUTHOR("Randy Vinson <rvinson@mvista.com>"); MODULE_DESCRIPTION("Maxim/Dallas DS1374 RTC I2C Client Driver"); MODULE_LICENSE("GPL");