All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Platform real time clock driver for Dallas 1511 chip.
@ 2007-12-04 20:00 Andrew Sharp
  2007-12-04 21:04 ` Andrew Morton
  0 siblings, 1 reply; 4+ messages in thread
From: Andrew Sharp @ 2007-12-04 20:00 UTC (permalink / raw)
  To: linux-kernel; +Cc: akpm, p_gortmaker, Atsushi Nemoto


Add RTC support for DS1511 RTC/WDT chip.

Signed-off-by: Andy Sharp <andy.sharp@onstor.com>
---
 drivers/rtc/Kconfig      |   10 +
 drivers/rtc/Makefile     |    1 +
 drivers/rtc/rtc-ds1511.c |  665 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 676 insertions(+), 0 deletions(-)

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 1e6715e..0e59fcf 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -302,6 +302,16 @@ config RTC_DRV_DS1216
 	help
 	  If you say yes here you get support for the Dallas DS1216 RTC chips.
 
+config RTC_DRV_DS1511
+	tristate "Dallas DS1511"
+	depends on RTC_CLASS
+	help
+	  If you say yes here you get support for the
+	  Dallas DS1511 timekeeping/watchdog chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-ds1511.
+
 config RTC_DRV_DS1553
 	tristate "Dallas DS1553"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 465db4d..52239bf 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_RTC_DRV_CMOS)	+= rtc-cmos.o
 obj-$(CONFIG_RTC_DRV_DS1216)	+= rtc-ds1216.o
 obj-$(CONFIG_RTC_DRV_DS1307)	+= rtc-ds1307.o
 obj-$(CONFIG_RTC_DRV_DS1374)	+= rtc-ds1374.o
+obj-$(CONFIG_RTC_DRV_DS1511)	+= rtc-ds1511.o
 obj-$(CONFIG_RTC_DRV_DS1553)	+= rtc-ds1553.o
 obj-$(CONFIG_RTC_DRV_DS1672)	+= rtc-ds1672.o
 obj-$(CONFIG_RTC_DRV_DS1742)	+= rtc-ds1742.o
diff --git a/drivers/rtc/rtc-ds1511.c b/drivers/rtc/rtc-ds1511.c
new file mode 100644
index 0000000..6f8cacf
--- /dev/null
+++ b/drivers/rtc/rtc-ds1511.c
@@ -0,0 +1,665 @@
+/*
+ * An rtc driver for the Dallas DS1511
+ *
+ * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
+ * Copyright (C) 2007 Andrew Sharp <andy.sharp@onstor.com>
+ *
+ * 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.
+ *
+ * Real time clock driver for the Dallas 1511 chip, which also
+ * contains a watchdog timer.  There is a tiny amount of code that
+ * platform code could use to mess with the watchdog device a little
+ * bit, but not a full watchdog driver.
+ */
+
+#include <linux/bcd.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#define DRV_VERSION "0.6"
+
+typedef enum ds1511reg {
+	DS1511_SEC = 0x0,
+	DS1511_MIN = 0x1,
+	DS1511_HOUR = 0x2,
+	DS1511_DOW = 0x3,
+	DS1511_DOM = 0x4,
+	DS1511_MONTH = 0x5,
+	DS1511_YEAR = 0x6,
+	DS1511_CENTURY = 0x7,
+	DS1511_AM1_SEC = 0x8,
+	DS1511_AM2_MIN = 0x9,
+	DS1511_AM3_HOUR = 0xa,
+	DS1511_AM4_DATE = 0xb,
+	DS1511_WD_MSEC = 0xc,
+	DS1511_WD_SEC = 0xd,
+	DS1511_CONTROL_A = 0xe,
+	DS1511_CONTROL_B = 0xf,
+	DS1511_RAMADDR_LSB = 0x10,
+	DS1511_RAMDATA = 0x13
+} ds1511reg_t;
+
+#define DS1511_BLF1	0x80
+#define DS1511_BLF2	0x40
+#define DS1511_PRS	0x20
+#define DS1511_PAB	0x10
+#define DS1511_TDF	0x08
+#define DS1511_KSF	0x04
+#define DS1511_WDF	0x02
+#define DS1511_IRQF	0x01
+#define DS1511_TE	0x80
+#define DS1511_CS	0x40
+#define DS1511_BME	0x20
+#define DS1511_TPE	0x10
+#define DS1511_TIE	0x08
+#define DS1511_KIE	0x04
+#define DS1511_WDE	0x02
+#define DS1511_WDS	0x01
+#define DS1511_RAM_MAX	0xff
+
+#define RTC_CMD		DS1511_CONTROL_B
+#define RTC_CMD1	DS1511_CONTROL_A
+
+#define RTC_ALARM_SEC	DS1511_AM1_SEC
+#define RTC_ALARM_MIN	DS1511_AM2_MIN
+#define RTC_ALARM_HOUR	DS1511_AM3_HOUR
+#define RTC_ALARM_DATE	DS1511_AM4_DATE
+
+#define RTC_SEC		DS1511_SEC
+#define RTC_MIN		DS1511_MIN
+#define RTC_HOUR	DS1511_HOUR
+#define RTC_DOW		DS1511_DOW
+#define RTC_DOM		DS1511_DOM
+#define RTC_MON		DS1511_MONTH
+#define RTC_YEAR	DS1511_YEAR
+#define RTC_CENTURY	DS1511_CENTURY
+
+#define RTC_TIE	DS1511_TIE
+#define RTC_TE	DS1511_TE
+
+struct rtc_plat_data {
+	struct rtc_device *rtc;
+	void __iomem *ioaddr;			/* virtual base address */
+	unsigned long baseaddr;			/* physical base address */
+	int size;						/* amount of memory mapped */
+	unsigned long last_jiffies;
+	int irq;
+	unsigned int irqen;
+	int alrm_sec;
+	int alrm_min;
+	int alrm_hour;
+	int alrm_mday;
+};
+
+static DEFINE_SPINLOCK(ds1511_lock);
+
+static __iomem char *ds1511_base;
+static u32 reg_spacing = 1;
+
+ static noinline void
+rtc_write(uint8_t val, uint32_t reg)
+{
+	writeb(val, ds1511_base + (reg * reg_spacing));
+}
+
+ static inline void
+rtc_write_alarm(uint8_t val, ds1511reg_t reg)
+{
+	rtc_write((val | 0x80), reg);
+}
+
+ static noinline uint8_t
+rtc_read(ds1511reg_t reg)
+{
+	return readb(ds1511_base + (reg * reg_spacing));
+}
+
+ static inline void
+rtc_disable_update(void)
+{
+	rtc_write((rtc_read(RTC_CMD) & ~RTC_TE), RTC_CMD);
+}
+
+ static noinline void
+rtc_enable_update(void)
+{
+	rtc_write((rtc_read(RTC_CMD) | RTC_TE), RTC_CMD);
+}
+
+//#define DS1511_WDOG_RESET_SUPPORT
+//#undef DS1511_WDOG_RESET_SUPPORT
+#ifdef DS1511_WDOG_RESET_SUPPORT
+/*
+ * just enough code to set the watchdog timer so that it
+ * will reboot the system
+ */
+ void
+ds1511_wdog_set(unsigned long deciseconds)
+{
+	/*
+	 * the wdog timer can take 99.99 seconds
+	 */
+	deciseconds %= 10000;
+	/*
+	 * set the wdog values in the wdog registers
+	 */
+	rtc_write(BIN2BCD(deciseconds % 100), DS1511_WD_MSEC);
+	rtc_write(BIN2BCD(deciseconds / 100), DS1511_WD_SEC);
+	/*
+	 * set wdog enable and wdog 'steering' bit to issue a reset
+	 */
+	rtc_write(DS1511_WDE | DS1511_WDS, RTC_CMD);
+}
+
+ void
+ds1511_wdog_disable(void)
+{
+	/*
+	 * clear wdog enable and wdog 'steering' bits
+	 */
+	rtc_write(rtc_read(RTC_CMD) & ~(DS1511_WDE | DS1511_WDS), RTC_CMD);
+	/*
+	 * clear the wdog counter
+	 */
+	rtc_write(0, DS1511_WD_MSEC);
+	rtc_write(0, DS1511_WD_SEC);
+}
+#endif
+
+/*
+ * set the rtc chip's idea of the time.
+ * stupidly, some callers call with year unmolested;
+ * and some call with  year = year - 1900.  thanks.
+ */
+ int
+ds1511_rtc_set_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+	u8 mon, day, dow, hrs, min, sec, yrs, cen;
+	unsigned int flags;
+
+	/*
+	 * won't have to change this for a while
+	 */
+	if (rtc_tm->tm_year < 1900) {
+		rtc_tm->tm_year += 1900;
+	}
+
+	if (rtc_tm->tm_year < 1970) {
+		return -EINVAL;
+	}
+	yrs = rtc_tm->tm_year % 100;
+	cen = rtc_tm->tm_year / 100;
+	mon = rtc_tm->tm_mon + 1;   /* tm_mon starts at zero */
+	day = rtc_tm->tm_mday;
+	dow = rtc_tm->tm_wday & 0x7; /* automatic BCD */
+	hrs = rtc_tm->tm_hour;
+	min = rtc_tm->tm_min;
+	sec = rtc_tm->tm_sec;
+
+	if ((mon > 12) || (day == 0)) {
+		return -EINVAL;
+	}
+
+	if (day > rtc_month_days(rtc_tm->tm_mon, rtc_tm->tm_year)) {
+		return -EINVAL;
+	}
+
+	if ((hrs >= 24) || (min >= 60) || (sec >= 60)) {
+		return -EINVAL;
+	}
+
+	/*
+	 * each register is a different number of valid bits
+	 */
+	sec = BIN2BCD(sec) & 0x7f;
+	min = BIN2BCD(min) & 0x7f;
+	hrs = BIN2BCD(hrs) & 0x3f;
+	day = BIN2BCD(day) & 0x3f;
+	mon = BIN2BCD(mon) & 0x1f;
+	yrs = BIN2BCD(yrs) & 0xff;
+	cen = BIN2BCD(cen) & 0xff;
+
+	spin_lock_irqsave(&ds1511_lock, flags);
+	rtc_disable_update();
+	rtc_write(cen, RTC_CENTURY);
+	rtc_write(yrs, RTC_YEAR);
+	rtc_write((rtc_read(RTC_MON) & 0xe0) | mon, RTC_MON);
+	rtc_write(day, RTC_DOM);
+	rtc_write(hrs, RTC_HOUR);
+	rtc_write(min, RTC_MIN);
+	rtc_write(sec, RTC_SEC);
+	rtc_write(dow, RTC_DOW);
+	rtc_enable_update();
+	spin_unlock_irqrestore(&ds1511_lock, flags);
+
+	return 0;
+}
+
+ int
+ds1511_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+	unsigned int century;
+	unsigned int flags;
+
+	/*
+	 * give enough time to update RTC in case of continuous read
+	 */
+	if (pdata->last_jiffies == jiffies) {
+		msleep(1);
+	}
+	pdata->last_jiffies = jiffies;
+
+	spin_lock_irqsave(&ds1511_lock, flags);
+	rtc_disable_update();
+
+	rtc_tm->tm_sec = rtc_read(RTC_SEC) & 0x7f;
+	rtc_tm->tm_min = rtc_read(RTC_MIN) & 0x7f;
+	rtc_tm->tm_hour = rtc_read(RTC_HOUR) & 0x3f;
+	rtc_tm->tm_mday = rtc_read(RTC_DOM) & 0x3f;
+	rtc_tm->tm_wday = rtc_read(RTC_DOW) & 0x7;
+	rtc_tm->tm_mon = rtc_read(RTC_MON) & 0x1f;
+	rtc_tm->tm_year = rtc_read(RTC_YEAR) & 0x7f;
+	century = rtc_read(RTC_CENTURY);
+
+	rtc_enable_update();
+	spin_unlock_irqrestore(&ds1511_lock, flags);
+
+	rtc_tm->tm_sec = BCD2BIN(rtc_tm->tm_sec);
+	rtc_tm->tm_min = BCD2BIN(rtc_tm->tm_min);
+	rtc_tm->tm_hour = BCD2BIN(rtc_tm->tm_hour);
+	rtc_tm->tm_mday = BCD2BIN(rtc_tm->tm_mday);
+	rtc_tm->tm_wday = BCD2BIN(rtc_tm->tm_wday);
+	rtc_tm->tm_mon = BCD2BIN(rtc_tm->tm_mon);
+	rtc_tm->tm_year = BCD2BIN(rtc_tm->tm_year);
+	century = BCD2BIN(century) * 100;
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	century += rtc_tm->tm_year;
+	rtc_tm->tm_year = century - 1900;
+
+	rtc_tm->tm_mon--;
+
+	if (rtc_valid_tm(rtc_tm) < 0) {
+		dev_err(dev, "retrieved date/time is not valid.\n");
+		rtc_time_to_tm(0, rtc_tm);
+	}
+	return 0;
+}
+
+/*
+ * write the alarm register settings
+ *
+ * we only have the use to interrupt every second, otherwise
+ * known as the update interrupt, or the interrupt if the whole
+ * date/hours/mins/secs matches.  the ds1511 has many more
+ * permutations, but the kernel doesn't.
+ */
+ static void
+ds1511_rtc_update_alarm(struct rtc_plat_data *pdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->rtc->irq_lock, flags);
+	rtc_write(pdata->alrm_mday < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_mday) & 0x3f,
+	       RTC_ALARM_DATE);
+	rtc_write(pdata->alrm_hour < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_hour) & 0x3f,
+	       RTC_ALARM_HOUR);
+	rtc_write(pdata->alrm_min < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_min) & 0x7f,
+	       RTC_ALARM_MIN);
+	rtc_write(pdata->alrm_sec < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_sec) & 0x7f,
+	       RTC_ALARM_SEC);
+	rtc_write(rtc_read(RTC_CMD) | (pdata->irqen ? RTC_TIE : 0), RTC_CMD);
+	rtc_read(RTC_CMD1);	/* clear interrupts */
+	spin_unlock_irqrestore(&pdata->rtc->irq_lock, flags);
+}
+
+ static int
+ds1511_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -EINVAL;
+	}
+	pdata->alrm_mday = alrm->time.tm_mday;
+	pdata->alrm_hour = alrm->time.tm_hour;
+	pdata->alrm_min = alrm->time.tm_min;
+	pdata->alrm_sec = alrm->time.tm_sec;
+	if (alrm->enabled) {
+		pdata->irqen |= RTC_AF;
+	}
+	ds1511_rtc_update_alarm(pdata);
+	return 0;
+}
+
+ static int
+ds1511_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -EINVAL;
+	}
+	alrm->time.tm_mday = pdata->alrm_mday < 0 ? 0 : pdata->alrm_mday;
+	alrm->time.tm_hour = pdata->alrm_hour < 0 ? 0 : pdata->alrm_hour;
+	alrm->time.tm_min = pdata->alrm_min < 0 ? 0 : pdata->alrm_min;
+	alrm->time.tm_sec = pdata->alrm_sec < 0 ? 0 : pdata->alrm_sec;
+	alrm->enabled = (pdata->irqen & RTC_AF) ? 1 : 0;
+	return 0;
+}
+
+ static irqreturn_t
+ds1511_interrupt(int irq, void *dev_id)
+{
+	struct platform_device *pdev = dev_id;
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+	unsigned long events = RTC_IRQF;
+
+	/*
+	 * read and clear interrupt
+	 */
+	if (!(rtc_read(RTC_CMD1) & DS1511_IRQF)) {
+		return IRQ_NONE;
+	}
+	if (rtc_read(RTC_ALARM_SEC) & 0x80) {
+		events |= RTC_UF;
+	} else {
+		events |= RTC_AF;
+	}
+	rtc_update_irq(pdata->rtc, 1, events);
+	return IRQ_HANDLED;
+}
+
+ static void
+ds1511_rtc_release(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq >= 0) {
+		pdata->irqen = 0;
+		ds1511_rtc_update_alarm(pdata);
+	}
+}
+
+ static int
+ds1511_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -ENOIOCTLCMD; /* fall back into rtc-dev's emulation */
+	}
+	switch (cmd) {
+	case RTC_AIE_OFF:
+		pdata->irqen &= ~RTC_AF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_AIE_ON:
+		pdata->irqen |= RTC_AF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_UIE_OFF:
+		pdata->irqen &= ~RTC_UF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_UIE_ON:
+		pdata->irqen |= RTC_UF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static const struct rtc_class_ops ds1511_rtc_ops = {
+	.read_time	= ds1511_rtc_read_time,
+	.set_time	= ds1511_rtc_set_time,
+	.read_alarm	= ds1511_rtc_read_alarm,
+	.set_alarm	= ds1511_rtc_set_alarm,
+	.release	= ds1511_rtc_release,
+	.ioctl		= ds1511_rtc_ioctl,
+};
+
+ static ssize_t
+ds1511_nvram_read(struct kobject *kobj, struct bin_attribute *ba,
+				char *buf, loff_t pos, size_t size)
+{
+	ssize_t count;
+
+	/*
+	 * if count is more than one, turn on "burst" mode
+	 * turn it off when you're done
+	 */
+	if (size > 1) {
+		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD);
+	}
+	if (pos > DS1511_RAM_MAX) {
+		pos = DS1511_RAM_MAX;
+	}
+	if (size + pos > DS1511_RAM_MAX + 1) {
+		size = DS1511_RAM_MAX - pos + 1;
+	}
+	rtc_write(pos, DS1511_RAMADDR_LSB);
+	for (count = 0; size > 0; count++, size--) {
+		*buf++ = rtc_read(DS1511_RAMDATA);
+	}
+	if (count > 1) {
+		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD);
+	}
+	return count;
+}
+
+ static ssize_t
+ds1511_nvram_write(struct kobject *kobj, struct bin_attribute *bin_attr,
+				char *buf, loff_t pos, size_t size)
+{
+	ssize_t count;
+
+	/*
+	 * if count is more than one, turn on "burst" mode
+	 * turn it off when you're done
+	 */
+	if (size > 1) {
+		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD);
+	}
+	if (pos > DS1511_RAM_MAX) {
+		pos = DS1511_RAM_MAX;
+	}
+	if (size + pos > DS1511_RAM_MAX + 1) {
+		size = DS1511_RAM_MAX - pos + 1;
+	}
+	rtc_write(pos, DS1511_RAMADDR_LSB);
+	for (count = 0; size > 0; count++, size--) {
+		rtc_write(*buf++, DS1511_RAMDATA);
+	}
+	if (count > 1) {
+		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD);
+	}
+	return count;
+}
+
+static struct bin_attribute ds1511_nvram_attr = {
+	.attr = {
+		.name = "nvram",
+		.mode = S_IRUGO | S_IWUGO,
+		.owner = THIS_MODULE,
+	},
+	.size = DS1511_RAM_MAX,
+	.read = ds1511_nvram_read,
+	.write = ds1511_nvram_write,
+};
+
+ static int __devinit
+ds1511_rtc_probe(struct platform_device *pdev)
+{
+	struct rtc_device *rtc;
+	struct resource *res;
+	struct rtc_plat_data *pdata = NULL;
+	int ret = 0;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		return -ENODEV;
+	}
+	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		return -ENOMEM;
+	}
+	pdata->irq = -1;
+	pdata->size = res->end - res->start + 1;
+	if (!request_mem_region(res->start, pdata->size, pdev->name)) {
+		ret = -EBUSY;
+		goto out;
+	}
+	pdata->baseaddr = res->start;
+	pdata->size = pdata->size;
+	ds1511_base = ioremap(pdata->baseaddr, pdata->size);
+	if (!ds1511_base) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	pdata->ioaddr = ds1511_base;
+	pdata->irq = platform_get_irq(pdev, 0);
+
+	/*
+	 * turn on the clock and the crystal, etc.
+	 */
+	rtc_write(0, RTC_CMD);
+	rtc_write(0, RTC_CMD1);
+	/*
+	 * clear the wdog counter
+	 */
+	rtc_write(0, DS1511_WD_MSEC);
+	rtc_write(0, DS1511_WD_SEC);
+	/*
+	 * start the clock
+	 */
+	rtc_enable_update();
+
+	/*
+	 * check for a dying bat-tree
+	 */
+	if (rtc_read(RTC_CMD1) & DS1511_BLF1) {
+		dev_warn(&pdev->dev, "voltage-low detected.\n");
+	}
+
+	/*
+	 * if the platform has an interrupt in mind for this device,
+	 * then by all means, set it
+	 */
+	if (pdata->irq >= 0) {
+		rtc_read(RTC_CMD1);
+		if (request_irq(pdata->irq, ds1511_interrupt,
+				IRQF_DISABLED | IRQF_SHARED, pdev->name, pdev) < 0) {
+
+			dev_warn(&pdev->dev, "interrupt not available.\n");
+			pdata->irq = -1;
+		}
+	}
+
+	rtc = rtc_device_register(pdev->name, &pdev->dev, &ds1511_rtc_ops,
+		THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		ret = PTR_ERR(rtc);
+		goto out;
+	}
+	pdata->rtc = rtc;
+	pdata->last_jiffies = jiffies;
+	platform_set_drvdata(pdev, pdata);
+	ret = sysfs_create_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr);
+	if (ret) {
+		goto out;
+	}
+	return 0;
+ out:
+	if (pdata->rtc) {
+		rtc_device_unregister(pdata->rtc);
+	}
+	if (pdata->irq >= 0) {
+		free_irq(pdata->irq, pdev);
+	}
+	if (ds1511_base) {
+		iounmap(ds1511_base);
+		ds1511_base = NULL;
+	}
+	if (pdata->baseaddr) {
+		release_mem_region(pdata->baseaddr, pdata->size);
+	}
+
+	kfree(pdata);
+	return ret;
+}
+
+ static int __devexit
+ds1511_rtc_remove(struct platform_device *pdev)
+{
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	sysfs_remove_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr);
+	rtc_device_unregister(pdata->rtc);
+	pdata->rtc = NULL;
+	if (pdata->irq >= 0) {
+		/*
+		 * disable the alarm interrupt
+		 */
+		rtc_write(rtc_read(RTC_CMD) & ~RTC_TIE, RTC_CMD);
+		rtc_read(RTC_CMD1);
+		free_irq(pdata->irq, pdev);
+	}
+	iounmap(pdata->ioaddr);
+	ds1511_base = NULL;
+	release_mem_region(pdata->baseaddr, pdata->size);
+	kfree(pdata);
+	return 0;
+}
+
+static struct platform_driver ds1511_rtc_driver = {
+	.probe		= ds1511_rtc_probe,
+	.remove		= __devexit_p(ds1511_rtc_remove),
+	.driver		= {
+		.name	= "ds1511",
+		.owner	= THIS_MODULE,
+	},
+};
+
+ static int __init
+ds1511_rtc_init(void)
+{
+	return platform_driver_register(&ds1511_rtc_driver);
+}
+
+ static void __exit
+ds1511_rtc_exit(void)
+{
+	return platform_driver_unregister(&ds1511_rtc_driver);
+}
+
+module_init(ds1511_rtc_init);
+module_exit(ds1511_rtc_exit);
+
+MODULE_AUTHOR("Andrew Sharp <andy.sharp@onstor.com>");
+MODULE_DESCRIPTION("Dallas DS1511 RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);
-- 
1.4.4.4


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH] Platform real time clock driver for Dallas 1511 chip.
  2007-12-04 20:00 [PATCH] Platform real time clock driver for Dallas 1511 chip Andrew Sharp
@ 2007-12-04 21:04 ` Andrew Morton
  0 siblings, 0 replies; 4+ messages in thread
