|  | /* | 
|  | * 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"); |