From patchwork Thu Jan 29 11:04:01 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alessandro Zummo X-Patchwork-Id: 21000 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from yx-out-2122.google.com (yx-out-2122.google.com [74.125.44.24]) by ozlabs.org (Postfix) with ESMTP id 2073DDE054 for ; Thu, 29 Jan 2009 22:04:11 +1100 (EST) Received: by yx-out-2122.google.com with SMTP id 35so2354935yxh.11 for ; Thu, 29 Jan 2009 03:04:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=beta; h=domainkey-signature:received:received:x-sender:x-apparently-to :received:received:received-spf:authentication-results:received :received:from:subject:to:cc:date:message-id:user-agent:mime-version :content-type:reply-to:sender:precedence:x-google-loop:mailing-list :list-id:list-post:list-help:list-unsubscribe:x-beenthere-env :x-beenthere; bh=SWdnLmPYMeSF99eAq2awprocHSx8ODowhQrSzulcZlk=; b=C41QKEjPAhUjCMZllSMDxDVDxVNvH6wL+KBJo0SrrtQyIfhUvB+QkwVJkAtDUYNBQ/ XlRxFOodQ32pzXXEaGKeSXMFr5XKsAGS4kkbsm1MoqVe/gAGljf1cBXCKpDsaj8yFJnL xVp8dOsxYiHCS+itCICkNvrej793empwxFBhE= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=x-sender:x-apparently-to:received-spf:authentication-results:from :subject:to:cc:date:message-id:user-agent:mime-version:content-type :reply-to:sender:precedence:x-google-loop:mailing-list:list-id :list-post:list-help:list-unsubscribe:x-beenthere-env:x-beenthere; b=1wEdHrptoLW00ShJoSfPsO8jPj4ZlXlsiNBYG9uAOAds9anUEFqSaMuIFeuZuRFGD6 tQT5yoeUBuWYqCE7CxCdLtdVolG9MYmrMDWPQjMfnAOOKA9/byyTv0CaeAdokoRYiHGR XyWIwCBTe8JK2iXeL4i+4IGiskndwLTXlRM2s= Received: by 10.100.132.14 with SMTP id f14mr2946694and.17.1233227044122; Thu, 29 Jan 2009 03:04:04 -0800 (PST) Received: by 10.176.101.40 with SMTP id y40gr1864yqb.0; Thu, 29 Jan 2009 03:04:04 -0800 (PST) X-Sender: a.zummo@towertech.it X-Apparently-To: rtc-linux@googlegroups.com Received: by 10.86.94.11 with SMTP id r11mr678fgb.13.1233227043292; Thu, 29 Jan 2009 03:04:03 -0800 (PST) Received: from mx0.towertech.it (mx0.towertech.it [213.215.222.73]) by mx.google.com with SMTP id 5si1963768fge.11.2009.01.29.03.04.02; Thu, 29 Jan 2009 03:04:03 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of a.zummo@towertech.it designates 213.215.222.73 as permitted sender) client-ip=213.215.222.73; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of a.zummo@towertech.it designates 213.215.222.73 as permitted sender) smtp.mail=a.zummo@towertech.it Received: (qmail 7619 invoked from network); 29 Jan 2009 12:04:02 +0100 Received: from unknown (HELO i1501.lan.towertech.it) (81.208.60.204) by mx0.towertech.it with SMTP; 29 Jan 2009 12:04:02 +0100 From: Alessandro Zummo Subject: [rtc-linux] [PATCH] rtc: Add DS1685/DS1687 rtc driver To: akpm@linux-foundation.org Cc: rtc-linux@googlegroups.com, Matthias Fuchs , Alessandro Zummo Date: Thu, 29 Jan 2009 12:04:01 +0100 Message-ID: <20090129110401.5958.76573.stgit@i1501.lan.towertech.it> User-Agent: StGIT/0.14.2 Mime-Version: 1.0 Reply-To: rtc-linux@googlegroups.com Sender: rtc-linux@googlegroups.com Precedence: bulk X-Google-Loop: groups Mailing-List: list rtc-linux@googlegroups.com; contact rtc-linux+owner@googlegroups.com List-Id: List-Post: List-Help: List-Unsubscribe: , X-BeenThere-Env: rtc-linux@googlegroups.com X-BeenThere: rtc-linux@googlegroups.com From: Matthias Fuchs This patch adds the rtc-ds1685 driver for DS1685/DS1687 rtc chips. Signed-off-by: Matthias Fuchs [a.zummo@towertech.it: replaced __devinit/__devexit due to platform_driver_probe] Signed-off-by: Alessandro Zummo --- drivers/rtc/Kconfig | 12 + drivers/rtc/Makefile | 1 drivers/rtc/rtc-ds1685.c | 470 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 483 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-ds1685.c --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to "rtc-linux". Membership options at http://groups.google.com/group/rtc-linux . Please read http://groups.google.com/group/rtc-linux/web/checklist before submitting a driver. -~----------~----~----~----~------~----~------~--~--- diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index cced4d1..951190c 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -165,6 +165,18 @@ config RTC_DRV_DS1672 This driver can also be built as a module. If so, the module will be called rtc-ds1672. +config RTC_DRV_DS1685 + tristate "Dallas/Maxim DS1685/DS1687" + help + If you say yes here you get support for the + Dallas/Maxim DS1685/DS1687 timekeeping chip. + + This RTC chip is used on EPPC-405-UC modules + by electronic system design gmbh. + + This driver can also be built as a module. If so, the module + will be called rtc-ds1685. + config RTC_DRV_MAX6900 tristate "Maxim MAX6900" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6e28021..126b02b 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -33,6 +33,7 @@ obj-$(CONFIG_RTC_DRV_DS1390) += rtc-ds1390.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_DS1685) += rtc-ds1685.o obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o obj-$(CONFIG_RTC_DRV_DS3234) += rtc-ds3234.o obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o diff --git a/drivers/rtc/rtc-ds1685.c b/drivers/rtc/rtc-ds1685.c new file mode 100644 index 0000000..4c6b801 --- /dev/null +++ b/drivers/rtc/rtc-ds1685.c @@ -0,0 +1,470 @@ +/* + * An rtc driver for the Dallas DS1685/DS1687 + * + * Copyright (C) 2009 Matthias Fuchs + * + * 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 +#include +#include + +#define DRV_VERSION "0.1" + +#define NVRAM_SIZE_B0 114 +#define NVRAM_SIZE_B1 128 +#define NVRAM_SIZE (NVRAM_SIZE_B0 + NVRAM_SIZE_B1) + +#define RTC_SECONDS 0 +#define RTC_MINUTES 2 +#define RTC_HOURS 4 +#define RTC_DAY 6 +#define RTC_DATE 7 +#define RTC_MONTH 8 +#define RTC_YEAR 9 + +#define RTC_CONTROLA 0x0a +#define RTC_CONTROLB 0x0b +#define RTC_CONTROLC 0x0c +#define RTC_CONTROLD 0x0d +#define RTC_NVRAM_START_B0 0x0e + +/* bits in control A register */ +#define RTC_DV2 0x40 +#define RTC_DV1 0x20 +#define RTC_DV0 0x10 + +/* bits in control B register */ +#define RTC_SET 0x80 +#define RTC_DM 0x04 +#define RTC_2412 0x02 + +/* bits in control D register */ +#define RTC_VRT 0x80 + +/* bank 1 only registers */ +#define RTC_MODEL 0x40 +#define RTC_CENTURY 0x48 +#define RTC_RAM_ADDR 0x50 +#define RTC_RAM_DATA 0x53 + +struct rtc_plat_data { + struct rtc_device *rtc; + void __iomem *ioaddr; + size_t size; + resource_size_t baseaddr; + unsigned long last_jiffies; +}; + +static int ds1685_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned int ctrla, ctrlb; + + /* set SET bit */ + ctrlb = readb(ioaddr + RTC_CONTROLB); + ctrlb |= RTC_SET; + writeb(ctrlb, ioaddr + RTC_CONTROLB); + + writeb(bin2bcd(tm->tm_year % 100), ioaddr + RTC_YEAR); + writeb(bin2bcd(tm->tm_mon + 1), ioaddr + RTC_MONTH); + writeb(bin2bcd(tm->tm_wday), ioaddr + RTC_DAY); + writeb(bin2bcd(tm->tm_mday), ioaddr + RTC_DATE); + writeb(bin2bcd(tm->tm_hour), ioaddr + RTC_HOURS); + writeb(bin2bcd(tm->tm_min), ioaddr + RTC_MINUTES); + writeb(bin2bcd(tm->tm_sec), ioaddr + RTC_SECONDS); + + /* switch to 2nd bank */ + ctrla = readb(ioaddr + RTC_CONTROLA); + ctrla |= RTC_DV0; + writeb(ctrla, ioaddr + RTC_CONTROLA); + + writeb(bin2bcd((tm->tm_year + 1900) / 100), ioaddr + RTC_CENTURY); + + /* switch back to original bank */ + ctrla &= ~RTC_DV0; + writeb(ctrla, ioaddr + RTC_CONTROLA); + + /* clear SET bit */ + ctrlb &= ~RTC_SET; + writeb(ctrlb, ioaddr + RTC_CONTROLB); + + return 0; +} + +static int ds1685_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned int century, year, month, day, hour, minute, second, week; + unsigned int ctrla, ctrlb; + + /* give enough time to update RTC in case of continuous read */ + if (pdata->last_jiffies == jiffies) + msleep(1); + pdata->last_jiffies = jiffies; + + /* set SET bit */ + ctrlb = readb(ioaddr + RTC_CONTROLB); + ctrlb |= RTC_SET; + writeb(ctrlb, ioaddr + RTC_CONTROLB); + + second = readb(ioaddr + RTC_SECONDS); + minute = readb(ioaddr + RTC_MINUTES); + hour = readb(ioaddr + RTC_HOURS); + day = readb(ioaddr + RTC_DATE); + week = readb(ioaddr + RTC_DAY); + month = readb(ioaddr + RTC_MONTH); + year = readb(ioaddr + RTC_YEAR); + + /* switch to 2nd bank */ + ctrla = readb(ioaddr + RTC_CONTROLA); + ctrla |= RTC_DV0; + writeb(ctrla, ioaddr + RTC_CONTROLA); + + century = readb(ioaddr + RTC_CENTURY); + + /* switch back to original bank */ + ctrla &= ~RTC_DV0; + writeb(ctrla, ioaddr + RTC_CONTROLA); + + /* clear SET bit */ + ctrlb &= ~RTC_SET; + writeb(ctrlb, ioaddr + RTC_CONTROLB); + + tm->tm_sec = bcd2bin(second); + tm->tm_min = bcd2bin(minute); + tm->tm_hour = bcd2bin(hour); + tm->tm_mday = bcd2bin(day); + tm->tm_wday = bcd2bin(week); + tm->tm_mon = bcd2bin(month) - 1; + + /* year is 1900 + tm->tm_year */ + tm->tm_year = bcd2bin(year) + bcd2bin(century) * 100 - 1900; + + return rtc_valid_tm(tm); +} + +static int ds1685_get_ssn(struct rtc_plat_data *pdata, u8 *ssn) +{ + void __iomem *ioaddr = pdata->ioaddr; + int i; + unsigned int ctrl; + + /* switch to 2nd bank */ + ctrl = readb(ioaddr + RTC_CONTROLA); + ctrl |= RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + + /* read silicon serial number */ + for (i = 0; i < 8; i++) + ssn[i] = readb(ioaddr + RTC_MODEL + i); + + /* switch back to original bank */ + ctrl &= ~RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + + return 0; +} + +static int ds1685_rtc_proc(struct device *dev, struct seq_file *seq) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned int val; + u8 ssn[8]; + + mutex_lock(&pdata->rtc->ops_lock); + val = readb(ioaddr + RTC_CONTROLD); + ds1685_get_ssn(pdata, ssn); + mutex_unlock(&pdata->rtc->ops_lock); + + seq_printf(seq, "battery\t\t: %s\n", + (val & RTC_VRT) ? "ok" : "exhausted"); + seq_printf(seq, "serial#\t\t: " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + ssn[0], ssn[1], ssn[2], ssn[3], + ssn[4], ssn[5], ssn[6], ssn[7]); + return 0; +} + +static const struct rtc_class_ops ds1685_rtc_ops = { + .proc = ds1685_rtc_proc, + .read_time = ds1685_rtc_read_time, + .set_time = ds1685_rtc_set_time, +}; + +static ssize_t ds1685_nvram_read(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct platform_device *pdev = + to_platform_device(container_of(kobj, struct device, kobj)); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + ssize_t count; + + mutex_lock(&pdata->rtc->ops_lock); + for (count = 0; size > 0 && pos < NVRAM_SIZE_B0; count++, size--) + *buf++ = readb(ioaddr + RTC_NVRAM_START_B0 + pos++); + + if (size > 0) { + unsigned int ctrl = readb(ioaddr + RTC_CONTROLA); + /* switch to 2nd bank */ + ctrl |= RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + + for (count = 0; size > 0 && pos < NVRAM_SIZE; count++, size--) { + writeb(pos - NVRAM_SIZE_B0, ioaddr + RTC_RAM_ADDR); + *buf++ = readb(ioaddr + RTC_RAM_DATA); + pos++; + } + + /* switch back to original bank */ + ctrl &= ~RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + } + mutex_unlock(&pdata->rtc->ops_lock); + return count; +} + +static ssize_t ds1685_nvram_write(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct platform_device *pdev = + to_platform_device(container_of(kobj, struct device, kobj)); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + ssize_t count; + + mutex_lock(&pdata->rtc->ops_lock); + for (count = 0; size > 0 && pos < NVRAM_SIZE_B0; count++, size--) + writeb(*buf++, ioaddr + RTC_NVRAM_START_B0 + pos++); + + if (size > 0) { + unsigned int ctrl = readb(ioaddr + RTC_CONTROLA); + /* switch to 2nd bank */ + ctrl |= RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + + for (count = 0; size > 0 && pos < NVRAM_SIZE; count++, size--) { + writeb(pos - NVRAM_SIZE_B0, ioaddr + RTC_RAM_ADDR); + writeb(*buf++, ioaddr + RTC_RAM_DATA); + pos++; + } + + /* switch back to original bank */ + ctrl &= ~RTC_DV0; + writeb(ctrl, ioaddr + RTC_CONTROLA); + } + mutex_unlock(&pdata->rtc->ops_lock); + + return count; +} + +static struct bin_attribute ds1685_nvram_attr = { + .attr = { + .name = "nvram", + .mode = S_IRUGO | S_IWUSR, + }, + .read = ds1685_nvram_read, + .write = ds1685_nvram_write, + .size = NVRAM_SIZE +}; + +static ssize_t +ds1685_sysfs_show_battery(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + void __iomem *ioaddr = pdata->ioaddr; + unsigned int val; + + mutex_lock(&pdata->rtc->ops_lock); + val = readb(ioaddr + RTC_CONTROLD); + mutex_unlock(&pdata->rtc->ops_lock); + + return sprintf(buf, "%s\n", (val & RTC_VRT) ? "ok" : "exhausted"); +} + +static DEVICE_ATTR(battery, S_IRUGO, ds1685_sysfs_show_battery, NULL); + +static ssize_t +ds1685_sysfs_show_serial(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + u8 ssn[8]; + + mutex_lock(&pdata->rtc->ops_lock); + ds1685_get_ssn(pdata, ssn); + mutex_unlock(&pdata->rtc->ops_lock); + + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + ssn[0], ssn[1], ssn[2], ssn[3], + ssn[4], ssn[5], ssn[6], ssn[7]); +} + +static DEVICE_ATTR(serial, S_IRUGO, ds1685_sysfs_show_serial, NULL); + +static int ds1685_sysfs_register(struct device *dev) +{ + int err; + + err = sysfs_create_bin_file(&dev->kobj, &ds1685_nvram_attr); + if (err) + return err; + + err = device_create_file(dev, &dev_attr_battery); + if (err) { + sysfs_remove_bin_file(&dev->kobj, &ds1685_nvram_attr); + return err; + } + + err = device_create_file(dev, &dev_attr_serial); + if (err) { + device_remove_file(dev, &dev_attr_battery); + sysfs_remove_bin_file(&dev->kobj, &ds1685_nvram_attr); + return err; + } + + return 0; +} + +static int ds1685_sysfs_unregister(struct device *dev) +{ + sysfs_remove_bin_file(&dev->kobj, &ds1685_nvram_attr); + device_remove_file(dev, &dev_attr_battery); + device_remove_file(dev, &dev_attr_serial); + + return 0; +} + +static int __init ds1685_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct resource *res; + unsigned int ctrl; + struct rtc_plat_data *pdata = NULL; + void __iomem *ioaddr = 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->size = resource_size(res); + if (!request_mem_region(res->start, pdata->size, pdev->name)) { + ret = -EBUSY; + goto out; + } + pdata->baseaddr = res->start; + + ioaddr = ioremap(pdata->baseaddr, pdata->size); + if (!ioaddr) { + ret = -ENOMEM; + goto out; + } + pdata->ioaddr = ioaddr; + + /* turn RTC on if it was not on */ + ctrl = readb(ioaddr + RTC_CONTROLA); + if (!(ctrl & RTC_DV1)) { + dev_warn(&pdev->dev, + "oscillator stop detected - enabled!\n"); + ctrl |= RTC_DV1; + } + ctrl &= ~RTC_DV2; + ctrl &= ~RTC_DV0; /* enable original bank */ + writeb(ctrl, ioaddr + RTC_CONTROLA); + + if (!(readb(ioaddr + RTC_CONTROLD) & RTC_VRT)) + dev_warn(&pdev->dev, "low battery detected.\n"); + + ctrl = readb(ioaddr + RTC_CONTROLB); + if ((ctrl & RTC_DM) || (!(ctrl & RTC_2412))) { + dev_dbg(&pdev->dev, "only 24-hr BCD mode supported\n"); + ret = -ENXIO; + goto out; + } + + rtc = rtc_device_register(pdev->name, &pdev->dev, + &ds1685_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 = ds1685_sysfs_register(&pdev->dev); + if (ret) + goto out; + return 0; + out: + if (pdata->rtc) + rtc_device_unregister(pdata->rtc); + if (pdata->ioaddr) + iounmap(pdata->ioaddr); + if (pdata->baseaddr) + release_mem_region(pdata->baseaddr, pdata->size); + kfree(pdata); + return ret; +} + +static int __exit ds1685_rtc_remove(struct platform_device *pdev) +{ + struct rtc_plat_data *pdata = platform_get_drvdata(pdev); + + ds1685_sysfs_unregister(&pdev->dev); + rtc_device_unregister(pdata->rtc); + iounmap(pdata->ioaddr); + release_mem_region(pdata->baseaddr, pdata->size); + kfree(pdata); + return 0; +} + +static struct platform_driver ds1685_rtc_driver = { + .remove = __exit_p(ds1685_rtc_remove), + .driver = { + .name = "rtc-ds1685", + .owner = THIS_MODULE, + }, +}; + +static __init int ds1685_init(void) +{ + return platform_driver_probe(&ds1685_rtc_driver, ds1685_rtc_probe); +} + +static __exit void ds1685_exit(void) +{ + platform_driver_unregister(&ds1685_rtc_driver); +} + +module_init(ds1685_init); +module_exit(ds1685_exit); + +MODULE_AUTHOR("Matthias Fuchs "); +MODULE_DESCRIPTION("Dallas DS1685/DS1687 RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRV_VERSION); +MODULE_ALIAS("platform:rtc-ds1685");