From: Andrew Morton @ 2007-12-04 21:04 UTC (permalink / raw)
  To: Andrew Sharp; +Cc: linux-kernel, p_gortmaker, anemo, Alessandro Zummo

On Tue, 4 Dec 2007 12:00:05 -0800
Andrew Sharp <andy.sharp@onstor.com> wrote:

> 
> Add RTC support for DS1511 RTC/WDT chip.
> 
> ...
>
> +typedef enum ds1511reg {
> +	DS1511_SEC = 0x0,
> +	DS1511_MIN = 0x1,
> +	DS1511_HOUR = 0x2,
> +	DS1511_DOW = 0x3,
> +	DS1511_DOM = 0x4,
> +	DS1511_MONTH = 0x5,
> +	DS1511_YEAR = 0x6,
> +	DS1511_CENTURY = 0x7,
> +	DS1511_AM1_SEC = 0x8,
> +	DS1511_AM2_MIN = 0x9,
> +	DS1511_AM3_HOUR = 0xa,
> +	DS1511_AM4_DATE = 0xb,
> +	DS1511_WD_MSEC = 0xc,
> +	DS1511_WD_SEC = 0xd,
> +	DS1511_CONTROL_A = 0xe,
> +	DS1511_CONTROL_B = 0xf,
> +	DS1511_RAMADDR_LSB = 0x10,
> +	DS1511_RAMDATA = 0x13
> +} ds1511reg_t;

