summaryrefslogtreecommitdiff
path: root/drivers/rtc
diff options
context:
space:
mode:
authorSergey Lapin <slapin@ossfans.org>2008-06-12 15:21:55 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2008-06-12 18:05:42 -0700
commitc6d8f400cc7610f04177f81168c19b8407cb48c3 (patch)
tree2313cdd3e506c1b9e8050a04690bf7cfd4761b04 /drivers/rtc
parente6d2bb2bacb43ff03b0f458108d71981d58e775a (diff)
rtc: Ramtron FM3130 RTC support
Ramtron FM3130 is a chip with two separate devices inside, RTC clock and FRAM. This driver provides only RTC functionality. This chip is met in lots of custom boards with AT91SAMXXXX CPU I work with, is cheap and in no way better or worse than any other RTC on market. While it is mostly met on much smaller devices, I think it is great to have it supported in Linux. Signed-off-by: Sergey Lapin <slapin@ossfans.org> Signed-off-by: Alessandro Zummo <a.zummo@towertech.it> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/rtc')
-rw-r--r--drivers/rtc/Kconfig11
-rw-r--r--drivers/rtc/Makefile3
-rw-r--r--drivers/rtc/rtc-fm3130.c501
3 files changed, 514 insertions, 1 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 60f8afc7a56e..4949dc4859be 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -256,6 +256,17 @@ config RTC_DRV_S35390A
This driver can also be built as a module. If so the module
will be called rtc-s35390a.
+config RTC_DRV_FM3130
+ tristate "Ramtron FM3130"
+ help
+ If you say Y here you will get support for the
+ Ramtron FM3130 RTC chips.
+ Ramtron FM3130 is a chip with two separate devices inside,
+ RTC clock and FRAM. This driver provides only RTC functionality.
+
+ This driver can also be built as a module. If so the module
+ will be called rtc-fm3130.
+
endif # I2C
comment "SPI RTC drivers"
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index ebe871cf16c1..b6e14d51670b 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_RTC_DRV_DS1553) += rtc-ds1553.o
obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o
obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o
obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
+obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o
@@ -41,6 +42,7 @@ obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
+obj-$(CONFIG_RTC_DRV_PPC) += rtc-ppc.o
obj-$(CONFIG_RTC_DRV_R9701) += rtc-r9701.o
obj-$(CONFIG_RTC_DRV_RS5C313) += rtc-rs5c313.o
obj-$(CONFIG_RTC_DRV_RS5C348) += rtc-rs5c348.o
@@ -54,4 +56,3 @@ obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o
obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o
obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
-obj-$(CONFIG_RTC_DRV_PPC) += rtc-ppc.o
diff --git a/drivers/rtc/rtc-fm3130.c b/drivers/rtc/rtc-fm3130.c
new file mode 100644
index 000000000000..11644c8fca82
--- /dev/null
+++ b/drivers/rtc/rtc-fm3130.c
@@ -0,0 +1,501 @@
+/*
+ * rtc-fm3130.c - RTC driver for Ramtron FM3130 I2C chip.
+ *
+ * Copyright (C) 2008 Sergey Lapin
+ * Based on ds1307 driver by James Chapman and David Brownell
+ *
+ * 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 <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+
+#define FM3130_RTC_CONTROL (0x0)
+#define FM3130_CAL_CONTROL (0x1)
+#define FM3130_RTC_SECONDS (0x2)
+#define FM3130_RTC_MINUTES (0x3)
+#define FM3130_RTC_HOURS (0x4)
+#define FM3130_RTC_DAY (0x5)
+#define FM3130_RTC_DATE (0x6)
+#define FM3130_RTC_MONTHS (0x7)
+#define FM3130_RTC_YEARS (0x8)
+
+#define FM3130_ALARM_SECONDS (0x9)
+#define FM3130_ALARM_MINUTES (0xa)
+#define FM3130_ALARM_HOURS (0xb)
+#define FM3130_ALARM_DATE (0xc)
+#define FM3130_ALARM_MONTHS (0xd)
+#define FM3130_ALARM_WP_CONTROL (0xe)
+
+#define FM3130_CAL_CONTROL_BIT_nOSCEN (1 << 7) /* Osciallator enabled */
+#define FM3130_RTC_CONTROL_BIT_LB (1 << 7) /* Low battery */
+#define FM3130_RTC_CONTROL_BIT_AF (1 << 6) /* Alarm flag */
+#define FM3130_RTC_CONTROL_BIT_CF (1 << 5) /* Century overflow */
+#define FM3130_RTC_CONTROL_BIT_POR (1 << 4) /* Power on reset */
+#define FM3130_RTC_CONTROL_BIT_AEN (1 << 3) /* Alarm enable */
+#define FM3130_RTC_CONTROL_BIT_CAL (1 << 2) /* Calibration mode */
+#define FM3130_RTC_CONTROL_BIT_WRITE (1 << 1) /* W=1 -> write mode W=0 normal */
+#define FM3130_RTC_CONTROL_BIT_READ (1 << 0) /* R=1 -> read mode R=0 normal */
+
+#define FM3130_CLOCK_REGS 7
+#define FM3130_ALARM_REGS 5
+
+struct fm3130 {
+ u8 reg_addr_time;
+ u8 reg_addr_alarm;
+ u8 regs[15];
+ struct i2c_msg msg[4];
+ struct i2c_client *client;
+ struct rtc_device *rtc;
+ int data_valid;
+ int alarm;
+};
+static const struct i2c_device_id fm3130_id[] = {
+ { "fm3130-rtc", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, fm3130_id);
+
+#define FM3130_MODE_NORMAL 0
+#define FM3130_MODE_WRITE 1
+#define FM3130_MODE_READ 2
+
+static void fm3130_rtc_mode(struct device *dev, int mode)
+{
+ struct fm3130 *fm3130 = dev_get_drvdata(dev);
+
+ fm3130->regs[FM3130_RTC_CONTROL] =
+ i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
+ switch (mode) {
+ case FM3130_MODE_NORMAL:
+ fm3130->regs[FM3130_RTC_CONTROL] &=
+ ~(FM3130_RTC_CONTROL_BIT_WRITE |
+ FM3130_RTC_CONTROL_BIT_READ);
+ break;
+ case FM3130_MODE_WRITE:
+ fm3130->regs[FM3130_RTC_CONTROL] |= FM3130_RTC_CONTROL_BIT_WRITE;
+ break;
+ case FM3130_MODE_READ:
+ fm3130->regs[FM3130_RTC_CONTROL] |= FM3130_RTC_CONTROL_BIT_READ;
+ break;
+ default:
+ dev_dbg(dev, "invalid mode %d\n", mode);
+ break;
+ }
+ /* Checking for alarm */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
+ fm3130->alarm = 1;
+ fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
+ }
+ i2c_smbus_write_byte_data(fm3130->client,
+ FM3130_RTC_CONTROL, fm3130->regs[FM3130_RTC_CONTROL]);
+}
+
+static int fm3130_get_time(struct device *dev, struct rtc_time *t)
+{
+ struct fm3130 *fm3130 = dev_get_drvdata(dev);
+ int tmp;
+
+ if (!fm3130->data_valid) {
+ /* We have invalid data in RTC, probably due
+ to battery faults or other problems. Return EIO
+ for now, it will allow us to set data later insted
+ of error during probing which disables device */
+ return -EIO;
+ }
+ fm3130_rtc_mode(dev, FM3130_MODE_READ);
+
+ /* read the RTC date and time registers all at once */
+ tmp = i2c_transfer(to_i2c_adapter(fm3130->client->dev.parent),
+ fm3130->msg, 2);
+ if (tmp != 2) {
+ dev_err(dev, "%s error %d\n", "read", tmp);
+ return -EIO;
+ }
+
+ fm3130_rtc_mode(dev, FM3130_MODE_NORMAL);
+
+ dev_dbg(dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x"
+ "%02x %02x %02x %02x %02x %02x %02x\n",
+ "read",
+ fm3130->regs[0], fm3130->regs[1],
+ fm3130->regs[2], fm3130->regs[3],
+ fm3130->regs[4], fm3130->regs[5],
+ fm3130->regs[6], fm3130->regs[7],
+ fm3130->regs[8], fm3130->regs[9],
+ fm3130->regs[0xa], fm3130->regs[0xb],
+ fm3130->regs[0xc], fm3130->regs[0xd],
+ fm3130->regs[0xe]);
+
+ t->tm_sec = BCD2BIN(fm3130->regs[FM3130_RTC_SECONDS] & 0x7f);
+ t->tm_min = BCD2BIN(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f);
+ tmp = fm3130->regs[FM3130_RTC_HOURS] & 0x3f;
+ t->tm_hour = BCD2BIN(tmp);
+ t->tm_wday = BCD2BIN(fm3130->regs[FM3130_RTC_DAY] & 0x07) - 1;
+ t->tm_mday = BCD2BIN(fm3130->regs[FM3130_RTC_DATE] & 0x3f);
+ tmp = fm3130->regs[FM3130_RTC_MONTHS] & 0x1f;
+ t->tm_mon = BCD2BIN(tmp) - 1;
+
+ /* assume 20YY not 19YY, and ignore CF bit */
+ t->tm_year = BCD2BIN(fm3130->regs[FM3130_RTC_YEARS]) + 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 fm3130_set_time(struct device *dev, struct rtc_time *t)
+{
+ struct fm3130 *fm3130 = dev_get_drvdata(dev);
+ int tmp, i;
+ u8 *buf = fm3130->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);
+
+ /* first register addr */
+ buf[FM3130_RTC_SECONDS] = BIN2BCD(t->tm_sec);
+ buf[FM3130_RTC_MINUTES] = BIN2BCD(t->tm_min);
+ buf[FM3130_RTC_HOURS] = BIN2BCD(t->tm_hour);
+ buf[FM3130_RTC_DAY] = BIN2BCD(t->tm_wday + 1);
+ buf[FM3130_RTC_DATE] = BIN2BCD(t->tm_mday);
+ buf[FM3130_RTC_MONTHS] = BIN2BCD(t->tm_mon + 1);
+
+ /* assume 20YY not 19YY */
+ tmp = t->tm_year - 100;
+ buf[FM3130_RTC_YEARS] = BIN2BCD(tmp);
+
+ dev_dbg(dev, "%s: %02x %02x %02x %02x %02x %02x %02x"
+ "%02x %02x %02x %02x %02x %02x %02x %02x\n",
+ "write", buf[0], buf[1], buf[2], buf[3],
+ buf[4], buf[5], buf[6], buf[7],
+ buf[8], buf[9], buf[0xa], buf[0xb],
+ buf[0xc], buf[0xd], buf[0xe]);
+
+ fm3130_rtc_mode(dev, FM3130_MODE_WRITE);
+
+ /* Writing time registers, we don't support multibyte transfers */
+ for (i = 0; i < FM3130_CLOCK_REGS; i++) {
+ i2c_smbus_write_byte_data(fm3130->client,
+ FM3130_RTC_SECONDS + i,
+ fm3130->regs[FM3130_RTC_SECONDS + i]);
+ }
+
+ fm3130_rtc_mode(dev, FM3130_MODE_NORMAL);
+
+ /* We assume here that data are valid once written */
+ if (!fm3130->data_valid)
+ fm3130->data_valid = 1;
+ return 0;
+}
+
+static int fm3130_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct fm3130 *fm3130 = dev_get_drvdata(dev);
+ int tmp;
+ struct rtc_time *tm = &alrm->time;
+ /* read the RTC alarm registers all at once */
+ tmp = i2c_transfer(to_i2c_adapter(fm3130->client->dev.parent),
+ &fm3130->msg[2], 2);
+ if (tmp != 2) {
+ dev_err(dev, "%s error %d\n", "read", tmp);
+ return -EIO;
+ }
+ dev_dbg(dev, "alarm read %02x %02x %02x %02x %02x\n",
+ fm3130->regs[FM3130_ALARM_SECONDS],
+ fm3130->regs[FM3130_ALARM_MINUTES],
+ fm3130->regs[FM3130_ALARM_HOURS],
+ fm3130->regs[FM3130_ALARM_DATE],
+ fm3130->regs[FM3130_ALARM_MONTHS]);
+
+
+ tm->tm_sec = BCD2BIN(fm3130->regs[FM3130_ALARM_SECONDS] & 0x7F);
+ tm->tm_min = BCD2BIN(fm3130->regs[FM3130_ALARM_MINUTES] & 0x7F);
+ tm->tm_hour = BCD2BIN(fm3130->regs[FM3130_ALARM_HOURS] & 0x3F);
+ tm->tm_mday = BCD2BIN(fm3130->regs[FM3130_ALARM_DATE] & 0x3F);
+ tm->tm_mon = BCD2BIN(fm3130->regs[FM3130_ALARM_MONTHS] & 0x1F);
+ if (tm->tm_mon > 0)
+ tm->tm_mon -= 1; /* RTC is 1-12, tm_mon is 0-11 */
+ dev_dbg(dev, "%s secs=%d, mins=%d, "
+ "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
+ "read alarm", tm->tm_sec, tm->tm_min,
+ tm->tm_hour, tm->tm_mday,
+ tm->tm_mon, tm->tm_year, tm->tm_wday);
+
+ return 0;
+}
+
+static int fm3130_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct fm3130 *fm3130 = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alrm->time;
+ int i;
+
+ dev_dbg(dev, "%s secs=%d, mins=%d, "
+ "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n",
+ "write alarm", tm->tm_sec, tm->tm_min,
+ tm->tm_hour, tm->tm_mday,
+ tm->tm_mon, tm->tm_year, tm->tm_wday);
+
+ if (tm->tm_sec != -1)
+ fm3130->regs[FM3130_ALARM_SECONDS] =
+ BIN2BCD(tm->tm_sec) | 0x80;
+
+ if (tm->tm_min != -1)
+ fm3130->regs[FM3130_ALARM_MINUTES] =
+ BIN2BCD(tm->tm_min) | 0x80;
+
+ if (tm->tm_hour != -1)
+ fm3130->regs[FM3130_ALARM_HOURS] =
+ BIN2BCD(tm->tm_hour) | 0x80;
+
+ if (tm->tm_mday != -1)
+ fm3130->regs[FM3130_ALARM_DATE] =
+ BIN2BCD(tm->tm_mday) | 0x80;
+
+ if (tm->tm_mon != -1)
+ fm3130->regs[FM3130_ALARM_MONTHS] =
+ BIN2BCD(tm->tm_mon + 1) | 0x80;
+
+ dev_dbg(dev, "alarm write %02x %02x %02x %02x %02x\n",
+ fm3130->regs[FM3130_ALARM_SECONDS],
+ fm3130->regs[FM3130_ALARM_MINUTES],
+ fm3130->regs[FM3130_ALARM_HOURS],
+ fm3130->regs[FM3130_ALARM_DATE],
+ fm3130->regs[FM3130_ALARM_MONTHS]);
+ /* Writing time registers, we don't support multibyte transfers */
+ for (i = 0; i < FM3130_ALARM_REGS; i++) {
+ i2c_smbus_write_byte_data(fm3130->client,
+ FM3130_ALARM_SECONDS + i,
+ fm3130->regs[FM3130_ALARM_SECONDS + i]);
+ }
+ fm3130->regs[FM3130_RTC_CONTROL] =
+ i2c_smbus_read_byte_data(fm3130->client, FM3130_RTC_CONTROL);
+ /* Checking for alarm */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
+ fm3130->alarm = 1;
+ fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
+ }
+ if (alrm->enabled) {
+ i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
+ (fm3130->regs[FM3130_RTC_CONTROL] &
+ ~(FM3130_RTC_CONTROL_BIT_CAL)) |
+ FM3130_RTC_CONTROL_BIT_AEN);
+ } else {
+ i2c_smbus_write_byte_data(fm3130->client, FM3130_RTC_CONTROL,
+ fm3130->regs[FM3130_RTC_CONTROL] &
+ ~(FM3130_RTC_CONTROL_BIT_AEN));
+ }
+ return 0;
+}
+
+static const struct rtc_class_ops fm3130_rtc_ops = {
+ .read_time = fm3130_get_time,
+ .set_time = fm3130_set_time,
+ .read_alarm = fm3130_read_alarm,
+ .set_alarm = fm3130_set_alarm,
+};
+
+static struct i2c_driver fm3130_driver;
+
+static int __devinit fm3130_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct fm3130 *fm3130;
+ int err = -ENODEV;
+ int tmp;
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+
+ if (!i2c_check_functionality(adapter,
+ I2C_FUNC_I2C | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
+ return -EIO;
+
+ fm3130 = kzalloc(sizeof(struct fm3130), GFP_KERNEL);
+
+ if (!fm3130)
+ return -ENOMEM;
+
+ fm3130->client = client;
+ i2c_set_clientdata(client, fm3130);
+ fm3130->reg_addr_time = FM3130_RTC_SECONDS;
+ fm3130->reg_addr_alarm = FM3130_ALARM_SECONDS;
+
+ /* Messages to read time */
+ fm3130->msg[0].addr = client->addr;
+ fm3130->msg[0].flags = 0;
+ fm3130->msg[0].len = 1;
+ fm3130->msg[0].buf = &fm3130->reg_addr_time;
+
+ fm3130->msg[1].addr = client->addr;
+ fm3130->msg[1].flags = I2C_M_RD;
+ fm3130->msg[1].len = FM3130_CLOCK_REGS;
+ fm3130->msg[1].buf = &fm3130->regs[FM3130_RTC_SECONDS];
+
+ /* Messages to read alarm */
+ fm3130->msg[2].addr = client->addr;
+ fm3130->msg[2].flags = 0;
+ fm3130->msg[2].len = 1;
+ fm3130->msg[2].buf = &fm3130->reg_addr_alarm;
+
+ fm3130->msg[3].addr = client->addr;
+ fm3130->msg[3].flags = I2C_M_RD;
+ fm3130->msg[3].len = FM3130_ALARM_REGS;
+ fm3130->msg[3].buf = &fm3130->regs[FM3130_ALARM_SECONDS];
+
+ fm3130->data_valid = 0;
+
+ tmp = i2c_transfer(adapter, fm3130->msg, 4);
+ if (tmp != 4) {
+ pr_debug("read error %d\n", tmp);
+ err = -EIO;
+ goto exit_free;
+ }
+
+ fm3130->regs[FM3130_RTC_CONTROL] =
+ i2c_smbus_read_byte_data(client, FM3130_RTC_CONTROL);
+ fm3130->regs[FM3130_CAL_CONTROL] =
+ i2c_smbus_read_byte_data(client, FM3130_CAL_CONTROL);
+
+ /* Checking for alarm */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_AF) {
+ fm3130->alarm = 1;
+ fm3130->regs[FM3130_RTC_CONTROL] &= ~FM3130_RTC_CONTROL_BIT_AF;
+ }
+
+ /* Disabling calibration mode */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_CAL)
+ i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
+ fm3130->regs[FM3130_RTC_CONTROL] &
+ ~(FM3130_RTC_CONTROL_BIT_CAL));
+ dev_warn(&client->dev, "Disabling calibration mode!\n");
+
+ /* Disabling read and write modes */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_WRITE ||
+ fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_READ)
+ i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
+ fm3130->regs[FM3130_RTC_CONTROL] &
+ ~(FM3130_RTC_CONTROL_BIT_READ |
+ FM3130_RTC_CONTROL_BIT_WRITE));
+ dev_warn(&client->dev, "Disabling READ or WRITE mode!\n");
+
+ /* oscillator off? turn it on, so clock can tick. */
+ if (fm3130->regs[FM3130_CAL_CONTROL] & FM3130_CAL_CONTROL_BIT_nOSCEN)
+ i2c_smbus_write_byte_data(client, FM3130_CAL_CONTROL,
+ fm3130->regs[FM3130_CAL_CONTROL] &
+ ~(FM3130_CAL_CONTROL_BIT_nOSCEN));
+
+ /* oscillator fault? clear flag, and warn */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_LB)
+ dev_warn(&client->dev, "Low battery!\n");
+
+ /* oscillator fault? clear flag, and warn */
+ if (fm3130->regs[FM3130_RTC_CONTROL] & FM3130_RTC_CONTROL_BIT_POR) {
+ i2c_smbus_write_byte_data(client, FM3130_RTC_CONTROL,
+ fm3130->regs[FM3130_RTC_CONTROL] &
+ ~FM3130_RTC_CONTROL_BIT_POR);
+ dev_warn(&client->dev, "SET TIME!\n");
+ }
+ /* ACS is controlled by alarm */
+ i2c_smbus_write_byte_data(client, FM3130_ALARM_WP_CONTROL, 0x80);
+
+ /* TODO */
+ /* TODO need to sanity check alarm */
+ tmp = fm3130->regs[FM3130_RTC_SECONDS];
+ tmp = BCD2BIN(tmp & 0x7f);
+ if (tmp > 60)
+ goto exit_bad;
+ tmp = BCD2BIN(fm3130->regs[FM3130_RTC_MINUTES] & 0x7f);
+ if (tmp > 60)
+ goto exit_bad;
+
+ tmp = BCD2BIN(fm3130->regs[FM3130_RTC_DATE] & 0x3f);
+ if (tmp == 0 || tmp > 31)
+ goto exit_bad;
+
+ tmp = BCD2BIN(fm3130->regs[FM3130_RTC_MONTHS] & 0x1f);
+ if (tmp == 0 || tmp > 12)
+ goto exit_bad;
+
+ tmp = fm3130->regs[FM3130_RTC_HOURS];
+
+ fm3130->data_valid = 1;
+
+exit_bad:
+ if (!fm3130->data_valid)
+ dev_dbg(&client->dev,
+ "%s: %02x %02x %02x %02x %02x %02x %02x %02x"
+ "%02x %02x %02x %02x %02x %02x %02x\n",
+ "bogus registers",
+ fm3130->regs[0], fm3130->regs[1],
+ fm3130->regs[2], fm3130->regs[3],
+ fm3130->regs[4], fm3130->regs[5],
+ fm3130->regs[6], fm3130->regs[7],
+ fm3130->regs[8], fm3130->regs[9],
+ fm3130->regs[0xa], fm3130->regs[0xb],
+ fm3130->regs[0xc], fm3130->regs[0xd],
+ fm3130->regs[0xe]);
+
+ /* We won't bail out here because we just got invalid data.
+ Time setting from u-boot doesn't work anyway */
+ fm3130->rtc = rtc_device_register(client->name, &client->dev,
+ &fm3130_rtc_ops, THIS_MODULE);
+ if (IS_ERR(fm3130->rtc)) {
+ err = PTR_ERR(fm3130->rtc);
+ dev_err(&client->dev,
+ "unable to register the class device\n");
+ goto exit_free;
+ }
+ return 0;
+exit_free:
+ kfree(fm3130);
+ return err;
+}
+
+static int __devexit fm3130_remove(struct i2c_client *client)
+{
+ struct fm3130 *fm3130 = i2c_get_clientdata(client);
+
+ rtc_device_unregister(fm3130->rtc);
+ kfree(fm3130);
+ return 0;
+}
+
+static struct i2c_driver fm3130_driver = {
+ .driver = {
+ .name = "rtc-fm3130",
+ .owner = THIS_MODULE,
+ },
+ .probe = fm3130_probe,
+ .remove = __devexit_p(fm3130_remove),
+ .id_table = fm3130_id,
+};
+
+static int __init fm3130_init(void)
+{
+ return i2c_add_driver(&fm3130_driver);
+}
+module_init(fm3130_init);
+
+static void __exit fm3130_exit(void)
+{
+ i2c_del_driver(&fm3130_driver);
+}
+module_exit(fm3130_exit);
+
+MODULE_DESCRIPTION("RTC driver for FM3130");
+MODULE_AUTHOR("Sergey Lapin <slapin@ossfans.org>");
+MODULE_LICENSE("GPL");
+