|  | /* | 
|  | * 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> | 
|  | #include <linux/slab.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; | 
|  | 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 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, | 
|  | }; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, davinci_rtc); | 
|  |  | 
|  | 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, | 
|  | 0, "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); | 
|  |  | 
|  | device_init_wakeup(&pdev->dev, 0); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail4: | 
|  | rtc_device_unregister(davinci_rtc->rtc); | 
|  | fail3: | 
|  | platform_set_drvdata(pdev, NULL); | 
|  | 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 <miguel.aguilar@ridgerun.com>"); | 
|  | MODULE_DESCRIPTION("Texas Instruments DaVinci PRTC Driver"); | 
|  | MODULE_LICENSE("GPL"); |