Please remove the typedef and just use `enum ds1511reg' everywhere.

> + static noinline void
> +rtc_write(uint8_t val, uint32_t reg)

It's unusual (unique) to indent the function declaration by a single space
in this way.  Maybe an editor option which needs fixing?

Also, although there are good reasons to always put a newline after the
declaration of the return type, kernel code generally doesn't do that
except as a way of avoiding 80-col wraparound sometimes.

IOW, please use

static noinline void rtc_write(uint8_t val, uint32_t reg)
{


Why was this function declared noinline?


> +{
> +	writeb(val, ds1511_base + (reg * reg_spacing));
> +}
> +
> + static inline void
> +rtc_write_alarm(uint8_t val, ds1511reg_t reg)
> +{
> +	rtc_write((val | 0x80), reg);
> +}
> +
> + static noinline uint8_t
> +rtc_read(ds1511reg_t reg)
> +{
> +	return readb(ds1511_base + (reg * reg_spacing));
> +}
> +
> + static inline void
> +rtc_disable_update(void)
> +{
> +	rtc_write((rtc_read(RTC_CMD) & ~RTC_TE), RTC_CMD);
> +}
> +
> + static noinline void
> +rtc_enable_update(void)
> +{
> +	rtc_write((rtc_read(RTC_CMD) | RTC_TE), RTC_CMD);
> +}
> +
> +//#define DS1511_WDOG_RESET_SUPPORT
> +//#undef DS1511_WDOG_RESET_SUPPORT

c99-style comments will generate checkpatch warnings.

Please run checkpatch.  It detects a lot of coding-style breakage in
this patch.

> +#ifdef DS1511_WDOG_RESET_SUPPORT
> +/*
> + * just enough code to set the watchdog timer so that it
> + * will reboot the system
> + */
> + void
> +ds1511_wdog_set(unsigned long deciseconds)
> +{
> +	/*
> +	 * the wdog timer can take 99.99 seconds
> +	 */
> +	deciseconds %= 10000;
> +	/*
> +	 * set the wdog values in the wdog registers
> +	 */
> +	rtc_write(BIN2BCD(deciseconds % 100), DS1511_WD_MSEC);
> +	rtc_write(BIN2BCD(deciseconds / 100), DS1511_WD_SEC);
> +	/*
> +	 * set wdog enable and wdog 'steering' bit to issue a reset
> +	 */
> +	rtc_write(DS1511_WDE | DS1511_WDS, RTC_CMD);
> +}
> +
> + void
> +ds1511_wdog_disable(void)
> +{
> +	/*
> +	 * clear wdog enable and wdog 'steering' bits
> +	 */
> +	rtc_write(rtc_read(RTC_CMD) & ~(DS1511_WDE | DS1511_WDS), RTC_CMD);
> +	/*
> +	 * clear the wdog counter
> +	 */
> +	rtc_write(0, DS1511_WD_MSEC);
> +	rtc_write(0, DS1511_WD_SEC);
> +}
> +#endif

What's the story here?  This code is permanently disabled?

> + int
> +ds1511_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm)
> +{
> +	struct platform_device *pdev = to_platform_device(dev);
> +	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
> +	unsigned int century;
> +	unsigned int flags;
> +
> +	/*
> +	 * give enough time to update RTC in case of continuous read
> +	 */
> +	if (pdata->last_jiffies == jiffies) {
> +		msleep(1);
> +	}

hm, that could be pretty obnoxious for some applications, I expect. 
They'll run veeeery sloooowly, and inconsistently slowly across different
values of CONFIG_HZ.  And the uninterruptible sleep will contribute to
system load average.

What's going on here?

Do other RTC drivers do things like this?

> +	pdata->last_jiffies = jiffies;
> +
> +	spin_lock_irqsave(&ds1511_lock, flags);
> +	rtc_disable_update();
> +
> +	rtc_tm->tm_sec = rtc_read(RTC_SEC) & 0x7f;
> +	rtc_tm->tm_min = rtc_read(RTC_MIN) & 0x7f;
> +	rtc_tm->tm_hour = rtc_read(RTC_HOUR) & 0x3f;
> +	rtc_tm->tm_mday = rtc_read(RTC_DOM) & 0x3f;
> +	rtc_tm->tm_wday = rtc_read(RTC_DOW) & 0x7;
> +	rtc_tm->tm_mon = rtc_read(RTC_MON) & 0x1f;
> +	rtc_tm->tm_year = rtc_read(RTC_YEAR) & 0x7f;
> +	century = rtc_read(RTC_CENTURY);
> +
> +	rtc_enable_update();
> +	spin_unlock_irqrestore(&ds1511_lock, flags);
> +
> +	rtc_tm->tm_sec = BCD2BIN(rtc_tm->tm_sec);
> +	rtc_tm->tm_min = BCD2BIN(rtc_tm->tm_min);
> +	rtc_tm->tm_hour = BCD2BIN(rtc_tm->tm_hour);
> +	rtc_tm->tm_mday = BCD2BIN(rtc_tm->tm_mday);
> +	rtc_tm->tm_wday = BCD2BIN(rtc_tm->tm_wday);
> +	rtc_tm->tm_mon = BCD2BIN(rtc_tm->tm_mon);
> +	rtc_tm->tm_year = BCD2BIN(rtc_tm->tm_year);
> +	century = BCD2BIN(century) * 100;
> +
> +	/*
> +	 * Account for differences between how the RTC uses the values
> +	 * and how they are defined in a struct rtc_time;
> +	 */
> +	century += rtc_tm->tm_year;
> +	rtc_tm->tm_year = century - 1900;
> +
> +	rtc_tm->tm_mon--;
> +
> +	if (rtc_valid_tm(rtc_tm) < 0) {
> +		dev_err(dev, "retrieved date/time is not valid.\n");
> +		rtc_time_to_tm(0, rtc_tm);
> +	}
> +	return 0;
> +}
> +
> +/*
> + * write the alarm register settings
> + *
> + * we only have the use to interrupt every second, otherwise
> + * known as the update interrupt, or the interrupt if the whole
> + * date/hours/mins/secs matches.  the ds1511 has many more
> + * permutations, but the kernel doesn't.
> + */
> + static void
> +ds1511_rtc_update_alarm(struct rtc_plat_data *pdata)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&pdata->rtc->irq_lock, flags);
> +	rtc_write(pdata->alrm_mday < 0 || (pdata->irqen & RTC_UF) ?
> +	       0x80 : BIN2BCD(pdata->alrm_mday) & 0x3f,
> +	       RTC_ALARM_DATE);

hm.  You appear to be a C precedence whizz ;)

> +	rtc_write(pdata->alrm_hour < 0 || (pdata->irqen & RTC_UF) ?
> +	       0x80 : BIN2BCD(pdata->alrm_hour) & 0x3f,
> +	       RTC_ALARM_HOUR);
> +	rtc_write(pdata->alrm_min < 0 || (pdata->irqen & RTC_UF) ?
> +	       0x80 : BIN2BCD(pdata->alrm_min) & 0x7f,
> +	       RTC_ALARM_MIN);
> +	rtc_write(pdata->alrm_sec < 0 || (pdata->irqen & RTC_UF) ?
> +	       0x80 : BIN2BCD(pdata->alrm_sec) & 0x7f,
> +	       RTC_ALARM_SEC);
> +	rtc_write(rtc_read(RTC_CMD) | (pdata->irqen ? RTC_TIE : 0), RTC_CMD);
> +	rtc_read(RTC_CMD1);	/* clear interrupts */
> +	spin_unlock_irqrestore(&pdata->rtc->irq_lock, flags);
> +}
> +


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] Platform real time clock driver for Dallas 1511 chip.
  2007-12-05  0:46 Andrew Sharp
@ 2007-12-05 15:32 ` Atsushi Nemoto
  0 siblings, 0 replies; 4+ messages in thread
