From patchwork Tue Nov 25 16:28:06 2008 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Antonio R. Costa" X-Patchwork-Id: 10656 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from yw-out-2122.google.com (yw-out-2122.google.com [74.125.46.26]) by ozlabs.org (Postfix) with ESMTP id E0EF4DDE23 for ; Wed, 26 Nov 2008 03:28:32 +1100 (EST) Received: by yw-out-2122.google.com with SMTP id 7so492924ywi.11 for ; Tue, 25 Nov 2008 08:28:30 -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 :dkim-signature:domainkey-signature:received:received:from:to:cc :subject:date:message-id:x-mailer:in-reply-to:references:reply-to :sender:precedence:x-google-loop:mailing-list:list-id:list-post :list-help:list-unsubscribe:x-beenthere-env:x-beenthere; bh=j3QXBo0/IVNBPqbzrcJoUj8a0BHnpTvLX2+ec10V0Bs=; b=Q+9hH0VLoCbyq21FffLM0+jK2BdNLugDZPYFj+p5pF7jUKXbSkhdkdt5XSSavVdMpB zc7Xid7IU3IVlRbSQvuC+MjZuhovTUwUgaYCRA+BXJNFbkPgUpV+rLJJnuKoU4fVdRy7 swJw/gmIlBQk62URd3riKXdcm5P1rPPUvI0SY= DomainKey-Signature: a=rsa-sha1; c=nofws; d=googlegroups.com; s=beta; h=x-sender:x-apparently-to:received-spf:authentication-results :dkim-signature:domainkey-signature:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references:reply-to:sender :precedence:x-google-loop:mailing-list:list-id:list-post:list-help :list-unsubscribe:x-beenthere-env:x-beenthere; b=XiwrMlWHWX6cbPHBZF6nKw4mS+TCF9aBdtaBE5iuvkGDuz5FsVkIbilo3E+RuKIkK9 31jpoqrsG2q6ib6JWWNoLorATLQo5KFfQZUxNTXKR5rsPUsDh0BtcJgJnNDHsCDLoyyH /B8RZFd8swj8/f96joq5SECR/UPkqnfjBzj3M= Received: by 10.100.197.3 with SMTP id u3mr344554anf.6.1227630507821; Tue, 25 Nov 2008 08:28:27 -0800 (PST) Received: by 10.176.15.39 with SMTP id 39gr1676yqo.0; Tue, 25 Nov 2008 08:28:27 -0800 (PST) X-Sender: costa.antonior@gmail.com X-Apparently-To: rtc-linux@googlegroups.com Received: by 10.90.33.15 with SMTP id g15mr1674122agg.9.1227630491938; Tue, 25 Nov 2008 08:28:11 -0800 (PST) Received: from gv-out-0910.google.com (gv-out-0910.google.com [216.239.58.186]) by mx.google.com with ESMTP id 7si5558368yxg.0.2008.11.25.08.28.10; Tue, 25 Nov 2008 08:28:10 -0800 (PST) Received-SPF: pass (google.com: domain of costa.antonior@gmail.com designates 216.239.58.186 as permitted sender) client-ip=216.239.58.186; Authentication-Results: mx.google.com; spf=pass (google.com: domain of costa.antonior@gmail.com designates 216.239.58.186 as permitted sender) smtp.mail=costa.antonior@gmail.com; dkim=pass (test mode) header.i=@gmail.com Received: by gv-out-0910.google.com with SMTP id n29so14802gve.21 for ; Tue, 25 Nov 2008 08:28:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references; bh=gNLV0f8JVz5/NRbwMjfDyaAnpktz1XhKwyaFm1aazNE=; b=oumMdo125VXFL9Bz42bjNNTjARr74KT1q9wtTagPccsMyoJPHhbBYtCXoe/KtBO8rD lZB3Q0AvfJipC5xzhsX9T2WRxS/BxJrZ++/ihIGqOCo4iZL7TCVXRiCWBwmri9qy9btp WnwZZEdzpz2bIoR1+k3Z1dhlPfRiNy4l0rUdc= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=myAWuAmtmc2aQAfoMihIS5UFZ8TNmdEGeXBwzSG5hyk7LbD6D1g2n2ZeRkgVrrGrRI 6lr9cztl23UUMrQeGGI8/XazpmX+CihzTlJ2SD+lTeZ6pifHN/9jJfCRhQ1tIYf/2Qn+ LJrH/5vwBRxIIzqDNTzlfVfi0Xb+HfV1jCvwY= Received: by 10.86.53.8 with SMTP id b8mr3113261fga.13.1227630489677; Tue, 25 Nov 2008 08:28:09 -0800 (PST) Received: from localhost.localdomain ([84.253.176.202]) by mx.google.com with ESMTPS id 12sm8253184fgg.0.2008.11.25.08.28.08 (version=SSLv3 cipher=RC4-MD5); Tue, 25 Nov 2008 08:28:08 -0800 (PST) From: "Antonio R. Costa" To: a.zummo@towertech.it Cc: rtc-linux@googlegroups.com, "Antonio R. Costa" Subject: [rtc-linux] [PATCH 1/1] [RFC] Support for DS1306 over SPI [RFC] Date: Tue, 25 Nov 2008 17:28:06 +0100 Message-Id: <1227630486-5359-1-git-send-email-costa.antonior@gmail.com> X-Mailer: git-send-email 1.5.4.3 In-Reply-To: <> References: <> 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 Further work will be performed to add support for alarm and non volatile memory. Signed-off-by: Antonio R. Costa --- drivers/rtc/Kconfig | 6 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-ds1306-spi.c | 759 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 766 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-ds1306-spi.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 4949dc4..85d12ac 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -282,6 +282,12 @@ config RTC_DRV_MAX6902 This driver can also be built as a module. If so, the module will be called rtc-max6902. +config RTC_DRV_DS1306 + boolean "Dallas DS1306 on SPI" + depends on SPI + help + If you say yes here you get support for the Maxim-Dallas DS1306 RTC chip via SPI. + config RTC_DRV_R9701 tristate "Epson RTC-9701JE" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index b6e14d5..1080fc7 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o obj-$(CONFIG_RTC_DRV_DS1216) += rtc-ds1216.o obj-$(CONFIG_RTC_DRV_DS1302) += rtc-ds1302.o +obj-$(CONFIG_RTC_DRV_DS1306) += rtc-ds1306-spi.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 diff --git a/drivers/rtc/rtc-ds1306-spi.c b/drivers/rtc/rtc-ds1306-spi.c new file mode 100644 index 0000000..8b38983 --- /dev/null +++ b/drivers/rtc/rtc-ds1306-spi.c @@ -0,0 +1,759 @@ +/* + * rtc-ds1306.c - RTC driver for DS1306 on SPI. + * + * Copyright (C) 2007 Antonio R. Costa (costa.antonior@atmel.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 Foundatio + */ + + +#ifdef DEBUG +#define DS1306_DEBUG +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +/* Registers map */ +#define DS1306_A0_OFF 0x07 +#define DS1306_A1_OFF 0x0b +#define DS1306_W_OFF 0x80 + +#define DS1306_REG_SECS 0x00 +#define DS1306_REG_MINS 0x01 +#define DS1306_REG_HOURS 0x02 +#define DS1306_REG_DAY 0x03 +#define DS1306_REG_DATE 0x04 +#define DS1306_REG_MONTHS 0x05 +#define DS1306_REG_YEARS 0x06 + +#define DS1306_REG_CTRL 0x0f +#define DS1306_REG_SR 0x10 + +#define DS1306_NVRAM 0x20 +#define DS1306_NVRAM_SIZE 0x60 +#define DS1306_SIZE 0x80 + +/* Register address macros */ + +#define DS1306_W(x) \ + ((x) + DS1306_W_OFF) + +#define DS1306_A0(x) \ + ((x) + DS1306_A0_OFF) + +#define DS1306_A1(x) \ + ((x) + DS1306_A1_OFF) + + +/* Bit fields */ + +#define DS1306_SEC 0x0f +#define DS1306_10_SEC 0x70 + +#define DS1306_MIN 0x0f +#define DS1306_10_MIN 0x70 + +#define DS1306_HOUR 0x0f +#define DS1306_10_HOUR_12 0x10 +#define DS1306_10_HOUR_24 0x30 +#define DS1306_P_A 0x20 +#define DS1306_12_24 0x40 + +#define DS1306_DAY 0x07 + +#define DS1306_DATE 0x0f +#define DS1306_10_DATE 0x30 + +#define DS1306_MONTH 0x0f +#define DS1306_10_MONTH 0x30 + +#define DS1306_YEAR 0x0f +#define DS1306_10_YEAR 0xf0 + +#define DS1306_CTRL_W 0x40 +#define DS1306_CTRL_HZ 0x04 +#define DS1306_CTRL_AIE1 0x02 +#define DS1306_CTRL_AIE0 0x01 + +#define DS1306_SR_IRQF0 0x01 +#define DS1306_SR_IRQF1 0x02 + +struct ds1306_data_t { + u8 reg_addr; + u8 reg_data[7]; +}; + +union ds1306_data_u { + struct ds1306_data_t s; + u8 b[8]; +}; + +struct ds1306_rtc { + struct rtc_device *rtc; + union ds1306_data_u data; + u8 irq; + u8 irq_mask; + u16 tx_buf[2]; + u16 rx_buf[2]; + spinlock_t lock; +}; + + +static void ds1306_set_reg(struct device *dev, unsigned char address, u8 data) +{ + struct spi_device *spi = to_spi_device(dev); + u8 tx_buf[2]; + + tx_buf[0] = DS1306_W(address); + tx_buf[1] = data; + + spi_write(spi, tx_buf, 2); +} + +static int ds1306_get_reg(struct device *dev, unsigned char address, u8 *data) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message message; + struct spi_transfer xfer; + u8 rx_buf[2]; + u8 tx_buf[2]; + int status; + + if (!data) + return -EINVAL; + + /* Build our spi message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + xfer.len = 2; + /* Can tx_buf and rx_buf be equal? The doc in spi.h is not sure... */ + xfer.tx_buf = tx_buf; + xfer.rx_buf = rx_buf; + + tx_buf[0] = address; + + spi_message_add_tail(&xfer, &message); + + /* do the I/O */ + status = spi_sync(spi, &message); + if (status == 0) + status = message.status; + else + return status; + + *data = rx_buf[1]; + + return status; +} + + +static int ds1306_read_burst(struct device *dev, u8 *buf, u8 offset,u8 len) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message message; + struct spi_transfer xfer; + u8 tx_buf[DS1306_SIZE+1], rx_buf[DS1306_SIZE+1]; + int status,i; + + if(unlikely(len > DS1306_SIZE)) + return -ENOMEM; + + /* build the message using temporary buffers */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + xfer.len = 1+len; /* Address + Burst read len registers */ + tx_buf[0] = offset; /* First register address */ + xfer.tx_buf = tx_buf; + xfer.rx_buf = rx_buf; + spi_message_add_tail(&xfer, &message); + + /* do the I/O */ + status = spi_sync(spi, &message); + if (status == 0) + status = message.status; + else + return status; + + /* Data could be thrown away */ + if(unlikely(!buf)) + return 0; + + /* Copy of data to final buffer skipping litter + * in the first position. + */ + for(i=1;i DS1306_SIZE)) + return -1; + + /* build the message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + memset(tx_buf,0, sizeof(tx_buf)); + memset(rx_buf,0, sizeof(rx_buf)); + + xfer.len = 1+len; /* Address + Burst read len registers */ + tx_buf[0] = DS1306_W(offset); /* First register address */ + xfer.tx_buf = tx_buf; + xfer.rx_buf = rx_buf; + + if(unlikely(!buf)) + return -ENOMEM; + for(i=1;iirq_mask); + + spi_message_add_tail(&xfer, &message); + + /* do the I/O */ + status = spi_sync(spi, &message); + + /* Write protect */ + ds1306_set_reg(dev, DS1306_REG_CTRL, DS1306_CTRL_W | chip->irq_mask); + + if (status == 0) + status = message.status; + else + return status; + + return 0; +} + + +static int ds1306_get_datetime(struct device *dev, struct rtc_time *dt) +{ + struct spi_device *spi = to_spi_device(dev); + struct ds1306_rtc *chip = dev_get_drvdata(dev); + struct spi_message message; + struct spi_transfer xfer; + int status; + + + /* build the message */ + spi_message_init(&message); + memset(&xfer, 0, sizeof(xfer)); + xfer.len = 1+7; /* Address + Burst read 7 registers */ + chip->data.s.reg_addr = DS1306_REG_SECS; /* First register address */ + xfer.tx_buf = &chip->data.s.reg_addr; + xfer.rx_buf = &chip->data.s.reg_addr; + spi_message_add_tail(&xfer, &message); + + /* do the I/O */ + status = spi_sync(spi, &message); + + if (status == 0) + status = message.status; + else + return status; + + /* ARC + * The chip sends data in this order: + * Seconds, Minutes, Hours, Day, Date, Month, Year + * Year starts from 1970 2 digit wide. + */ + + dt->tm_sec = BCD2BIN(chip->data.s.reg_data[0]); + dt->tm_min = BCD2BIN(chip->data.s.reg_data[1]); + dt->tm_hour = BCD2BIN(chip->data.s.reg_data[2]); + dt->tm_wday = BCD2BIN(chip->data.s.reg_data[3]); + dt->tm_mday = BCD2BIN(chip->data.s.reg_data[4]); + dt->tm_mon = BCD2BIN(chip->data.s.reg_data[5]); + dt->tm_year = BCD2BIN(chip->data.s.reg_data[6]); + + /* ARC + * 1) Time struct starts counting years from 1900. + * 2) We want to keep track from year 1970. + * So year will be tm_year+1900-1970 = tm_year-70; + * 3) Time struct starts counting month from 0. + * So month will be tm_mon+1 + */ + dt->tm_mon -= 1; + dt->tm_year += 70; + +#ifdef DS1306_DEBUG + dev_dbg(dev,"%s : Reading RTC values:\n",__FUNCTION__); + dev_dbg(dev,"tm_sec : %i\n",dt->tm_sec); + dev_dbg(dev,"tm_min : %i\n",dt->tm_min); + dev_dbg(dev,"tm_hour: %i\n",dt->tm_hour); + dev_dbg(dev,"tm_mday: %i\n",dt->tm_mday); + dev_dbg(dev,"tm_wday: %i\n",dt->tm_wday); + dev_dbg(dev,"tm_year: %i\n",dt->tm_year); +#endif + + + return 0; +} + + +static int ds1306_set_datetime(struct device *dev, struct rtc_time *dt) +{ + struct ds1306_rtc *chip = dev_get_drvdata(dev); + + /* ARC + * Refer to comments in ds1306_get_datetime + */ + dt->tm_mon += 1; + dt->tm_year -= 70; + +#ifdef DS1306_DEBUG + dev_dbg(dev,"\n%s : Setting RTC values\n",__FUNCTION__); + dev_dbg(dev,"tm_sec : %i\n",dt->tm_sec); + dev_dbg(dev,"tm_min : %i\n",dt->tm_min); + dev_dbg(dev,"tm_hour: %i\n",dt->tm_hour); + dev_dbg(dev,"tm_mday: %i\n",dt->tm_mday); + dev_dbg(dev,"tm_wday: %i\n",dt->tm_wday); + dev_dbg(dev,"tm_year: %i\n",dt->tm_year); +#endif + + /* Remove write protection */ + ds1306_set_reg(dev, DS1306_REG_CTRL, chip->irq_mask); + + ds1306_set_reg(dev, DS1306_REG_SECS, BIN2BCD(dt->tm_sec)); + ds1306_set_reg(dev, DS1306_REG_MINS, BIN2BCD(dt->tm_min)); + ds1306_set_reg(dev, DS1306_REG_HOURS, BIN2BCD(dt->tm_hour)); + + ds1306_set_reg(dev, DS1306_REG_DATE, BIN2BCD(dt->tm_mday)); + ds1306_set_reg(dev, DS1306_REG_MONTHS,BIN2BCD(dt->tm_mon)); + ds1306_set_reg(dev, DS1306_REG_DAY, BIN2BCD(dt->tm_wday)); + ds1306_set_reg(dev, DS1306_REG_YEARS, BIN2BCD(dt->tm_year)); + + /* Write protect */ + ds1306_set_reg(dev, DS1306_REG_CTRL, DS1306_CTRL_W | chip->irq_mask); + + return 0; +} + +static int ds1306_read_time(struct device *dev, struct rtc_time *tm) +{ + int ret = 0; + down_interruptible(&dev->sem); + + ret=ds1306_get_datetime(dev, tm); + + up(&dev->sem); + + return ret; +} + +static int ds1306_set_time(struct device *dev, struct rtc_time *tm) +{ + int ret = 0; + down_interruptible(&dev->sem); + + ret=ds1306_set_datetime(dev, tm); + + up(&dev->sem); + + return ret; +} + +static int ds1306_read_alarm(struct device *dev, struct rtc_time *tm,u8 alarm) +{ + int ret = 0; + union ds1306_data_u data; + dev_dbg(dev,"%s\n", __FUNCTION__); + + if(likely(alarm==0)) + ret = ds1306_read_burst(dev,data.s.reg_data,DS1306_A0(DS1306_REG_SECS),4); + else + ret = ds1306_read_burst(dev,data.s.reg_data,DS1306_A1(DS1306_REG_SECS),4); + + tm->tm_sec = BCD2BIN(data.s.reg_data[0]); + tm->tm_min = BCD2BIN(data.s.reg_data[1]); + tm->tm_hour = BCD2BIN(data.s.reg_data[2]); + tm->tm_wday = BCD2BIN(data.s.reg_data[3]); + tm->tm_mday = 0; + tm->tm_mon = 0; + tm->tm_year = 0; + +#ifdef DS1306_DEBUG + dev_dbg(dev,"\n%s: RTC values:\n",__FUNCTION__); + dev_dbg(dev,"tm_sec : %i\n",tm->tm_sec); + dev_dbg(dev,"tm_min : %i\n",tm->tm_min); + dev_dbg(dev,"tm_hour: %i\n",tm->tm_hour); + dev_dbg(dev,"tm_mday: %i\n",tm->tm_mday); + dev_dbg(dev,"tm_wday: %i\n",tm->tm_wday); + dev_dbg(dev,"tm_year: %i\n",tm->tm_year); +#endif + return ret; +} + +static int ds1306_set_alarm(struct device *dev, struct rtc_time *tm, u8 alarm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1306_rtc *chip = platform_get_drvdata(pdev); + union ds1306_data_u data; + int ret; + + if(!tm) + return 0; + + data.s.reg_data[0] = BIN2BCD(tm->tm_sec); + data.s.reg_data[1] = BIN2BCD(tm->tm_min); + data.s.reg_data[2] = BIN2BCD(tm->tm_hour); + data.s.reg_data[3] = BIN2BCD(tm->tm_wday); + +#ifdef DS1306_DEBUG + dev_dbg(dev,"\n%s: RTC values:\n",__FUNCTION__); + dev_dbg(dev,"tm_sec : %i\n",tm->tm_sec); + dev_dbg(dev,"tm_min : %i\n",tm->tm_min); + dev_dbg(dev,"tm_hour: %i\n",tm->tm_hour); + dev_dbg(dev,"tm_mday: %i\n",tm->tm_mday); + dev_dbg(dev,"tm_wday: %i\n",tm->tm_wday); + dev_dbg(dev,"tm_year: %i\n",tm->tm_year); +#endif + + /* Remove write protection */ + ds1306_set_reg(dev, DS1306_REG_CTRL, chip->irq_mask); + + if(alarm==0) + ret = ds1306_write_burst(dev,data.s.reg_data,DS1306_A0(DS1306_REG_SECS),4); + else + ret = ds1306_write_burst(dev,data.s.reg_data,DS1306_A1(DS1306_REG_SECS),4); + + /* Write protect */ + ds1306_set_reg(dev, DS1306_REG_CTRL, DS1306_CTRL_W | chip->irq_mask); + + return ret; +} + + +static int ds1306_set_wkalarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1306_rtc *chip = platform_get_drvdata(pdev); + int ret = 0; + + dev_dbg(dev,"%s\n", __FUNCTION__); + + down_interruptible(&dev->sem); + + ret = ds1306_set_alarm(dev, &alarm->time,0); + + if(alarm->enabled & RTC_AF) + chip->irq_mask |= RTC_AF; + else + chip->irq_mask &= ~RTC_AF; + + dev_dbg(dev, "%s wkalarm: 0x%08x irq_mask: 0x%08x\n", + __FUNCTION__, alarm->enabled, chip->irq_mask); + + /* Remove write protection */ + ds1306_set_reg(dev, DS1306_REG_CTRL, chip->irq_mask); + + ds1306_set_reg(dev,DS1306_REG_CTRL, chip->irq_mask); + + /* Write protect */ + ds1306_set_reg(dev, DS1306_REG_CTRL, DS1306_CTRL_W | chip->irq_mask); + + up(&dev->sem); + + return 0; +} + +static int ds1306_read_wkalarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct ds1306_rtc *chip = dev_get_drvdata(dev); + + int ret = 0; + + dev_dbg(dev,"%s\n", __FUNCTION__); + + down_interruptible(&dev->sem); + + ret = ds1306_read_alarm(dev, &alarm->time,0); + + ret |= ds1306_get_reg(dev,DS1306_REG_CTRL,&chip->irq_mask); + ret |= ds1306_get_reg(dev,DS1306_REG_SR,&alarm->pending); + + up(&dev->sem); + + if(ret) + return ret; + + alarm->enabled &= RTC_AF; + alarm->pending &= RTC_AF; + return 0; +} + + +#if (defined(CONFIG_DS1306_RTC_IRQ) || defined(CONFIG_DS1306_RTC_HZ)) +static int ds1306_irq_set_state(struct device *dev, int mask) +{ + struct ds1306_rtc *chip = dev_get_drvdata(dev); + + dev_dbg(dev,"%s\n", __FUNCTION__); + dev_dbg(dev,"irq mask : 0x%08x\n", mask); + + chip->irq_mask = mask; + + /* Remove write protection then write + * again to be sure the the alarm flags + * has been written + */ + ds1306_set_reg(dev,DS1306_REG_CTRL,chip->irq_mask); + ds1306_set_reg(dev,DS1306_REG_CTRL,chip->irq_mask); + ds1306_set_reg(dev,DS1306_REG_CTRL,DS1306_CTRL_W | chip->irq_mask); + + return 0; +} +#endif + +static int ds1306_irq_set_freq(struct device *dev, int freq) +{ + return 0; +} + + +#if (defined(CONFIG_DS1306_RTC_IRQ) || defined(CONFIG_DS1306_RTC_HZ)) +static int ds1306_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1306_rtc *chip = platform_get_drvdata(pdev); + + dev_dbg(dev,"*** %s ***\n", __FUNCTION__); + + down_interruptible(&dev->sem); + + switch (cmd) { +/* + * Alarm support ? + */ +#ifdef CONFIG_DS1306_RTC_IRQ + case RTC_UIE_OFF: + dev_dbg(dev,"ioctl: RTC_UIE_OFF\n"); + ds1306_irq_set_state(dev,chip->irq_mask & ~DS1306_CTRL_AIE1); + break; + case RTC_UIE_ON: + dev_dbg(dev,"ioctl: RTC_UIE_ON\n"); + ds1306_irq_set_state(dev,chip->irq_mask | DS1306_CTRL_AIE1); + break; + case RTC_AIE_OFF: + dev_dbg(dev,"ioctl: RTC_AIE_OFF\n"); + ds1306_irq_set_state(dev,chip->irq_mask & ~DS1306_CTRL_AIE0); + break; + case RTC_AIE_ON: + dev_dbg(dev,"ioctl: RTC_AIE_ON\n"); + ds1306_irq_set_state(dev,chip->irq_mask | DS1306_CTRL_AIE0); + break; + case RTC_ALM_SET: + dev_dbg(dev,"ioctl: RTC_ALM_SET\n"); + ds1306_set_alarm(dev,(struct rtc_time *) arg,0); + break; + case RTC_ALM_READ: + dev_dbg(dev,"ioctl: RTC_ALM_READ\n"); + ds1306_read_alarm(dev,(struct rtc_time *) arg,0); + break; +#endif +/* + * HZ signal support ? + */ +#ifdef CONFIG_DS1306_RTC_HZ + case RTC_PIE_OFF: + dev_dbg(dev,"ioctl: RTC_PIE_OFF\n"); + ds1306_irq_set_state(dev,chip->irq_mask & ~DS1306_CTRL_HZ); + break; + case RTC_PIE_ON: + dev_dbg(dev,"ioctl: RTC_PIE_ON\n"); + ds1306_irq_set_state(dev,chip->irq_mask | DS1306_CTRL_HZ); + break; +#endif + default: + dev_dbg(dev,"ioctl: cmd: 0x%08x arg: %p \n",cmd,(void*)arg); + up(&dev->sem); + return -ENOIOCTLCMD; + } + + up(&dev->sem); + + return 0; +} +#endif + +static const struct rtc_class_ops ds1306_rtc_ops = { + .read_time = ds1306_read_time, + .set_time = ds1306_set_time, + .read_alarm = ds1306_read_wkalarm, + .set_alarm = ds1306_set_wkalarm, +#if (defined(CONFIG_DS1306_RTC_IRQ) || defined(CONFIG_DS1306_RTC_HZ)) + .irq_set_state = ds1306_irq_set_state, +#endif + .irq_set_freq = ds1306_irq_set_freq, +#if (defined(CONFIG_DS1306_RTC_IRQ) || defined(CONFIG_DS1306_RTC_HZ)) + .ioctl = ds1306_ioctl, +#endif +}; + + +struct ds1306_wq_alarm_t { + unsigned int alarm; + struct device *pdev; +}; + +struct ds1306_wq_alarm_t ds1306_wq_alarm_data; + +void ds1306_read_alarm_wq(void) +{ + down_interruptible(&ds1306_wq_alarm_data.pdev->sem); + + if(ds1306_wq_alarm_data.alarm==0) + ds1306_read_burst(ds1306_wq_alarm_data.pdev,NULL,DS1306_A0(DS1306_REG_SECS),4); + else + ds1306_read_burst(ds1306_wq_alarm_data.pdev,NULL,DS1306_A1(DS1306_REG_SECS),4); + up(&ds1306_wq_alarm_data.pdev->sem); +} + + +DECLARE_WORK(ds1306_alarm_work, ds1306_read_alarm_wq); + +static irqreturn_t ds1306_rtc_interrupt(int irq, void *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct ds1306_rtc *chip = platform_get_drvdata(pdev); + + unsigned int ret = 0; + unsigned char rtc_sr = 0; + unsigned long events = 0; + + spin_lock(&chip->lock); + + ret=ds1306_get_reg(dev,DS1306_REG_SR,&rtc_sr); + + if(rtc_sr & DS1306_SR_IRQF0) { + ds1306_wq_alarm_data.alarm=0; + ds1306_wq_alarm_data.pdev=dev; + schedule_work(&ds1306_alarm_work); + events |= RTC_IRQF | RTC_AF; + } + + if(rtc_sr & DS1306_SR_IRQF1) { + ds1306_wq_alarm_data.alarm=1; + ds1306_wq_alarm_data.pdev=dev; + schedule_work(&ds1306_alarm_work); + events |= RTC_IRQF | RTC_AF; + } + + rtc_update_irq(chip->rtc, 1, events); + + spin_unlock(&chip->lock); + + return IRQ_HANDLED; +} + + +static int __devinit ds1306_probe(struct spi_device *spi) +{ + struct ds1306_rtc *chip; + struct rtc_device *rtc; + u8 tmp; + int res; + + rtc = rtc_device_register("rtc-ds1306", + &spi->dev, &ds1306_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + spi->mode = SPI_CS_HIGH | SPI_CPOL | SPI_CPHA; + spi->bits_per_word = 8; + spi_setup(spi); + + chip = kzalloc(sizeof *chip, GFP_KERNEL); + if (!chip) { + rtc_device_unregister(rtc); + return -ENOMEM; + } + chip->rtc = rtc; + spin_lock_init(&chip->lock); + dev_set_drvdata(&spi->dev, chip); + + res = ds1306_get_reg(&spi->dev, DS1306_REG_SECS, &tmp); + if (res) { + rtc_device_unregister(rtc); + return res; + } + +// request_irq(30,ds1306_rtc_interrupt,IRQF_SHARED,"rtc-ds1306",&spi->dev); + + return 0; +} + +static int __devexit ds1306_remove(struct spi_device *spi) +{ + struct ds1306_rtc *chip = platform_get_drvdata(spi); + if(chip->rtc) + rtc_device_unregister(chip->rtc); + + kfree(chip); + + return 0; +} + +static struct spi_driver ds1306_driver = { + .driver = { + .name = "rtc-ds1306", + .owner = THIS_MODULE, + }, + .probe = ds1306_probe, + .remove = __devexit_p(ds1306_remove), +}; + +static int __init ds1306_init(void) +{ + printk(KERN_INFO "DS1306 SPI driver\n"); + return spi_register_driver(&ds1306_driver); +} +module_init(ds1306_init); + +static void __exit ds1306_exit(void) +{ + spi_unregister_driver(&ds1306_driver); +} +module_exit(ds1306_exit); + +MODULE_AUTHOR("Antonio R. Costa "); +MODULE_DESCRIPTION("RTC driver for DS1306 on SPI bus"); +MODULE_LICENSE("GPL");