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