From: Atsushi Nemoto @ 2007-12-05 15:32 UTC (permalink / raw)
  To: andy.sharp; +Cc: linux-kernel, akpm, p_gortmaker, a.zummo

On Tue, 4 Dec 2007 16:46:51 -0800, Andrew Sharp <andy.sharp@onstor.com> wrote:
> +struct rtc_plat_data {
> +	struct rtc_device *rtc;
> +	void __iomem *ioaddr;		/* virtual base address */
> +	unsigned long baseaddr;		/* physical base address */
> +	int size;				/* amount of memory mapped */
> +	int irq;
> +	unsigned int irqen;
> +	int alrm_sec;
> +	int alrm_min;
> +	int alrm_hour;
> +	int alrm_mday;
> +};
> +
> +static DEFINE_SPINLOCK(ds1511_lock);
> +
> +static __iomem char *ds1511_base;

If you used ds1511_base, ioaddr in rtc_plat_data is not needed.

> + static noinline void
> +rtc_write(uint8_t val, uint32_t reg)
> +{
> +	writeb(val, ds1511_base + (reg * reg_spacing));
> +}

Still "noinline" here and there.

> +static struct bin_attribute ds1511_nvram_attr = {
> +	.attr = {
> +		.name = "nvram",
> +		.mode = S_IRUGO | S_IWUGO,
> +		.owner = THIS_MODULE,
> +	},
> +	.size = DS1511_RAM_MAX,
> +	.read = ds1511_nvram_read,
> +	.write = ds1511_nvram_write,
> +};

