Message ID | 1257432694-12230-1-git-send-email-miguel.aguilar@ridgerun.com |
---|---|
State | Accepted |
Headers | show |
miguel.aguilar@ridgerun.com writes: > From: Miguel Aguilar <miguel.aguilar@ridgerun.com> > > This driver features: > > * Alarm support. > * Periodic interrupt by using a timer include into the RTC module. > * The update interrupt is not supported by this RTC module. > > This driver was tested on a DM365 EVM by using the rtc-test application > from the Documentation/rtc.txt. > > Signed-off-by: Miguel Aguilar <miguel.aguilar@ridgerun.com> Signed-off-by: Kevin Hilman <khilman@deeprootsystems.com> > --- > drivers/rtc/Kconfig | 10 + > drivers/rtc/Makefile | 1 + > drivers/rtc/rtc-davinci.c | 673 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 684 insertions(+), 0 deletions(-) > create mode 100644 drivers/rtc/rtc-davinci.c > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig > index 3c20dae..61bd4d6 100644 > --- a/drivers/rtc/Kconfig > +++ b/drivers/rtc/Kconfig > @@ -576,6 +576,16 @@ config RTC_DRV_AB3100 > > comment "on-CPU RTC drivers" > > +config RTC_DRV_DAVINCI > + tristate "TI DaVinci RTC" > + depends on ARCH_DAVINCI_DM365 > + help > + If you say yes here you get support for the RTC on the > + DaVinci platforms (DM365). > + > + This driver can also be built as a module. If so, the module > + will be called rtc-davinci. > + > config RTC_DRV_OMAP > tristate "TI OMAP1" > depends on ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile > index aa3fbd5..d968694 100644 > --- a/drivers/rtc/Makefile > +++ b/drivers/rtc/Makefile > @@ -26,6 +26,7 @@ obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o > obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o > obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o > obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o > +obj-$(CONFIG_RTC_DRV_DAVINCI) += rtc-davinci.o > obj-$(CONFIG_RTC_DRV_DM355EVM) += rtc-dm355evm.o > obj-$(CONFIG_RTC_DRV_DS1216) += rtc-ds1216.o > obj-$(CONFIG_RTC_DRV_DS1286) += rtc-ds1286.o > diff --git a/drivers/rtc/rtc-davinci.c b/drivers/rtc/rtc-davinci.c > new file mode 100644 > index 0000000..09c5f6c > --- /dev/null > +++ b/drivers/rtc/rtc-davinci.c > @@ -0,0 +1,673 @@ > +/* > + * DaVinci Power Management and Real Time Clock Driver for TI platforms > + * > + * Copyright (C) 2009 Texas Instruments, Inc > + * > + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.com> > + * > + * This program is free software; you can redistribute it and/or modify > + * it under the terms of the GNU General Public License as published by > + * the Free Software Foundation; either version 2 of the License, or > + * (at your option) any later version. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > + * GNU General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA > + */ > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/module.h> > +#include <linux/ioport.h> > +#include <linux/delay.h> > +#include <linux/spinlock.h> > +#include <linux/rtc.h> > +#include <linux/bcd.h> > +#include <linux/platform_device.h> > +#include <linux/io.h> > + > +/* > + * The DaVinci RTC is a simple RTC with the following > + * Sec: 0 - 59 : BCD count > + * Min: 0 - 59 : BCD count > + * Hour: 0 - 23 : BCD count > + * Day: 0 - 0x7FFF(32767) : Binary count ( Over 89 years ) > + */ > + > +/* PRTC interface registers */ > +#define DAVINCI_PRTCIF_PID 0x00 > +#define PRTCIF_CTLR 0x04 > +#define PRTCIF_LDATA 0x08 > +#define PRTCIF_UDATA 0x0C > +#define PRTCIF_INTEN 0x10 > +#define PRTCIF_INTFLG 0x14 > + > +/* PRTCIF_CTLR bit fields */ > +#define PRTCIF_CTLR_BUSY BIT(31) > +#define PRTCIF_CTLR_SIZE BIT(25) > +#define PRTCIF_CTLR_DIR BIT(24) > +#define PRTCIF_CTLR_BENU_MSB BIT(23) > +#define PRTCIF_CTLR_BENU_3RD_BYTE BIT(22) > +#define PRTCIF_CTLR_BENU_2ND_BYTE BIT(21) > +#define PRTCIF_CTLR_BENU_LSB BIT(20) > +#define PRTCIF_CTLR_BENU_MASK (0x00F00000) > +#define PRTCIF_CTLR_BENL_MSB BIT(19) > +#define PRTCIF_CTLR_BENL_3RD_BYTE BIT(18) > +#define PRTCIF_CTLR_BENL_2ND_BYTE BIT(17) > +#define PRTCIF_CTLR_BENL_LSB BIT(16) > +#define PRTCIF_CTLR_BENL_MASK (0x000F0000) > + > +/* PRTCIF_INTEN bit fields */ > +#define PRTCIF_INTEN_RTCSS BIT(1) > +#define PRTCIF_INTEN_RTCIF BIT(0) > +#define PRTCIF_INTEN_MASK (PRTCIF_INTEN_RTCSS \ > + | PRTCIF_INTEN_RTCIF) > + > +/* PRTCIF_INTFLG bit fields */ > +#define PRTCIF_INTFLG_RTCSS BIT(1) > +#define PRTCIF_INTFLG_RTCIF BIT(0) > +#define PRTCIF_INTFLG_MASK (PRTCIF_INTFLG_RTCSS \ > + | PRTCIF_INTFLG_RTCIF) > + > +/* PRTC subsystem registers */ > +#define PRTCSS_RTC_INTC_EXTENA1 (0x0C) > +#define PRTCSS_RTC_CTRL (0x10) > +#define PRTCSS_RTC_WDT (0x11) > +#define PRTCSS_RTC_TMR0 (0x12) > +#define PRTCSS_RTC_TMR1 (0x13) > +#define PRTCSS_RTC_CCTRL (0x14) > +#define PRTCSS_RTC_SEC (0x15) > +#define PRTCSS_RTC_MIN (0x16) > +#define PRTCSS_RTC_HOUR (0x17) > +#define PRTCSS_RTC_DAY0 (0x18) > +#define PRTCSS_RTC_DAY1 (0x19) > +#define PRTCSS_RTC_AMIN (0x1A) > +#define PRTCSS_RTC_AHOUR (0x1B) > +#define PRTCSS_RTC_ADAY0 (0x1C) > +#define PRTCSS_RTC_ADAY1 (0x1D) > +#define PRTCSS_RTC_CLKC_CNT (0x20) > + > +/* PRTCSS_RTC_INTC_EXTENA1 */ > +#define PRTCSS_RTC_INTC_EXTENA1_MASK (0x07) > + > +/* PRTCSS_RTC_CTRL bit fields */ > +#define PRTCSS_RTC_CTRL_WDTBUS BIT(7) > +#define PRTCSS_RTC_CTRL_WEN BIT(6) > +#define PRTCSS_RTC_CTRL_WDRT BIT(5) > +#define PRTCSS_RTC_CTRL_WDTFLG BIT(4) > +#define PRTCSS_RTC_CTRL_TE BIT(3) > +#define PRTCSS_RTC_CTRL_TIEN BIT(2) > +#define PRTCSS_RTC_CTRL_TMRFLG BIT(1) > +#define PRTCSS_RTC_CTRL_TMMD BIT(0) > + > +/* PRTCSS_RTC_CCTRL bit fields */ > +#define PRTCSS_RTC_CCTRL_CALBUSY BIT(7) > +#define PRTCSS_RTC_CCTRL_DAEN BIT(5) > +#define PRTCSS_RTC_CCTRL_HAEN BIT(4) > +#define PRTCSS_RTC_CCTRL_MAEN BIT(3) > +#define PRTCSS_RTC_CCTRL_ALMFLG BIT(2) > +#define PRTCSS_RTC_CCTRL_AIEN BIT(1) > +#define PRTCSS_RTC_CCTRL_CAEN BIT(0) > + > +static DEFINE_SPINLOCK(davinci_rtc_lock); > + > +struct davinci_rtc { > + struct rtc_device *rtc; > + void __iomem *base; > + resource_size_t pbase; > + size_t base_size; > + int irq; > +}; > + > +static inline void rtcif_write(struct davinci_rtc *davinci_rtc, > + u32 val, u32 addr) > +{ > + writel(val, davinci_rtc->base + addr); > +} > + > +static inline u32 rtcif_read(struct davinci_rtc *davinci_rtc, u32 addr) > +{ > + return readl(davinci_rtc->base + addr); > +} > + > +static inline void rtcif_wait(struct davinci_rtc *davinci_rtc) > +{ > + while (rtcif_read(davinci_rtc, PRTCIF_CTLR) & PRTCIF_CTLR_BUSY) > + cpu_relax(); > +} > + > +static inline void rtcss_write(struct davinci_rtc *davinci_rtc, > + unsigned long val, u8 addr) > +{ > + rtcif_wait(davinci_rtc); > + > + rtcif_write(davinci_rtc, PRTCIF_CTLR_BENL_LSB | addr, PRTCIF_CTLR); > + rtcif_write(davinci_rtc, val, PRTCIF_LDATA); > + > + rtcif_wait(davinci_rtc); > +} > + > +static inline u8 rtcss_read(struct davinci_rtc *davinci_rtc, u8 addr) > +{ > + rtcif_wait(davinci_rtc); > + > + rtcif_write(davinci_rtc, PRTCIF_CTLR_DIR | PRTCIF_CTLR_BENL_LSB | addr, > + PRTCIF_CTLR); > + > + rtcif_wait(davinci_rtc); > + > + return rtcif_read(davinci_rtc, PRTCIF_LDATA); > +} > + > +static inline void davinci_rtcss_calendar_wait(struct davinci_rtc *davinci_rtc) > +{ > + while (rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL) & > + PRTCSS_RTC_CCTRL_CALBUSY) > + cpu_relax(); > +} > + > +static irqreturn_t davinci_rtc_interrupt(int irq, void *class_dev) > +{ > + struct davinci_rtc *davinci_rtc = class_dev; > + unsigned long events = 0; > + u32 irq_flg; > + u8 alm_irq, tmr_irq; > + u8 rtc_ctrl, rtc_cctrl; > + int ret = IRQ_NONE; > + > + irq_flg = rtcif_read(davinci_rtc, PRTCIF_INTFLG) & > + PRTCIF_INTFLG_RTCSS; > + > + alm_irq = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL) & > + PRTCSS_RTC_CCTRL_ALMFLG; > + > + tmr_irq = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL) & > + PRTCSS_RTC_CTRL_TMRFLG; > + > + if (irq_flg) { > + if (alm_irq) { > + events |= RTC_IRQF | RTC_AF; > + rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); > + rtc_cctrl |= PRTCSS_RTC_CCTRL_ALMFLG; > + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); > + } else if (tmr_irq) { > + events |= RTC_IRQF | RTC_PF; > + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); > + rtc_ctrl |= PRTCSS_RTC_CTRL_TMRFLG; > + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); > + } > + > + rtcif_write(davinci_rtc, PRTCIF_INTFLG_RTCSS, > + PRTCIF_INTFLG); > + rtc_update_irq(davinci_rtc->rtc, 1, events); > + > + ret = IRQ_HANDLED; > + } > + > + return ret; > +} > + > +static int > +davinci_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + u8 rtc_ctrl; > + unsigned long flags; > + int ret = 0; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); > + > + switch (cmd) { > + case RTC_WIE_ON: > + rtc_ctrl |= PRTCSS_RTC_CTRL_WEN | PRTCSS_RTC_CTRL_WDTFLG; > + break; > + case RTC_WIE_OFF: > + rtc_ctrl &= ~PRTCSS_RTC_CTRL_WEN; > + break; > + case RTC_UIE_OFF: > + case RTC_UIE_ON: > + ret = -ENOTTY; > + break; > + default: > + ret = -ENOIOCTLCMD; > + } > + > + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return ret; > +} > + > +static int convertfromdays(u16 days, struct rtc_time *tm) > +{ > + int tmp_days, year, mon; > + > + for (year = 2000;; year++) { > + tmp_days = rtc_year_days(1, 12, year); > + if (days >= tmp_days) > + days -= tmp_days; > + else { > + for (mon = 0;; mon++) { > + tmp_days = rtc_month_days(mon, year); > + if (days >= tmp_days) { > + days -= tmp_days; > + } else { > + tm->tm_year = year - 1900; > + tm->tm_mon = mon; > + tm->tm_mday = days + 1; > + break; > + } > + } > + break; > + } > + } > + return 0; > +} > + > +static int convert2days(u16 *days, struct rtc_time *tm) > +{ > + int i; > + *days = 0; > + > + /* epoch == 1900 */ > + if (tm->tm_year < 100 || tm->tm_year > 199) > + return -EINVAL; > + > + for (i = 2000; i < 1900 + tm->tm_year; i++) > + *days += rtc_year_days(1, 12, i); > + > + *days += rtc_year_days(tm->tm_mday, tm->tm_mon, 1900 + tm->tm_year); > + > + return 0; > +} > + > +static int davinci_rtc_read_time(struct device *dev, struct rtc_time *tm) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + u16 days = 0; > + u8 day0, day1; > + unsigned long flags; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + tm->tm_sec = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_SEC)); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + tm->tm_min = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_MIN)); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + tm->tm_hour = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_HOUR)); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + day0 = rtcss_read(davinci_rtc, PRTCSS_RTC_DAY0); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + day1 = rtcss_read(davinci_rtc, PRTCSS_RTC_DAY1); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + days |= day1; > + days <<= 8; > + days |= day0; > + > + if (convertfromdays(days, tm) < 0) > + return -EINVAL; > + > + return 0; > +} > + > +static int davinci_rtc_set_time(struct device *dev, struct rtc_time *tm) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + u16 days; > + u8 rtc_cctrl; > + unsigned long flags; > + > + if (convert2days(&days, tm) < 0) > + return -EINVAL; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, bin2bcd(tm->tm_sec), PRTCSS_RTC_SEC); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, bin2bcd(tm->tm_min), PRTCSS_RTC_MIN); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, bin2bcd(tm->tm_hour), PRTCSS_RTC_HOUR); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, days & 0xFF, PRTCSS_RTC_DAY0); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, (days & 0xFF00) >> 8, PRTCSS_RTC_DAY1); > + > + rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); > + rtc_cctrl |= PRTCSS_RTC_CCTRL_CAEN; > + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return 0; > +} > + > +static int davinci_rtc_alarm_irq_enable(struct device *dev, > + unsigned int enabled) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + unsigned long flags; > + u8 rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + if (enabled) > + rtc_cctrl |= PRTCSS_RTC_CCTRL_DAEN | > + PRTCSS_RTC_CCTRL_HAEN | > + PRTCSS_RTC_CCTRL_MAEN | > + PRTCSS_RTC_CCTRL_ALMFLG | > + PRTCSS_RTC_CCTRL_AIEN; > + else > + rtc_cctrl &= ~PRTCSS_RTC_CCTRL_AIEN; > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return 0; > +} > + > +static int davinci_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + u16 days = 0; > + u8 day0, day1; > + unsigned long flags; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + alm->time.tm_min = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_AMIN)); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + alm->time.tm_hour = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_AHOUR)); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + day0 = rtcss_read(davinci_rtc, PRTCSS_RTC_ADAY0); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + day1 = rtcss_read(davinci_rtc, PRTCSS_RTC_ADAY1); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + days |= day1; > + days <<= 8; > + days |= day0; > + > + if (convertfromdays(days, &alm->time) < 0) > + return -EINVAL; > + > + alm->pending = !!(rtcss_read(davinci_rtc, > + PRTCSS_RTC_CCTRL) & > + PRTCSS_RTC_CCTRL_AIEN); > + alm->enabled = alm->pending && device_may_wakeup(dev); > + > + return 0; > +} > + > +static int davinci_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + unsigned long flags; > + u16 days; > + > + if (alm->time.tm_mday <= 0 && alm->time.tm_mon < 0 > + && alm->time.tm_year < 0) { > + struct rtc_time tm; > + unsigned long now, then; > + > + davinci_rtc_read_time(dev, &tm); > + rtc_tm_to_time(&tm, &now); > + > + alm->time.tm_mday = tm.tm_mday; > + alm->time.tm_mon = tm.tm_mon; > + alm->time.tm_year = tm.tm_year; > + rtc_tm_to_time(&alm->time, &then); > + > + if (then < now) { > + rtc_time_to_tm(now + 24 * 60 * 60, &tm); > + alm->time.tm_mday = tm.tm_mday; > + alm->time.tm_mon = tm.tm_mon; > + alm->time.tm_year = tm.tm_year; > + } > + } > + > + if (convert2days(&days, &alm->time) < 0) > + return -EINVAL; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, bin2bcd(alm->time.tm_min), PRTCSS_RTC_AMIN); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, bin2bcd(alm->time.tm_hour), PRTCSS_RTC_AHOUR); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, days & 0xFF, PRTCSS_RTC_ADAY0); > + > + davinci_rtcss_calendar_wait(davinci_rtc); > + rtcss_write(davinci_rtc, (days & 0xFF00) >> 8, PRTCSS_RTC_ADAY1); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return 0; > +} > + > +static int davinci_rtc_irq_set_state(struct device *dev, int enabled) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + unsigned long flags; > + u8 rtc_ctrl; > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); > + > + if (enabled) { > + while (rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL) > + & PRTCSS_RTC_CTRL_WDTBUS) > + cpu_relax(); > + > + rtc_ctrl |= PRTCSS_RTC_CTRL_TE; > + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); > + > + rtcss_write(davinci_rtc, 0x0, PRTCSS_RTC_CLKC_CNT); > + > + rtc_ctrl |= PRTCSS_RTC_CTRL_TIEN | > + PRTCSS_RTC_CTRL_TMMD | > + PRTCSS_RTC_CTRL_TMRFLG; > + } else > + rtc_ctrl &= ~PRTCSS_RTC_CTRL_TIEN; > + > + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return 0; > +} > + > +static int davinci_rtc_irq_set_freq(struct device *dev, int freq) > +{ > + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); > + unsigned long flags; > + u16 tmr_counter = (0x8000 >> (ffs(freq) - 1)); > + > + spin_lock_irqsave(&davinci_rtc_lock, flags); > + > + rtcss_write(davinci_rtc, tmr_counter & 0xFF, PRTCSS_RTC_TMR0); > + rtcss_write(davinci_rtc, (tmr_counter & 0xFF00) >> 8, PRTCSS_RTC_TMR1); > + > + spin_unlock_irqrestore(&davinci_rtc_lock, flags); > + > + return 0; > +} > + > +static struct rtc_class_ops davinci_rtc_ops = { > + .ioctl = davinci_rtc_ioctl, > + .read_time = davinci_rtc_read_time, > + .set_time = davinci_rtc_set_time, > + .alarm_irq_enable = davinci_rtc_alarm_irq_enable, > + .read_alarm = davinci_rtc_read_alarm, > + .set_alarm = davinci_rtc_set_alarm, > + .irq_set_state = davinci_rtc_irq_set_state, > + .irq_set_freq = davinci_rtc_irq_set_freq, > +}; > + > +static int __init davinci_rtc_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct davinci_rtc *davinci_rtc; > + struct resource *res, *mem; > + int ret = 0; > + > + davinci_rtc = kzalloc(sizeof(struct davinci_rtc), GFP_KERNEL); > + if (!davinci_rtc) { > + dev_dbg(dev, "could not allocate memory for private data\n"); > + return -ENOMEM; > + } > + > + davinci_rtc->irq = platform_get_irq(pdev, 0); > + if (davinci_rtc->irq < 0) { > + dev_err(dev, "no RTC irq\n"); > + ret = davinci_rtc->irq; > + goto fail1; > + } > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) { > + dev_err(dev, "no mem resource\n"); > + ret = -EINVAL; > + goto fail1; > + } > + > + davinci_rtc->pbase = res->start; > + davinci_rtc->base_size = resource_size(res); > + > + mem = request_mem_region(davinci_rtc->pbase, davinci_rtc->base_size, > + pdev->name); > + if (!mem) { > + dev_err(dev, "RTC registers at %08x are not free\n", > + davinci_rtc->pbase); > + ret = -EBUSY; > + goto fail1; > + } > + > + davinci_rtc->base = ioremap(davinci_rtc->pbase, davinci_rtc->base_size); > + if (!davinci_rtc->base) { > + dev_err(dev, "unable to ioremap MEM resource\n"); > + ret = -ENOMEM; > + goto fail2; > + } > + > + davinci_rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, > + &davinci_rtc_ops, THIS_MODULE); > + if (IS_ERR(davinci_rtc->rtc)) { > + dev_err(dev, "unable to register RTC device, err %ld\n", > + PTR_ERR(davinci_rtc->rtc)); > + goto fail3; > + } > + > + rtcif_write(davinci_rtc, PRTCIF_INTFLG_RTCSS, PRTCIF_INTFLG); > + rtcif_write(davinci_rtc, 0, PRTCIF_INTEN); > + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_INTC_EXTENA1); > + > + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_CTRL); > + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_CCTRL); > + > + ret = request_irq(davinci_rtc->irq, davinci_rtc_interrupt, > + IRQF_DISABLED, "davinci_rtc", davinci_rtc); > + if (ret < 0) { > + dev_err(dev, "unable to register davinci RTC interrupt\n"); > + goto fail4; > + } > + > + /* Enable interrupts */ > + rtcif_write(davinci_rtc, PRTCIF_INTEN_RTCSS, PRTCIF_INTEN); > + rtcss_write(davinci_rtc, PRTCSS_RTC_INTC_EXTENA1_MASK, > + PRTCSS_RTC_INTC_EXTENA1); > + > + rtcss_write(davinci_rtc, PRTCSS_RTC_CCTRL_CAEN, PRTCSS_RTC_CCTRL); > + > + platform_set_drvdata(pdev, davinci_rtc); > + > + device_init_wakeup(&pdev->dev, 0); > + > + return 0; > + > +fail4: > + rtc_device_unregister(davinci_rtc->rtc); > +fail3: > + iounmap(davinci_rtc->base); > +fail2: > + release_mem_region(davinci_rtc->pbase, davinci_rtc->base_size); > +fail1: > + kfree(davinci_rtc); > + > + return ret; > +} > + > +static int __devexit davinci_rtc_remove(struct platform_device *pdev) > +{ > + struct davinci_rtc *davinci_rtc = platform_get_drvdata(pdev); > + > + device_init_wakeup(&pdev->dev, 0); > + > + rtcif_write(davinci_rtc, 0, PRTCIF_INTEN); > + > + free_irq(davinci_rtc->irq, davinci_rtc); > + > + rtc_device_unregister(davinci_rtc->rtc); > + > + iounmap(davinci_rtc->base); > + release_mem_region(davinci_rtc->pbase, davinci_rtc->base_size); > + > + platform_set_drvdata(pdev, NULL); > + > + kfree(davinci_rtc); > + > + return 0; > +} > + > +static struct platform_driver davinci_rtc_driver = { > + .probe = davinci_rtc_probe, > + .remove = __devexit_p(davinci_rtc_remove), > + .driver = { > + .name = "rtc_davinci", > + .owner = THIS_MODULE, > + }, > +}; > + > +static int __init rtc_init(void) > +{ > + return platform_driver_probe(&davinci_rtc_driver, davinci_rtc_probe); > +} > +module_init(rtc_init); > + > +static void __exit rtc_exit(void) > +{ > + platform_driver_unregister(&davinci_rtc_driver); > +} > +module_exit(rtc_exit); > + > +MODULE_AUTHOR("Miguel Aguilar"); > +MODULE_DESCRIPTION("Texas Instruments DaVinci PRTC Driver"); > +MODULE_LICENSE("GPL"); > -- > 1.6.0.4 --~--~---------~--~----~------------~-------~--~----~ 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 3c20dae..61bd4d6 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -576,6 +576,16 @@ config RTC_DRV_AB3100 comment "on-CPU RTC drivers" +config RTC_DRV_DAVINCI + tristate "TI DaVinci RTC" + depends on ARCH_DAVINCI_DM365 + help + If you say yes here you get support for the RTC on the + DaVinci platforms (DM365). + + This driver can also be built as a module. If so, the module + will be called rtc-davinci. + config RTC_DRV_OMAP tristate "TI OMAP1" depends on ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index aa3fbd5..d968694 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_RTC_DRV_BFIN) += rtc-bfin.o obj-$(CONFIG_RTC_DRV_BQ4802) += rtc-bq4802.o obj-$(CONFIG_RTC_DRV_CMOS) += rtc-cmos.o obj-$(CONFIG_RTC_DRV_COH901331) += rtc-coh901331.o +obj-$(CONFIG_RTC_DRV_DAVINCI) += rtc-davinci.o obj-$(CONFIG_RTC_DRV_DM355EVM) += rtc-dm355evm.o obj-$(CONFIG_RTC_DRV_DS1216) += rtc-ds1216.o obj-$(CONFIG_RTC_DRV_DS1286) += rtc-ds1286.o diff --git a/drivers/rtc/rtc-davinci.c b/drivers/rtc/rtc-davinci.c new file mode 100644 index 0000000..09c5f6c --- /dev/null +++ b/drivers/rtc/rtc-davinci.c @@ -0,0 +1,673 @@ +/* + * DaVinci Power Management and Real Time Clock Driver for TI platforms + * + * Copyright (C) 2009 Texas Instruments, Inc + * + * Author: Miguel Aguilar <miguel.aguilar@ridgerun.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/spinlock.h> +#include <linux/rtc.h> +#include <linux/bcd.h> +#include <linux/platform_device.h> +#include <linux/io.h> + +/* + * The DaVinci RTC is a simple RTC with the following + * Sec: 0 - 59 : BCD count + * Min: 0 - 59 : BCD count + * Hour: 0 - 23 : BCD count + * Day: 0 - 0x7FFF(32767) : Binary count ( Over 89 years ) + */ + +/* PRTC interface registers */ +#define DAVINCI_PRTCIF_PID 0x00 +#define PRTCIF_CTLR 0x04 +#define PRTCIF_LDATA 0x08 +#define PRTCIF_UDATA 0x0C +#define PRTCIF_INTEN 0x10 +#define PRTCIF_INTFLG 0x14 + +/* PRTCIF_CTLR bit fields */ +#define PRTCIF_CTLR_BUSY BIT(31) +#define PRTCIF_CTLR_SIZE BIT(25) +#define PRTCIF_CTLR_DIR BIT(24) +#define PRTCIF_CTLR_BENU_MSB BIT(23) +#define PRTCIF_CTLR_BENU_3RD_BYTE BIT(22) +#define PRTCIF_CTLR_BENU_2ND_BYTE BIT(21) +#define PRTCIF_CTLR_BENU_LSB BIT(20) +#define PRTCIF_CTLR_BENU_MASK (0x00F00000) +#define PRTCIF_CTLR_BENL_MSB BIT(19) +#define PRTCIF_CTLR_BENL_3RD_BYTE BIT(18) +#define PRTCIF_CTLR_BENL_2ND_BYTE BIT(17) +#define PRTCIF_CTLR_BENL_LSB BIT(16) +#define PRTCIF_CTLR_BENL_MASK (0x000F0000) + +/* PRTCIF_INTEN bit fields */ +#define PRTCIF_INTEN_RTCSS BIT(1) +#define PRTCIF_INTEN_RTCIF BIT(0) +#define PRTCIF_INTEN_MASK (PRTCIF_INTEN_RTCSS \ + | PRTCIF_INTEN_RTCIF) + +/* PRTCIF_INTFLG bit fields */ +#define PRTCIF_INTFLG_RTCSS BIT(1) +#define PRTCIF_INTFLG_RTCIF BIT(0) +#define PRTCIF_INTFLG_MASK (PRTCIF_INTFLG_RTCSS \ + | PRTCIF_INTFLG_RTCIF) + +/* PRTC subsystem registers */ +#define PRTCSS_RTC_INTC_EXTENA1 (0x0C) +#define PRTCSS_RTC_CTRL (0x10) +#define PRTCSS_RTC_WDT (0x11) +#define PRTCSS_RTC_TMR0 (0x12) +#define PRTCSS_RTC_TMR1 (0x13) +#define PRTCSS_RTC_CCTRL (0x14) +#define PRTCSS_RTC_SEC (0x15) +#define PRTCSS_RTC_MIN (0x16) +#define PRTCSS_RTC_HOUR (0x17) +#define PRTCSS_RTC_DAY0 (0x18) +#define PRTCSS_RTC_DAY1 (0x19) +#define PRTCSS_RTC_AMIN (0x1A) +#define PRTCSS_RTC_AHOUR (0x1B) +#define PRTCSS_RTC_ADAY0 (0x1C) +#define PRTCSS_RTC_ADAY1 (0x1D) +#define PRTCSS_RTC_CLKC_CNT (0x20) + +/* PRTCSS_RTC_INTC_EXTENA1 */ +#define PRTCSS_RTC_INTC_EXTENA1_MASK (0x07) + +/* PRTCSS_RTC_CTRL bit fields */ +#define PRTCSS_RTC_CTRL_WDTBUS BIT(7) +#define PRTCSS_RTC_CTRL_WEN BIT(6) +#define PRTCSS_RTC_CTRL_WDRT BIT(5) +#define PRTCSS_RTC_CTRL_WDTFLG BIT(4) +#define PRTCSS_RTC_CTRL_TE BIT(3) +#define PRTCSS_RTC_CTRL_TIEN BIT(2) +#define PRTCSS_RTC_CTRL_TMRFLG BIT(1) +#define PRTCSS_RTC_CTRL_TMMD BIT(0) + +/* PRTCSS_RTC_CCTRL bit fields */ +#define PRTCSS_RTC_CCTRL_CALBUSY BIT(7) +#define PRTCSS_RTC_CCTRL_DAEN BIT(5) +#define PRTCSS_RTC_CCTRL_HAEN BIT(4) +#define PRTCSS_RTC_CCTRL_MAEN BIT(3) +#define PRTCSS_RTC_CCTRL_ALMFLG BIT(2) +#define PRTCSS_RTC_CCTRL_AIEN BIT(1) +#define PRTCSS_RTC_CCTRL_CAEN BIT(0) + +static DEFINE_SPINLOCK(davinci_rtc_lock); + +struct davinci_rtc { + struct rtc_device *rtc; + void __iomem *base; + resource_size_t pbase; + size_t base_size; + int irq; +}; + +static inline void rtcif_write(struct davinci_rtc *davinci_rtc, + u32 val, u32 addr) +{ + writel(val, davinci_rtc->base + addr); +} + +static inline u32 rtcif_read(struct davinci_rtc *davinci_rtc, u32 addr) +{ + return readl(davinci_rtc->base + addr); +} + +static inline void rtcif_wait(struct davinci_rtc *davinci_rtc) +{ + while (rtcif_read(davinci_rtc, PRTCIF_CTLR) & PRTCIF_CTLR_BUSY) + cpu_relax(); +} + +static inline void rtcss_write(struct davinci_rtc *davinci_rtc, + unsigned long val, u8 addr) +{ + rtcif_wait(davinci_rtc); + + rtcif_write(davinci_rtc, PRTCIF_CTLR_BENL_LSB | addr, PRTCIF_CTLR); + rtcif_write(davinci_rtc, val, PRTCIF_LDATA); + + rtcif_wait(davinci_rtc); +} + +static inline u8 rtcss_read(struct davinci_rtc *davinci_rtc, u8 addr) +{ + rtcif_wait(davinci_rtc); + + rtcif_write(davinci_rtc, PRTCIF_CTLR_DIR | PRTCIF_CTLR_BENL_LSB | addr, + PRTCIF_CTLR); + + rtcif_wait(davinci_rtc); + + return rtcif_read(davinci_rtc, PRTCIF_LDATA); +} + +static inline void davinci_rtcss_calendar_wait(struct davinci_rtc *davinci_rtc) +{ + while (rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL) & + PRTCSS_RTC_CCTRL_CALBUSY) + cpu_relax(); +} + +static irqreturn_t davinci_rtc_interrupt(int irq, void *class_dev) +{ + struct davinci_rtc *davinci_rtc = class_dev; + unsigned long events = 0; + u32 irq_flg; + u8 alm_irq, tmr_irq; + u8 rtc_ctrl, rtc_cctrl; + int ret = IRQ_NONE; + + irq_flg = rtcif_read(davinci_rtc, PRTCIF_INTFLG) & + PRTCIF_INTFLG_RTCSS; + + alm_irq = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL) & + PRTCSS_RTC_CCTRL_ALMFLG; + + tmr_irq = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL) & + PRTCSS_RTC_CTRL_TMRFLG; + + if (irq_flg) { + if (alm_irq) { + events |= RTC_IRQF | RTC_AF; + rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); + rtc_cctrl |= PRTCSS_RTC_CCTRL_ALMFLG; + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); + } else if (tmr_irq) { + events |= RTC_IRQF | RTC_PF; + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); + rtc_ctrl |= PRTCSS_RTC_CTRL_TMRFLG; + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); + } + + rtcif_write(davinci_rtc, PRTCIF_INTFLG_RTCSS, + PRTCIF_INTFLG); + rtc_update_irq(davinci_rtc->rtc, 1, events); + + ret = IRQ_HANDLED; + } + + return ret; +} + +static int +davinci_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + u8 rtc_ctrl; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); + + switch (cmd) { + case RTC_WIE_ON: + rtc_ctrl |= PRTCSS_RTC_CTRL_WEN | PRTCSS_RTC_CTRL_WDTFLG; + break; + case RTC_WIE_OFF: + rtc_ctrl &= ~PRTCSS_RTC_CTRL_WEN; + break; + case RTC_UIE_OFF: + case RTC_UIE_ON: + ret = -ENOTTY; + break; + default: + ret = -ENOIOCTLCMD; + } + + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return ret; +} + +static int convertfromdays(u16 days, struct rtc_time *tm) +{ + int tmp_days, year, mon; + + for (year = 2000;; year++) { + tmp_days = rtc_year_days(1, 12, year); + if (days >= tmp_days) + days -= tmp_days; + else { + for (mon = 0;; mon++) { + tmp_days = rtc_month_days(mon, year); + if (days >= tmp_days) { + days -= tmp_days; + } else { + tm->tm_year = year - 1900; + tm->tm_mon = mon; + tm->tm_mday = days + 1; + break; + } + } + break; + } + } + return 0; +} + +static int convert2days(u16 *days, struct rtc_time *tm) +{ + int i; + *days = 0; + + /* epoch == 1900 */ + if (tm->tm_year < 100 || tm->tm_year > 199) + return -EINVAL; + + for (i = 2000; i < 1900 + tm->tm_year; i++) + *days += rtc_year_days(1, 12, i); + + *days += rtc_year_days(tm->tm_mday, tm->tm_mon, 1900 + tm->tm_year); + + return 0; +} + +static int davinci_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + u16 days = 0; + u8 day0, day1; + unsigned long flags; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + davinci_rtcss_calendar_wait(davinci_rtc); + tm->tm_sec = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_SEC)); + + davinci_rtcss_calendar_wait(davinci_rtc); + tm->tm_min = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_MIN)); + + davinci_rtcss_calendar_wait(davinci_rtc); + tm->tm_hour = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_HOUR)); + + davinci_rtcss_calendar_wait(davinci_rtc); + day0 = rtcss_read(davinci_rtc, PRTCSS_RTC_DAY0); + + davinci_rtcss_calendar_wait(davinci_rtc); + day1 = rtcss_read(davinci_rtc, PRTCSS_RTC_DAY1); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + days |= day1; + days <<= 8; + days |= day0; + + if (convertfromdays(days, tm) < 0) + return -EINVAL; + + return 0; +} + +static int davinci_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + u16 days; + u8 rtc_cctrl; + unsigned long flags; + + if (convert2days(&days, tm) < 0) + return -EINVAL; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, bin2bcd(tm->tm_sec), PRTCSS_RTC_SEC); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, bin2bcd(tm->tm_min), PRTCSS_RTC_MIN); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, bin2bcd(tm->tm_hour), PRTCSS_RTC_HOUR); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, days & 0xFF, PRTCSS_RTC_DAY0); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, (days & 0xFF00) >> 8, PRTCSS_RTC_DAY1); + + rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); + rtc_cctrl |= PRTCSS_RTC_CCTRL_CAEN; + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return 0; +} + +static int davinci_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + unsigned long flags; + u8 rtc_cctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CCTRL); + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + if (enabled) + rtc_cctrl |= PRTCSS_RTC_CCTRL_DAEN | + PRTCSS_RTC_CCTRL_HAEN | + PRTCSS_RTC_CCTRL_MAEN | + PRTCSS_RTC_CCTRL_ALMFLG | + PRTCSS_RTC_CCTRL_AIEN; + else + rtc_cctrl &= ~PRTCSS_RTC_CCTRL_AIEN; + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, rtc_cctrl, PRTCSS_RTC_CCTRL); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return 0; +} + +static int davinci_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + u16 days = 0; + u8 day0, day1; + unsigned long flags; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + davinci_rtcss_calendar_wait(davinci_rtc); + alm->time.tm_min = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_AMIN)); + + davinci_rtcss_calendar_wait(davinci_rtc); + alm->time.tm_hour = bcd2bin(rtcss_read(davinci_rtc, PRTCSS_RTC_AHOUR)); + + davinci_rtcss_calendar_wait(davinci_rtc); + day0 = rtcss_read(davinci_rtc, PRTCSS_RTC_ADAY0); + + davinci_rtcss_calendar_wait(davinci_rtc); + day1 = rtcss_read(davinci_rtc, PRTCSS_RTC_ADAY1); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + days |= day1; + days <<= 8; + days |= day0; + + if (convertfromdays(days, &alm->time) < 0) + return -EINVAL; + + alm->pending = !!(rtcss_read(davinci_rtc, + PRTCSS_RTC_CCTRL) & + PRTCSS_RTC_CCTRL_AIEN); + alm->enabled = alm->pending && device_may_wakeup(dev); + + return 0; +} + +static int davinci_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + unsigned long flags; + u16 days; + + if (alm->time.tm_mday <= 0 && alm->time.tm_mon < 0 + && alm->time.tm_year < 0) { + struct rtc_time tm; + unsigned long now, then; + + davinci_rtc_read_time(dev, &tm); + rtc_tm_to_time(&tm, &now); + + alm->time.tm_mday = tm.tm_mday; + alm->time.tm_mon = tm.tm_mon; + alm->time.tm_year = tm.tm_year; + rtc_tm_to_time(&alm->time, &then); + + if (then < now) { + rtc_time_to_tm(now + 24 * 60 * 60, &tm); + alm->time.tm_mday = tm.tm_mday; + alm->time.tm_mon = tm.tm_mon; + alm->time.tm_year = tm.tm_year; + } + } + + if (convert2days(&days, &alm->time) < 0) + return -EINVAL; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, bin2bcd(alm->time.tm_min), PRTCSS_RTC_AMIN); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, bin2bcd(alm->time.tm_hour), PRTCSS_RTC_AHOUR); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, days & 0xFF, PRTCSS_RTC_ADAY0); + + davinci_rtcss_calendar_wait(davinci_rtc); + rtcss_write(davinci_rtc, (days & 0xFF00) >> 8, PRTCSS_RTC_ADAY1); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return 0; +} + +static int davinci_rtc_irq_set_state(struct device *dev, int enabled) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + unsigned long flags; + u8 rtc_ctrl; + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + rtc_ctrl = rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL); + + if (enabled) { + while (rtcss_read(davinci_rtc, PRTCSS_RTC_CTRL) + & PRTCSS_RTC_CTRL_WDTBUS) + cpu_relax(); + + rtc_ctrl |= PRTCSS_RTC_CTRL_TE; + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); + + rtcss_write(davinci_rtc, 0x0, PRTCSS_RTC_CLKC_CNT); + + rtc_ctrl |= PRTCSS_RTC_CTRL_TIEN | + PRTCSS_RTC_CTRL_TMMD | + PRTCSS_RTC_CTRL_TMRFLG; + } else + rtc_ctrl &= ~PRTCSS_RTC_CTRL_TIEN; + + rtcss_write(davinci_rtc, rtc_ctrl, PRTCSS_RTC_CTRL); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return 0; +} + +static int davinci_rtc_irq_set_freq(struct device *dev, int freq) +{ + struct davinci_rtc *davinci_rtc = dev_get_drvdata(dev); + unsigned long flags; + u16 tmr_counter = (0x8000 >> (ffs(freq) - 1)); + + spin_lock_irqsave(&davinci_rtc_lock, flags); + + rtcss_write(davinci_rtc, tmr_counter & 0xFF, PRTCSS_RTC_TMR0); + rtcss_write(davinci_rtc, (tmr_counter & 0xFF00) >> 8, PRTCSS_RTC_TMR1); + + spin_unlock_irqrestore(&davinci_rtc_lock, flags); + + return 0; +} + +static struct rtc_class_ops davinci_rtc_ops = { + .ioctl = davinci_rtc_ioctl, + .read_time = davinci_rtc_read_time, + .set_time = davinci_rtc_set_time, + .alarm_irq_enable = davinci_rtc_alarm_irq_enable, + .read_alarm = davinci_rtc_read_alarm, + .set_alarm = davinci_rtc_set_alarm, + .irq_set_state = davinci_rtc_irq_set_state, + .irq_set_freq = davinci_rtc_irq_set_freq, +}; + +static int __init davinci_rtc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct davinci_rtc *davinci_rtc; + struct resource *res, *mem; + int ret = 0; + + davinci_rtc = kzalloc(sizeof(struct davinci_rtc), GFP_KERNEL); + if (!davinci_rtc) { + dev_dbg(dev, "could not allocate memory for private data\n"); + return -ENOMEM; + } + + davinci_rtc->irq = platform_get_irq(pdev, 0); + if (davinci_rtc->irq < 0) { + dev_err(dev, "no RTC irq\n"); + ret = davinci_rtc->irq; + goto fail1; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "no mem resource\n"); + ret = -EINVAL; + goto fail1; + } + + davinci_rtc->pbase = res->start; + davinci_rtc->base_size = resource_size(res); + + mem = request_mem_region(davinci_rtc->pbase, davinci_rtc->base_size, + pdev->name); + if (!mem) { + dev_err(dev, "RTC registers at %08x are not free\n", + davinci_rtc->pbase); + ret = -EBUSY; + goto fail1; + } + + davinci_rtc->base = ioremap(davinci_rtc->pbase, davinci_rtc->base_size); + if (!davinci_rtc->base) { + dev_err(dev, "unable to ioremap MEM resource\n"); + ret = -ENOMEM; + goto fail2; + } + + davinci_rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, + &davinci_rtc_ops, THIS_MODULE); + if (IS_ERR(davinci_rtc->rtc)) { + dev_err(dev, "unable to register RTC device, err %ld\n", + PTR_ERR(davinci_rtc->rtc)); + goto fail3; + } + + rtcif_write(davinci_rtc, PRTCIF_INTFLG_RTCSS, PRTCIF_INTFLG); + rtcif_write(davinci_rtc, 0, PRTCIF_INTEN); + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_INTC_EXTENA1); + + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_CTRL); + rtcss_write(davinci_rtc, 0, PRTCSS_RTC_CCTRL); + + ret = request_irq(davinci_rtc->irq, davinci_rtc_interrupt, + IRQF_DISABLED, "davinci_rtc", davinci_rtc); + if (ret < 0) { + dev_err(dev, "unable to register davinci RTC interrupt\n"); + goto fail4; + } + + /* Enable interrupts */ + rtcif_write(davinci_rtc, PRTCIF_INTEN_RTCSS, PRTCIF_INTEN); + rtcss_write(davinci_rtc, PRTCSS_RTC_INTC_EXTENA1_MASK, + PRTCSS_RTC_INTC_EXTENA1); + + rtcss_write(davinci_rtc, PRTCSS_RTC_CCTRL_CAEN, PRTCSS_RTC_CCTRL); + + platform_set_drvdata(pdev, davinci_rtc); + + device_init_wakeup(&pdev->dev, 0); + + return 0; + +fail4: + rtc_device_unregister(davinci_rtc->rtc); +fail3: + iounmap(davinci_rtc->base); +fail2: + release_mem_region(davinci_rtc->pbase, davinci_rtc->base_size); +fail1: + kfree(davinci_rtc); + + return ret; +} + +static int __devexit davinci_rtc_remove(struct platform_device *pdev) +{ + struct davinci_rtc *davinci_rtc = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + + rtcif_write(davinci_rtc, 0, PRTCIF_INTEN); + + free_irq(davinci_rtc->irq, davinci_rtc); + + rtc_device_unregister(davinci_rtc->rtc); + + iounmap(davinci_rtc->base); + release_mem_region(davinci_rtc->pbase, davinci_rtc->base_size); + + platform_set_drvdata(pdev, NULL); + + kfree(davinci_rtc); + + return 0; +} + +static struct platform_driver davinci_rtc_driver = { + .probe = davinci_rtc_probe, + .remove = __devexit_p(davinci_rtc_remove), + .driver = { + .name = "rtc_davinci", + .owner = THIS_MODULE, + }, +}; + +static int __init rtc_init(void) +{ + return platform_driver_probe(&davinci_rtc_driver, davinci_rtc_probe); +} +module_init(rtc_init); + +static void __exit rtc_exit(void) +{ + platform_driver_unregister(&davinci_rtc_driver); +} +module_exit(rtc_exit); + +MODULE_AUTHOR("Miguel Aguilar"); +MODULE_DESCRIPTION("Texas Instruments DaVinci PRTC Driver"); +MODULE_LICENSE("GPL");