diff options
author | Antony Pavlov <antonynpavlov@gmail.com> | 2014-07-30 00:10:19 +0400 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2014-08-02 06:45:43 +0200 |
commit | 686df208b304ea6a8970005aa58c9e112dd68cb3 (patch) | |
tree | 4307320cf18cb670b4750ffc8830c14a1c52b32f /drivers/rtc/rtc-ds1307.c | |
parent | 14963b8c461b9d1c310554b414238a6e97d1a8c0 (diff) | |
download | barebox-686df208b304ea6a8970005aa58c9e112dd68cb3.tar.gz barebox-686df208b304ea6a8970005aa58c9e112dd68cb3.tar.xz |
rtc: import ds1307 driver from linux-3.15
Current ds1307 rtc driver supports only ds1307 and ds1338 chips;
it has no nvram support at the moment.
Signed-off-by: Antony Pavlov <antonynpavlov@gmail.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/rtc/rtc-ds1307.c')
-rw-r--r-- | drivers/rtc/rtc-ds1307.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-ds1307.c b/drivers/rtc/rtc-ds1307.c new file mode 100644 index 0000000000..725f15ab57 --- /dev/null +++ b/drivers/rtc/rtc-ds1307.c @@ -0,0 +1,347 @@ +/* + * rtc-ds1307.c - RTC driver for some mostly-compatible I2C chips. + * + * This code was ported from linux-3.15 kernel by Antony Pavlov. + * + * Copyright (C) 2005 James Chapman (ds1337 core) + * Copyright (C) 2006 David Brownell + * Copyright (C) 2009 Matthias Fuchs (rx8025 support) + * Copyright (C) 2012 Bertrand Achard (nvram access fixes) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <common.h> +#include <init.h> +#include <driver.h> +#include <xfuncs.h> +#include <errno.h> +#include <i2c/i2c.h> +#include <rtc.h> +#include <linux/rtc.h> +#include <linux/bcd.h> + +/* + * We can't determine type by probing, but if we expect pre-Linux code + * to have set the chip up as a clock (turning on the oscillator and + * setting the date and time), Linux can ignore the non-clock features. + * That's a natural job for a factory or repair bench. + */ +enum ds_type { + ds_1307, + ds_1338, + last_ds_type /* always last */ +}; + +/* RTC registers don't differ much, except for the century flag */ +#define DS1307_REG_SECS 0x00 /* 00-59 */ +# define DS1307_BIT_CH 0x80 +# define DS1340_BIT_nEOSC 0x80 +#define DS1307_REG_MIN 0x01 /* 00-59 */ +#define DS1307_REG_HOUR 0x02 /* 00-23, or 1-12{am,pm} */ +# define DS1307_BIT_12HR 0x40 /* in REG_HOUR */ +# define DS1307_BIT_PM 0x20 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY_EN 0x80 /* in REG_HOUR */ +# define DS1340_BIT_CENTURY 0x40 /* in REG_HOUR */ +#define DS1307_REG_WDAY 0x03 /* 01-07 */ +#define DS1307_REG_MDAY 0x04 /* 01-31 */ +#define DS1307_REG_MONTH 0x05 /* 01-12 */ +# define DS1337_BIT_CENTURY 0x80 /* in REG_MONTH */ +#define DS1307_REG_YEAR 0x06 /* 00-99 */ + +/* + * Other registers (control, status, alarms, trickle charge, NVRAM, etc) + * start at 7, and they differ a LOT. Only control and status matter for + * basic RTC date and time functionality; be careful using them. + */ +#define DS1307_REG_CONTROL 0x07 /* or ds1338 */ +# define DS1307_BIT_OUT 0x80 +# define DS1338_BIT_OSF 0x20 +# define DS1307_BIT_SQWE 0x10 +# define DS1307_BIT_RS1 0x02 +# define DS1307_BIT_RS0 0x01 + +struct ds1307 { + struct rtc_device rtc; + u8 offset; /* register's offset */ + u8 regs[11]; + enum ds_type type; + unsigned long flags; + struct i2c_client *client; + s32 (*read_block_data)(const struct i2c_client *client, u8 command, + u8 length, u8 *values); + s32 (*write_block_data)(const struct i2c_client *client, u8 command, + u8 length, const u8 *values); +}; + +static struct platform_device_id ds1307_id[] = { + { "ds1307", ds_1307 }, + { "ds1338", ds_1338 }, + { "pt7c4338", ds_1307 }, + { } +}; + +/*----------------------------------------------------------------------*/ + +#define BLOCK_DATA_MAX_TRIES 10 + +static s32 ds1307_read_block_data_once(const struct i2c_client *client, + u8 command, u8 length, u8 *values) +{ + s32 i, data; + + for (i = 0; i < length; i++) { + data = i2c_smbus_read_byte_data(client, command + i); + if (data < 0) + return data; + values[i] = data; + } + return i; +} + +static s32 ds1307_read_block_data(const struct i2c_client *client, u8 command, + u8 length, u8 *values) +{ + u8 oldvalues[255]; + s32 ret; + int tries = 0; + + dev_dbg(&client->dev, "ds1307_read_block_data (length=%d)\n", length); + ret = ds1307_read_block_data_once(client, command, length, values); + if (ret < 0) + return ret; + do { + if (++tries > BLOCK_DATA_MAX_TRIES) { + dev_err(&client->dev, + "ds1307_read_block_data failed\n"); + return -EIO; + } + memcpy(oldvalues, values, length); + ret = ds1307_read_block_data_once(client, command, length, + values); + if (ret < 0) + return ret; + } while (memcmp(oldvalues, values, length)); + return length; +} + +static s32 ds1307_write_block_data(const struct i2c_client *client, u8 command, + u8 length, const u8 *values) +{ + u8 currvalues[255]; + int tries = 0; + + dev_dbg(&client->dev, "ds1307_write_block_data (length=%d)\n", length); + do { + s32 i, ret; + + if (++tries > BLOCK_DATA_MAX_TRIES) { + dev_err(&client->dev, + "ds1307_write_block_data failed\n"); + return -EIO; + } + for (i = 0; i < length; i++) { + ret = i2c_smbus_write_byte_data(client, command + i, + values[i]); + if (ret < 0) + return ret; + } + ret = ds1307_read_block_data_once(client, command, length, + currvalues); + if (ret < 0) + return ret; + } while (memcmp(currvalues, values, length)); + return length; +} + +static inline struct ds1307 *to_ds1307_priv(struct rtc_device *rtcdev) +{ + return container_of(rtcdev, struct ds1307, rtc); +} + +static int ds1307_get_time(struct rtc_device *rtcdev, struct rtc_time *t) +{ + struct device_d *dev = rtcdev->dev; + struct ds1307 *ds1307 = to_ds1307_priv(rtcdev); + int tmp; + + /* read the RTC date and time registers all at once */ + tmp = ds1307->read_block_data(ds1307->client, + ds1307->offset, 7, ds1307->regs); + if (tmp != 7) { + dev_err(dev, "%s error %d\n", "read", tmp); + return -EIO; + } + + dev_dbg(dev, "%s: %7ph\n", "read", ds1307->regs); + + t->tm_sec = bcd2bin(ds1307->regs[DS1307_REG_SECS] & 0x7f); + t->tm_min = bcd2bin(ds1307->regs[DS1307_REG_MIN] & 0x7f); + tmp = ds1307->regs[DS1307_REG_HOUR] & 0x3f; + t->tm_hour = bcd2bin(tmp); + t->tm_wday = bcd2bin(ds1307->regs[DS1307_REG_WDAY] & 0x07) - 1; + t->tm_mday = bcd2bin(ds1307->regs[DS1307_REG_MDAY] & 0x3f); + tmp = ds1307->regs[DS1307_REG_MONTH] & 0x1f; + t->tm_mon = bcd2bin(tmp) - 1; + + /* assume 20YY not 19YY, and ignore DS1337_BIT_CENTURY */ + t->tm_year = bcd2bin(ds1307->regs[DS1307_REG_YEAR]) + 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + /* initial clock setting can be undefined */ + return rtc_valid_tm(t); +} + +static int ds1307_set_time(struct rtc_device *rtcdev, struct rtc_time *t) +{ + struct device_d *dev = rtcdev->dev; + struct ds1307 *ds1307 = to_ds1307_priv(rtcdev); + int result; + int tmp; + u8 *buf = ds1307->regs; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", t->tm_sec, t->tm_min, + t->tm_hour, t->tm_mday, + t->tm_mon, t->tm_year, t->tm_wday); + + buf[DS1307_REG_SECS] = bin2bcd(t->tm_sec); + buf[DS1307_REG_MIN] = bin2bcd(t->tm_min); + buf[DS1307_REG_HOUR] = bin2bcd(t->tm_hour); + buf[DS1307_REG_WDAY] = bin2bcd(t->tm_wday + 1); + buf[DS1307_REG_MDAY] = bin2bcd(t->tm_mday); + buf[DS1307_REG_MONTH] = bin2bcd(t->tm_mon + 1); + + /* assume 20YY not 19YY */ + tmp = t->tm_year - 100; + buf[DS1307_REG_YEAR] = bin2bcd(tmp); + + dev_dbg(dev, "%s: %7ph\n", "write", buf); + + result = ds1307->write_block_data(ds1307->client, + ds1307->offset, 7, buf); + if (result < 0) { + dev_err(dev, "%s error %d\n", "write", result); + return result; + } + return 0; +} + +static const struct rtc_class_ops ds13xx_rtc_ops = { + .read_time = ds1307_get_time, + .set_time = ds1307_set_time, +}; + +static int ds1307_probe(struct device_d *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ds1307 *ds1307; + int err = -ENODEV; + int tmp; + unsigned char *buf; + unsigned long driver_data; + + ds1307 = xzalloc(sizeof(struct ds1307)); + + err = dev_get_drvdata(dev, &driver_data); + if (err) + goto exit; + + ds1307->client = client; + ds1307->type = driver_data; + + buf = ds1307->regs; + + ds1307->read_block_data = ds1307_read_block_data; + ds1307->write_block_data = ds1307_write_block_data; + +read_rtc: + /* read RTC registers */ + tmp = ds1307->read_block_data(client, ds1307->offset, 8, buf); + if (tmp != 8) { + dev_dbg(&client->dev, "read error %d\n", tmp); + err = -EIO; + goto exit; + } + + /* + * minimal sanity checking; some chips (like DS1340) don't + * specify the extra bits as must-be-zero, but there are + * still a few values that are clearly out-of-range. + */ + tmp = ds1307->regs[DS1307_REG_SECS]; + switch (ds1307->type) { + case ds_1307: + /* clock halted? turn it on, so clock can tick. */ + if (tmp & DS1307_BIT_CH) { + i2c_smbus_write_byte_data(client, DS1307_REG_SECS, 0); + dev_warn(&client->dev, "SET TIME!\n"); + goto read_rtc; + } + break; + case ds_1338: + /* clock halted? turn it on, so clock can tick. */ + if (tmp & DS1307_BIT_CH) + i2c_smbus_write_byte_data(client, DS1307_REG_SECS, 0); + + /* oscillator fault? clear flag, and warn */ + if (ds1307->regs[DS1307_REG_CONTROL] & DS1338_BIT_OSF) { + i2c_smbus_write_byte_data(client, DS1307_REG_CONTROL, + ds1307->regs[DS1307_REG_CONTROL] + & ~DS1338_BIT_OSF); + dev_warn(&client->dev, "SET TIME!\n"); + goto read_rtc; + } + break; + default: + break; + } + + tmp = ds1307->regs[DS1307_REG_HOUR]; + switch (ds1307->type) { + default: + if (!(tmp & DS1307_BIT_12HR)) + break; + + /* + * Be sure we're in 24 hour mode. Multi-master systems + * take note... + */ + tmp = bcd2bin(tmp & 0x1f); + if (tmp == 12) + tmp = 0; + if (ds1307->regs[DS1307_REG_HOUR] & DS1307_BIT_PM) + tmp += 12; + i2c_smbus_write_byte_data(client, + ds1307->offset + DS1307_REG_HOUR, + bin2bcd(tmp)); + } + + ds1307->rtc.ops = &ds13xx_rtc_ops; + ds1307->rtc.dev = dev; + + err = rtc_register(&ds1307->rtc); + +exit: + return err; +} + +static struct driver_d ds1307_driver = { + .name = "rtc-ds1307", + .probe = ds1307_probe, + .id_table = ds1307_id, +}; + +static int __init ds1307_init(void) +{ + return i2c_driver_register(&ds1307_driver); +} +device_initcall(ds1307_init); |