It would be better to use S_IWUSR instead of S_IWUGO.  Please check
rtc-ds1553 changes in current git tree.

> +static struct platform_driver ds1511_rtc_driver = {
> +	.probe		= ds1511_rtc_probe,
> +	.remove		= __devexit_p(ds1511_rtc_remove),
> +	.driver		= {
> +		.name	= "ds1511",
> +		.owner	= THIS_MODULE,
> +	},
> +};

Use "rtc-ds1511" for driver name for hotplugging.  Please check
rtc-ds1553 changes in current git tree.

> + static void __exit
> +ds1511_rtc_exit(void)
> +{
> +	return platform_driver_unregister(&ds1511_rtc_driver);
> +}

Do not return void value.  Please check rtc-ds1553 changes in current
git tree.

---
Atsushi Nemoto

^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH] Platform real time clock driver for Dallas 1511 chip.
@ 2007-12-05  0:46 Andrew Sharp
  2007-12-05 15:32 ` Atsushi Nemoto
  0 siblings, 1 reply; 4+ messages in thread
From: Andrew Sharp @ 2007-12-05  0:46 UTC (permalink / raw)
  To: linux-kernel; +Cc: akpm, p_gortmaker, Alessandro Zummo, Atsushi Nemoto


Add RTC support for DS1511 RTC/WDT chip.

Incorprate changes from feedback:
	Remove noinline;
	Remove code checking/setting jiffies since last read;
	Remove enum typedef.


Signed-off-by: Andy Sharp <andy.sharp@onstor.com>
---
 drivers/rtc/Kconfig      |   10 +
 drivers/rtc/Makefile     |    1 +
 drivers/rtc/rtc-ds1511.c |  656 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 667 insertions(+), 0 deletions(-)

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 1e6715e..0e59fcf 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -302,6 +302,16 @@ config RTC_DRV_DS1216
 	help
 	  If you say yes here you get support for the Dallas DS1216 RTC chips.
 
+config RTC_DRV_DS1511
+	tristate "Dallas DS1511"
+	depends on RTC_CLASS
+	help
+	  If you say yes here you get support for the
+	  Dallas DS1511 timekeeping/watchdog chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-ds1511.
+
 config RTC_DRV_DS1553
 	tristate "Dallas DS1553"
 	help
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 465db4d..52239bf 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_RTC_DRV_CMOS)	+= rtc-cmos.o
 obj-$(CONFIG_RTC_DRV_DS1216)	+= rtc-ds1216.o
 obj-$(CONFIG_RTC_DRV_DS1307)	+= rtc-ds1307.o
 obj-$(CONFIG_RTC_DRV_DS1374)	+= rtc-ds1374.o
+obj-$(CONFIG_RTC_DRV_DS1511)	+= rtc-ds1511.o
 obj-$(CONFIG_RTC_DRV_DS1553)	+= rtc-ds1553.o
 obj-$(CONFIG_RTC_DRV_DS1672)	+= rtc-ds1672.o
 obj-$(CONFIG_RTC_DRV_DS1742)	+= rtc-ds1742.o
diff --git a/drivers/rtc/rtc-ds1511.c b/drivers/rtc/rtc-ds1511.c
new file mode 100644
index 0000000..604192f
--- /dev/null
+++ b/drivers/rtc/rtc-ds1511.c
@@ -0,0 +1,656 @@
+/*
+ * An rtc driver for the Dallas DS1511
+ *
+ * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
+ * Copyright (C) 2007 Andrew Sharp <andy.sharp@onstor.com>
+ *
+ * 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.
+ *
+ * Real time clock driver for the Dallas 1511 chip, which also
+ * contains a watchdog timer.  There is a tiny amount of code that
+ * platform code could use to mess with the watchdog device a little
+ * bit, but not a full watchdog driver.
+ */
+
+#include <linux/bcd.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#define DRV_VERSION "0.6"
+
+enum ds1511reg {
+	DS1511_SEC = 0x0,
+	DS1511_MIN = 0x1,
+	DS1511_HOUR = 0x2,
+	DS1511_DOW = 0x3,
+	DS1511_DOM = 0x4,
+	DS1511_MONTH = 0x5,
+	DS1511_YEAR = 0x6,
+	DS1511_CENTURY = 0x7,
+	DS1511_AM1_SEC = 0x8,
+	DS1511_AM2_MIN = 0x9,
+	DS1511_AM3_HOUR = 0xa,
+	DS1511_AM4_DATE = 0xb,
+	DS1511_WD_MSEC = 0xc,
+	DS1511_WD_SEC = 0xd,
+	DS1511_CONTROL_A = 0xe,
+	DS1511_CONTROL_B = 0xf,
+	DS1511_RAMADDR_LSB = 0x10,
+	DS1511_RAMDATA = 0x13
+};
+
+#define DS1511_BLF1	0x80
+#define DS1511_BLF2	0x40
+#define DS1511_PRS	0x20
+#define DS1511_PAB	0x10
+#define DS1511_TDF	0x08
+#define DS1511_KSF	0x04
+#define DS1511_WDF	0x02
+#define DS1511_IRQF	0x01
+#define DS1511_TE	0x80
+#define DS1511_CS	0x40
+#define DS1511_BME	0x20
+#define DS1511_TPE	0x10
+#define DS1511_TIE	0x08
+#define DS1511_KIE	0x04
+#define DS1511_WDE	0x02
+#define DS1511_WDS	0x01
+#define DS1511_RAM_MAX	0xff
+
+#define RTC_CMD		DS1511_CONTROL_B
+#define RTC_CMD1	DS1511_CONTROL_A
+
+#define RTC_ALARM_SEC	DS1511_AM1_SEC
+#define RTC_ALARM_MIN	DS1511_AM2_MIN
+#define RTC_ALARM_HOUR	DS1511_AM3_HOUR
+#define RTC_ALARM_DATE	DS1511_AM4_DATE
+
+#define RTC_SEC		DS1511_SEC
+#define RTC_MIN		DS1511_MIN
+#define RTC_HOUR	DS1511_HOUR
+#define RTC_DOW		DS1511_DOW
+#define RTC_DOM		DS1511_DOM
+#define RTC_MON		DS1511_MONTH
+#define RTC_YEAR	DS1511_YEAR
+#define RTC_CENTURY	DS1511_CENTURY
+
+#define RTC_TIE	DS1511_TIE
+#define RTC_TE	DS1511_TE
+
+struct rtc_plat_data {
+	struct rtc_device *rtc;
+	void __iomem *ioaddr;		/* virtual base address */
+	unsigned long baseaddr;		/* physical base address */
+	int size;				/* amount of memory mapped */
+	int irq;
+	unsigned int irqen;
+	int alrm_sec;
+	int alrm_min;
+	int alrm_hour;
+	int alrm_mday;
+};
+
+static DEFINE_SPINLOCK(ds1511_lock);
+
+static __iomem char *ds1511_base;
+static u32 reg_spacing = 1;
+
+ static noinline void
+rtc_write(uint8_t val, uint32_t reg)
+{
+	writeb(val, ds1511_base + (reg * reg_spacing));
+}
+
+ static inline void
+rtc_write_alarm(uint8_t val, enum ds1511reg reg)
+{
+	rtc_write((val | 0x80), reg);
+}
+
+ static noinline uint8_t
+rtc_read(enum ds1511reg reg)
+{
+	return readb(ds1511_base + (reg * reg_spacing));
+}
+
+ static inline void
+rtc_disable_update(void)
+{
+	rtc_write((rtc_read(RTC_CMD) & ~RTC_TE), RTC_CMD);
+}
+
+ static void
+rtc_enable_update(void)
+{
+	rtc_write((rtc_read(RTC_CMD) | RTC_TE), RTC_CMD);
+}
+
+/*
+ * #define DS1511_WDOG_RESET_SUPPORT
+ *
+ * Uncomment this if you want to use these routines in
+ * some platform code.
+ */
+#ifdef DS1511_WDOG_RESET_SUPPORT
+/*
+ * just enough code to set the watchdog timer so that it
+ * will reboot the system
+ */
+ void
+ds1511_wdog_set(unsigned long deciseconds)
+{
+	/*
+	 * the wdog timer can take 99.99 seconds
+	 */
+	deciseconds %= 10000;
+	/*
+	 * set the wdog values in the wdog registers
+	 */
+	rtc_write(BIN2BCD(deciseconds % 100), DS1511_WD_MSEC);
+	rtc_write(BIN2BCD(deciseconds / 100), DS1511_WD_SEC);
+	/*
+	 * set wdog enable and wdog 'steering' bit to issue a reset
+	 */
+	rtc_write(DS1511_WDE | DS1511_WDS, RTC_CMD);
+}
+
+ void
+ds1511_wdog_disable(void)
+{
+	/*
+	 * clear wdog enable and wdog 'steering' bits
+	 */
+	rtc_write(rtc_read(RTC_CMD) & ~(DS1511_WDE | DS1511_WDS), RTC_CMD);
+	/*
+	 * clear the wdog counter
+	 */
+	rtc_write(0, DS1511_WD_MSEC);
+	rtc_write(0, DS1511_WD_SEC);
+}
+#endif
+
+/*
+ * set the rtc chip's idea of the time.
+ * stupidly, some callers call with year unmolested;
+ * and some call with  year = year - 1900.  thanks.
+ */
+ int
+ds1511_rtc_set_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+	u8 mon, day, dow, hrs, min, sec, yrs, cen;
+	unsigned int flags;
+
+	/*
+	 * won't have to change this for a while
+	 */
+	if (rtc_tm->tm_year < 1900) {
+		rtc_tm->tm_year += 1900;
+	}
+
+	if (rtc_tm->tm_year < 1970) {
+		return -EINVAL;
+	}
+	yrs = rtc_tm->tm_year % 100;
+	cen = rtc_tm->tm_year / 100;
+	mon = rtc_tm->tm_mon + 1;   /* tm_mon starts at zero */
+	day = rtc_tm->tm_mday;
+	dow = rtc_tm->tm_wday & 0x7; /* automatic BCD */
+	hrs = rtc_tm->tm_hour;
+	min = rtc_tm->tm_min;
+	sec = rtc_tm->tm_sec;
+
+	if ((mon > 12) || (day == 0)) {
+		return -EINVAL;
+	}
+
+	if (day > rtc_month_days(rtc_tm->tm_mon, rtc_tm->tm_year)) {
+		return -EINVAL;
+	}
+
+	if ((hrs >= 24) || (min >= 60) || (sec >= 60)) {
+		return -EINVAL;
+	}
+
+	/*
+	 * each register is a different number of valid bits
+	 */
+	sec = BIN2BCD(sec) & 0x7f;
+	min = BIN2BCD(min) & 0x7f;
+	hrs = BIN2BCD(hrs) & 0x3f;
+	day = BIN2BCD(day) & 0x3f;
+	mon = BIN2BCD(mon) & 0x1f;
+	yrs = BIN2BCD(yrs) & 0xff;
+	cen = BIN2BCD(cen) & 0xff;
+
+	spin_lock_irqsave(&ds1511_lock, flags);
+	rtc_disable_update();
+	rtc_write(cen, RTC_CENTURY);
+	rtc_write(yrs, RTC_YEAR);
+	rtc_write((rtc_read(RTC_MON) & 0xe0) | mon, RTC_MON);
+	rtc_write(day, RTC_DOM);
+	rtc_write(hrs, RTC_HOUR);
+	rtc_write(min, RTC_MIN);
+	rtc_write(sec, RTC_SEC);
+	rtc_write(dow, RTC_DOW);
+	rtc_enable_update();
+	spin_unlock_irqrestore(&ds1511_lock, flags);
+
+	return 0;
+}
+
+ int
+ds1511_rtc_read_time(struct device *dev, struct rtc_time *rtc_tm)
+{
+	unsigned int century;
+	unsigned int flags;
+
+	spin_lock_irqsave(&ds1511_lock, flags);
+	rtc_disable_update();
+
+	rtc_tm->tm_sec = rtc_read(RTC_SEC) & 0x7f;
+	rtc_tm->tm_min = rtc_read(RTC_MIN) & 0x7f;
+	rtc_tm->tm_hour = rtc_read(RTC_HOUR) & 0x3f;
+	rtc_tm->tm_mday = rtc_read(RTC_DOM) & 0x3f;
+	rtc_tm->tm_wday = rtc_read(RTC_DOW) & 0x7;
+	rtc_tm->tm_mon = rtc_read(RTC_MON) & 0x1f;
+	rtc_tm->tm_year = rtc_read(RTC_YEAR) & 0x7f;
+	century = rtc_read(RTC_CENTURY);
+
+	rtc_enable_update();
+	spin_unlock_irqrestore(&ds1511_lock, flags);
+
+	rtc_tm->tm_sec = BCD2BIN(rtc_tm->tm_sec);
+	rtc_tm->tm_min = BCD2BIN(rtc_tm->tm_min);
+	rtc_tm->tm_hour = BCD2BIN(rtc_tm->tm_hour);
+	rtc_tm->tm_mday = BCD2BIN(rtc_tm->tm_mday);
+	rtc_tm->tm_wday = BCD2BIN(rtc_tm->tm_wday);
+	rtc_tm->tm_mon = BCD2BIN(rtc_tm->tm_mon);
+	rtc_tm->tm_year = BCD2BIN(rtc_tm->tm_year);
+	century = BCD2BIN(century) * 100;
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	century += rtc_tm->tm_year;
+	rtc_tm->tm_year = century - 1900;
+
+	rtc_tm->tm_mon--;
+
+	if (rtc_valid_tm(rtc_tm) < 0) {
+		dev_err(dev, "retrieved date/time is not valid.\n");
+		rtc_time_to_tm(0, rtc_tm);
+	}
+	return 0;
+}
+
+/*
+ * write the alarm register settings
+ *
+ * we only have the use to interrupt every second, otherwise
+ * known as the update interrupt, or the interrupt if the whole
+ * date/hours/mins/secs matches.  the ds1511 has many more
+ * permutations, but the kernel doesn't.
+ */
+ static void
+ds1511_rtc_update_alarm(struct rtc_plat_data *pdata)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&pdata->rtc->irq_lock, flags);
+	rtc_write(pdata->alrm_mday < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_mday) & 0x3f,
+	       RTC_ALARM_DATE);
+	rtc_write(pdata->alrm_hour < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_hour) & 0x3f,
+	       RTC_ALARM_HOUR);
+	rtc_write(pdata->alrm_min < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_min) & 0x7f,
+	       RTC_ALARM_MIN);
+	rtc_write(pdata->alrm_sec < 0 || (pdata->irqen & RTC_UF) ?
+	       0x80 : BIN2BCD(pdata->alrm_sec) & 0x7f,
+	       RTC_ALARM_SEC);
+	rtc_write(rtc_read(RTC_CMD) | (pdata->irqen ? RTC_TIE : 0), RTC_CMD);
+	rtc_read(RTC_CMD1);	/* clear interrupts */
+	spin_unlock_irqrestore(&pdata->rtc->irq_lock, flags);
+}
+
+ static int
+ds1511_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -EINVAL;
+	}
+	pdata->alrm_mday = alrm->time.tm_mday;
+	pdata->alrm_hour = alrm->time.tm_hour;
+	pdata->alrm_min = alrm->time.tm_min;
+	pdata->alrm_sec = alrm->time.tm_sec;
+	if (alrm->enabled) {
+		pdata->irqen |= RTC_AF;
+	}
+	ds1511_rtc_update_alarm(pdata);
+	return 0;
+}
+
+ static int
+ds1511_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -EINVAL;
+	}
+	alrm->time.tm_mday = pdata->alrm_mday < 0 ? 0 : pdata->alrm_mday;
+	alrm->time.tm_hour = pdata->alrm_hour < 0 ? 0 : pdata->alrm_hour;
+	alrm->time.tm_min = pdata->alrm_min < 0 ? 0 : pdata->alrm_min;
+	alrm->time.tm_sec = pdata->alrm_sec < 0 ? 0 : pdata->alrm_sec;
+	alrm->enabled = (pdata->irqen & RTC_AF) ? 1 : 0;
+	return 0;
+}
+
+ static irqreturn_t
+ds1511_interrupt(int irq, void *dev_id)
+{
+	struct platform_device *pdev = dev_id;
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+	unsigned long events = RTC_IRQF;
+
+	/*
+	 * read and clear interrupt
+	 */
+	if (!(rtc_read(RTC_CMD1) & DS1511_IRQF)) {
+		return IRQ_NONE;
+	}
+	if (rtc_read(RTC_ALARM_SEC) & 0x80) {
+		events |= RTC_UF;
+	} else {
+		events |= RTC_AF;
+	}
+	rtc_update_irq(pdata->rtc, 1, events);
+	return IRQ_HANDLED;
+}
+
+ static void
+ds1511_rtc_release(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq >= 0) {
+		pdata->irqen = 0;
+		ds1511_rtc_update_alarm(pdata);
+	}
+}
+
+ static int
+ds1511_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	if (pdata->irq < 0) {
+		return -ENOIOCTLCMD; /* fall back into rtc-dev's emulation */
+	}
+	switch (cmd) {
+	case RTC_AIE_OFF:
+		pdata->irqen &= ~RTC_AF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_AIE_ON:
+		pdata->irqen |= RTC_AF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_UIE_OFF:
+		pdata->irqen &= ~RTC_UF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	case RTC_UIE_ON:
+		pdata->irqen |= RTC_UF;
+		ds1511_rtc_update_alarm(pdata);
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static const struct rtc_class_ops ds1511_rtc_ops = {
+	.read_time	= ds1511_rtc_read_time,
+	.set_time	= ds1511_rtc_set_time,
+	.read_alarm	= ds1511_rtc_read_alarm,
+	.set_alarm	= ds1511_rtc_set_alarm,
+	.release	= ds1511_rtc_release,
+	.ioctl		= ds1511_rtc_ioctl,
+};
+
+ static ssize_t
+ds1511_nvram_read(struct kobject *kobj, struct bin_attribute *ba,
+				char *buf, loff_t pos, size_t size)
+{
+	ssize_t count;
+
+	/*
+	 * if count is more than one, turn on "burst" mode
+	 * turn it off when you're done
+	 */
+	if (size > 1) {
+		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD);
+	}
+	if (pos > DS1511_RAM_MAX) {
+		pos = DS1511_RAM_MAX;
+	}
+	if (size + pos > DS1511_RAM_MAX + 1) {
+		size = DS1511_RAM_MAX - pos + 1;
+	}
+	rtc_write(pos, DS1511_RAMADDR_LSB);
+	for (count = 0; size > 0; count++, size--) {
+		*buf++ = rtc_read(DS1511_RAMDATA);
+	}
+	if (count > 1) {
+		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD);
+	}
+	return count;
+}
+
+ static ssize_t
+ds1511_nvram_write(struct kobject *kobj, struct bin_attribute *bin_attr,
+				char *buf, loff_t pos, size_t size)
+{
+	ssize_t count;
+
+	/*
+	 * if count is more than one, turn on "burst" mode
+	 * turn it off when you're done
+	 */
+	if (size > 1) {
+		rtc_write((rtc_read(RTC_CMD) | DS1511_BME), RTC_CMD);
+	}
+	if (pos > DS1511_RAM_MAX) {
+		pos = DS1511_RAM_MAX;
+	}
+	if (size + pos > DS1511_RAM_MAX + 1) {
+		size = DS1511_RAM_MAX - pos + 1;
+	}
+	rtc_write(pos, DS1511_RAMADDR_LSB);
+	for (count = 0; size > 0; count++, size--) {
+		rtc_write(*buf++, DS1511_RAMDATA);
+	}
+	if (count > 1) {
+		rtc_write((rtc_read(RTC_CMD) & ~DS1511_BME), RTC_CMD);
+	}
+	return count;
+}
+
+static struct bin_attribute ds1511_nvram_attr = {
+	.attr = {
+		.name = "nvram",
+		.mode = S_IRUGO | S_IWUGO,
+		.owner = THIS_MODULE,
+	},
+	.size = DS1511_RAM_MAX,
+	.read = ds1511_nvram_read,
+	.write = ds1511_nvram_write,
+};
+
+ static int __devinit
+ds1511_rtc_probe(struct platform_device *pdev)
+{
+	struct rtc_device *rtc;
+	struct resource *res;
+	struct rtc_plat_data *pdata = NULL;
+	int ret = 0;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		return -ENODEV;
+	}
+	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
+	if (!pdata) {
+		return -ENOMEM;
+	}
+	pdata->irq = -1;
+	pdata->size = res->end - res->start + 1;
+	if (!request_mem_region(res->start, pdata->size, pdev->name)) {
+		ret = -EBUSY;
+		goto out;
+	}
+	pdata->baseaddr = res->start;
+	pdata->size = pdata->size;
+	ds1511_base = ioremap(pdata->baseaddr, pdata->size);
+	if (!ds1511_base) {
+		ret = -ENOMEM;
+		goto out;
+	}
+	pdata->ioaddr = ds1511_base;
+	pdata->irq = platform_get_irq(pdev, 0);
+
+	/*
+	 * turn on the clock and the crystal, etc.
+	 */
+	rtc_write(0, RTC_CMD);
+	rtc_write(0, RTC_CMD1);
+	/*
+	 * clear the wdog counter
+	 */
+	rtc_write(0, DS1511_WD_MSEC);
+	rtc_write(0, DS1511_WD_SEC);
+	/*
+	 * start the clock
+	 */
+	rtc_enable_update();
+
+	/*
+	 * check for a dying bat-tree
+	 */
+	if (rtc_read(RTC_CMD1) & DS1511_BLF1) {
+		dev_warn(&pdev->dev, "voltage-low detected.\n");
+	}
+
+	/*
+	 * if the platform has an interrupt in mind for this device,
+	 * then by all means, set it
+	 */
+	if (pdata->irq >= 0) {
+		rtc_read(RTC_CMD1);
+		if (request_irq(pdata->irq, ds1511_interrupt,
+			IRQF_DISABLED | IRQF_SHARED, pdev->name, pdev) < 0) {
+
+			dev_warn(&pdev->dev, "interrupt not available.\n");
+			pdata->irq = -1;
+		}
+	}
+
+	rtc = rtc_device_register(pdev->name, &pdev->dev, &ds1511_rtc_ops,
+		THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		ret = PTR_ERR(rtc);
+		goto out;
+	}
+	pdata->rtc = rtc;
+	platform_set_drvdata(pdev, pdata);
+	ret = sysfs_create_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr);
+	if (ret) {
+		goto out;
+	}
+	return 0;
+ out:
+	if (pdata->rtc) {
+		rtc_device_unregister(pdata->rtc);
+	}
+	if (pdata->irq >= 0) {
+		free_irq(pdata->irq, pdev);
+	}
+	if (ds1511_base) {
+		iounmap(ds1511_base);
+		ds1511_base = NULL;
+	}
+	if (pdata->baseaddr) {
+		release_mem_region(pdata->baseaddr, pdata->size);
+	}
+
+	kfree(pdata);
+	return ret;
+}
+
+ static int __devexit
+ds1511_rtc_remove(struct platform_device *pdev)
+{
+	struct rtc_plat_data *pdata = platform_get_drvdata(pdev);
+
+	sysfs_remove_bin_file(&pdev->dev.kobj, &ds1511_nvram_attr);
+	rtc_device_unregister(pdata->rtc);
+	pdata->rtc = NULL;
+	if (pdata->irq >= 0) {
+		/*
+		 * disable the alarm interrupt
+		 */
+		rtc_write(rtc_read(RTC_CMD) & ~RTC_TIE, RTC_CMD);
+		rtc_read(RTC_CMD1);
+		free_irq(pdata->irq, pdev);
+	}
+	iounmap(pdata->ioaddr);
+	ds1511_base = NULL;
+	release_mem_region(pdata->baseaddr, pdata->size);
+	kfree(pdata);
+	return 0;
+}
+
+static struct platform_driver ds1511_rtc_driver = {
+	.probe		= ds1511_rtc_probe,
+	.remove		= __devexit_p(ds1511_rtc_remove),
+	.driver		= {
+		.name	= "ds1511",
+		.owner	= THIS_MODULE,
+	},
+};
+
+ static int __init
+ds1511_rtc_init(void)
+{
+	return platform_driver_register(&ds1511_rtc_driver);
+}
+
+ static void __exit
+ds1511_rtc_exit(void)
+{
+	return platform_driver_unregister(&ds1511_rtc_driver);
+}
+
+module_init(ds1511_rtc_init);
+module_exit(ds1511_rtc_exit);
+
+MODULE_AUTHOR("Andrew Sharp <andy.sharp@onstor.com>");
+MODULE_DESCRIPTION("Dallas DS1511 RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRV_VERSION);

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2007-12-05 15:30 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-12-04 20:00 [PATCH] Platform real time clock driver for Dallas 1511 chip Andrew Sharp
2007-12-04 21:04 ` Andrew Morton
2007-12-05  0:46 Andrew Sharp
2007-12-05 15:32 ` Atsushi Nemoto

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.