From patchwork Fri Nov 5 11:46:24 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Voss, Nikolaus" X-Patchwork-Id: 70253 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-fx0-f56.google.com (mail-fx0-f56.google.com [209.85.161.56]) by ozlabs.org (Postfix) with ESMTP id 24D821007D1 for ; Fri, 5 Nov 2010 22:46:39 +1100 (EST) Received: by fxm13 with SMTP id 13sf642788fxm.11 for ; Fri, 05 Nov 2010 04:46:35 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=beta; h=domainkey-signature:received:x-beenthere:received:received:received :received:received-spf:from:to:cc:date:subject:thread-topic :thread-index:message-id:accept-language:x-ms-has-attach :x-ms-tnef-correlator:acceptlanguage:mime-version:x-original-sender :x-original-authentication-results:reply-to:precedence:mailing-list :list-id:list-post:list-help:list-archive:sender:list-subscribe :list-unsubscribe:content-language:content-type; bh=9HnA05T0VIoN/ZU+MZEADStMvz469FrOLCsAy/o1O9g=; b=AWkKbrdxi3y0NGqyHUPAdrc3WaNK7jDm/A/Zxy71MOHloRn7QbqJgURr5476ps/5+K uLNIUTUpP/foBnJmdCgfjJCbXA9Kjy3RXA/LAxY7sGWA1rxjuXyKv5WB/0u7PVXfeBhc bkuVD/ikVcZh7sVrICuNocZEqd+bzoIlJCr9U= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=x-beenthere:received-spf:from:to:cc:date:subject:thread-topic :thread-index:message-id:accept-language:x-ms-has-attach :x-ms-tnef-correlator:acceptlanguage:mime-version:x-original-sender :x-original-authentication-results:reply-to:precedence:mailing-list :list-id:list-post:list-help:list-archive:sender:list-subscribe :list-unsubscribe:content-language:content-type; b=GIKx2EkATQzXg53FNwu+Y8jtoPyaXR3SooCin2sylXxzAP+MaEGUthf3OdrQqbb5pH ryGh8MJ15lBbYylpRZKxkeOZQh7eqJhGigaOmv2mXAAPhoQLChM8dUobbjH6ShKysyWy eAbpLfSyFzPiACatGKgJV08M5FaoeGV65rJs8= Received: by 10.223.126.11 with SMTP id a11mr41117fas.6.1288957594643; Fri, 05 Nov 2010 04:46:34 -0700 (PDT) X-BeenThere: rtc-linux@googlegroups.com Received: by 10.223.59.212 with SMTP id m20ls704901fah.3.p; Fri, 05 Nov 2010 04:46:33 -0700 (PDT) Received: by 10.223.87.5 with SMTP id u5mr67923fal.7.1288957593464; Fri, 05 Nov 2010 04:46:33 -0700 (PDT) Received: by 10.223.87.5 with SMTP id u5mr67922fal.7.1288957593434; Fri, 05 Nov 2010 04:46:33 -0700 (PDT) Received: from portal.weinmann.de (portal.weinmann.de [62.8.140.122]) by gmr-mx.google.com with ESMTP id 28si277744fas.11.2010.11.05.04.46.33; Fri, 05 Nov 2010 04:46:33 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of prvs=1925ebca4c=N.Voss@weinmann.de designates 62.8.140.122 as permitted sender) client-ip=62.8.140.122; From: "Voss, Nikolaus" To: "'rtc-linux@googlegroups.com'" CC: "Voss, Nikolaus" Date: Fri, 5 Nov 2010 12:46:24 +0100 Subject: [rtc-linux] [PATCH] Add basic support for ST M41T93 SPI RTC Thread-Topic: [PATCH] Add basic support for ST M41T93 SPI RTC Thread-Index: Act8wW76yYYdc2KtRm++LbQjEizoKg== Message-ID: Accept-Language: en-US, de-DE X-MS-Has-Attach: X-MS-TNEF-Correlator: acceptlanguage: en-US, de-DE MIME-Version: 1.0 X-Original-Sender: n.voss@weinmann.de X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: best guess record for domain of prvs=1925ebca4c=N.Voss@weinmann.de designates 62.8.140.122 as permitted sender) smtp.mail=prvs=1925ebca4c=N.Voss@weinmann.de Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: List-Post: , List-Help: , List-Archive: Sender: rtc-linux@googlegroups.com List-Subscribe: , List-Unsubscribe: , Content-Language: de-DE This driver has been tested with factory-new ST m41t93 SPI rtcs and with "run-in" species with and without backup batteries. Signed-off-by: Nikolaus Voss --- drivers/rtc/Kconfig | 9 ++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-m41t93.c | 225 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-m41t93.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 48ca713..230c1cd 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -349,6 +349,15 @@ comment "SPI RTC drivers" if SPI_MASTER +config RTC_DRV_M41T93 + tristate "ST M41T93" + help + If you say yes here you will get support for the + ST M41T93 SPI RTC chip. + + This driver can also be built as a module. If so, the module + will be called rtc-m41t93. + config RTC_DRV_M41T94 tristate "ST M41T94" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 0f207b3..70327e6 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -52,6 +52,7 @@ obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o obj-$(CONFIG_RTC_DRV_ISL12022) += rtc-isl12022.o obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o +obj-$(CONFIG_RTC_DRV_M41T93) += rtc-m41t93.o obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o obj-$(CONFIG_RTC_DRV_M48T59) += rtc-m48t59.o diff --git a/drivers/rtc/rtc-m41t93.c b/drivers/rtc/rtc-m41t93.c new file mode 100644 index 0000000..28d203a --- /dev/null +++ b/drivers/rtc/rtc-m41t93.c @@ -0,0 +1,225 @@ +/* + * + * Driver for ST M41T93 SPI RTC + * + * (c) 2010 Nikolaus Voss, Weinmann Medical GmbH + * + * 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 +#include +#include +#include +#include +#include + +#define M41T93_REG_SSEC 0 +#define M41T93_REG_ST_SEC 1 +#define M41T93_REG_MIN 2 +#define M41T93_REG_CENT_HOUR 3 +#define M41T93_REG_WDAY 4 +#define M41T93_REG_DAY 5 +#define M41T93_REG_MON 6 +#define M41T93_REG_YEAR 7 + + +#define M41T93_REG_ALM_HOUR_HT 0xc +#define M41T93_REG_FLAGS 0xf + +#define M41T93_FLAG_ST (1 << 7) +#define M41T93_FLAG_OF (1 << 2) +#define M41T93_FLAG_BL (1 << 4) +#define M41T93_FLAG_HT (1 << 6) + +static inline int m41t93_set_reg(struct spi_device *spi, u8 addr, u8 data) +{ + u8 buf[2]; + + /* MSB must be '1' to write */ + buf[0] = addr | 0x80; + buf[1] = data; + + return spi_write(spi, buf, sizeof(buf)); +} + +static int m41t93_set_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + u8 buf[9] = {0x80}; /* write cmd + 8 data bytes */ + u8 * const data = &buf[1]; /* ptr to first data byte */ + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "write", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + if (tm->tm_year < 100) { + dev_warn(&spi->dev, "unsupported date (before 2000-01-01).\n"); + return -EINVAL; + } + + data[M41T93_REG_SSEC] = 0; + data[M41T93_REG_ST_SEC] = bin2bcd(tm->tm_sec); + data[M41T93_REG_MIN] = bin2bcd(tm->tm_min); + data[M41T93_REG_CENT_HOUR] = bin2bcd(tm->tm_hour) | + ((tm->tm_year/100-1) << 6); + data[M41T93_REG_DAY] = bin2bcd(tm->tm_mday); + data[M41T93_REG_WDAY] = bin2bcd(tm->tm_wday + 1); + data[M41T93_REG_MON] = bin2bcd(tm->tm_mon + 1); + data[M41T93_REG_YEAR] = bin2bcd(tm->tm_year % 100); + + return spi_write(spi, buf, sizeof(buf)); +} + + +static int m41t93_get_time(struct device *dev, struct rtc_time *tm) +{ + struct spi_device *spi = to_spi_device(dev); + const u8 start_addr = 0; + u8 buf[8]; + int century_after_1900; + int tmp; + int ret = 0; + + /* Check status of clock. Two states must be considered: + 1. halt bit (HT) is set: the clock is running but update of readout + registers has been disabled due to power failure. This is normal + case after poweron. Time is valid after resetting HT bit. + 2. oscillator fail bit (OF) is set. Oscillator has be stopped and + time is invalid: + a) OF can be immeditely reset. + b) OF cannot be immediately reset: oscillator has to be restarted. + */ + tmp = spi_w8r8(spi, M41T93_REG_ALM_HOUR_HT); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_HT) { + dev_dbg(&spi->dev, "HT bit is set, reenable clock update.\n"); + m41t93_set_reg(spi, M41T93_REG_ALM_HOUR_HT, + tmp & ~M41T93_FLAG_HT); + } + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + + if (tmp & M41T93_FLAG_OF) { + ret = -EINVAL; + dev_warn(&spi->dev, "OF bit is set, resetting.\n"); + m41t93_set_reg(spi, M41T93_REG_FLAGS, tmp & ~M41T93_FLAG_OF); + + tmp = spi_w8r8(spi, M41T93_REG_FLAGS); + if (tmp < 0) + return tmp; + else if (tmp & M41T93_FLAG_OF) { + u8 reset_osc = buf[M41T93_REG_ST_SEC] | M41T93_FLAG_ST; + + dev_warn(&spi->dev, + "OF bit is still set, kickstarting clock.\n"); + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + reset_osc &= ~M41T93_FLAG_ST; + m41t93_set_reg(spi, M41T93_REG_ST_SEC, reset_osc); + } + } + + if (tmp & M41T93_FLAG_BL) + dev_warn(&spi->dev, "BL bit is set, replace battery.\n"); + + /* read actual time/date */ + tmp = spi_write_then_read(spi, &start_addr, 1, buf, sizeof(buf)); + if (tmp < 0) + return tmp; + + tm->tm_sec = bcd2bin(buf[M41T93_REG_ST_SEC]); + tm->tm_min = bcd2bin(buf[M41T93_REG_MIN]); + tm->tm_hour = bcd2bin(buf[M41T93_REG_CENT_HOUR] & 0x3f); + tm->tm_mday = bcd2bin(buf[M41T93_REG_DAY]); + tm->tm_mon = bcd2bin(buf[M41T93_REG_MON]) - 1; + tm->tm_wday = bcd2bin(buf[M41T93_REG_WDAY] & 0x0f) - 1; + + century_after_1900 = (buf[M41T93_REG_CENT_HOUR] >> 6) + 1; + tm->tm_year = bcd2bin(buf[M41T93_REG_YEAR]) + century_after_1900 * 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", tm->tm_sec, tm->tm_min, + tm->tm_hour, tm->tm_mday, + tm->tm_mon, tm->tm_year, tm->tm_wday); + + return ret < 0 ? ret : rtc_valid_tm(tm); +} + + +static const struct rtc_class_ops m41t93_rtc_ops = { + .read_time = m41t93_get_time, + .set_time = m41t93_set_time, +}; + +static struct spi_driver m41t93_driver; + +static int __devinit m41t93_probe(struct spi_device *spi) +{ + struct rtc_device *rtc; + int res; + + spi->bits_per_word = 8; + spi_setup(spi); + + res = spi_w8r8(spi, M41T93_REG_WDAY); + if (res < 0 || (res & 0xf8) != 0) { + dev_err(&spi->dev, "not found 0x%x.\n", res); + return -ENODEV; + } + + rtc = rtc_device_register(m41t93_driver.driver.name, + &spi->dev, &m41t93_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + dev_set_drvdata(&spi->dev, rtc); + + return 0; +} + + +static int __devexit m41t93_remove(struct spi_device *spi) +{ + struct rtc_device *rtc = platform_get_drvdata(spi); + + if (rtc) + rtc_device_unregister(rtc); + + return 0; +} + +static struct spi_driver m41t93_driver = { + .driver = { + .name = "rtc-m41t93", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = m41t93_probe, + .remove = __devexit_p(m41t93_remove), +}; + +static __init int m41t93_init(void) +{ + return spi_register_driver(&m41t93_driver); +} +module_init(m41t93_init); + +static __exit void m41t93_exit(void) +{ + spi_unregister_driver(&m41t93_driver); +} +module_exit(m41t93_exit); + +MODULE_AUTHOR("Nikolaus Voss "); +MODULE_DESCRIPTION("Driver for ST M41T93 SPI RTC"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("spi:rtc-m41t93");