diff options
| author | Sergey Lapin <slapin@ossfans.org> | 2008-06-12 15:21:55 -0700 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-06-12 18:05:42 -0700 | 
| commit | c6d8f400cc7610f04177f81168c19b8407cb48c3 (patch) | |
| tree | 2313cdd3e506c1b9e8050a04690bf7cfd4761b04 | |
| parent | e6d2bb2bacb43ff03b0f458108d71981d58e775a (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>
| -rw-r--r-- | drivers/rtc/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/rtc/Makefile | 3 | ||||
| -rw-r--r-- | drivers/rtc/rtc-fm3130.c | 501 | 
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"); + | 
