diff mbox

[4/5] RTC driver for MX1

Message ID 49C8F293.3070900@gmail.com
State Superseded, archived
Headers show

Commit Message

Darius Augulis March 24, 2009, 2:47 p.m. UTC
--~--~---------~--~----~------------~-------~--~----~
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.
-~----------~----~----~----~------~----~------~--~---
From: Paulius Zaleckas <paulius.zaleckas@teltonika.lt>

Add i.MX1/L RTC driver

Signed-off-by: Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
---

Comments

Trilok Soni March 24, 2009, 5:15 p.m. UTC | #1
Hi Darius,

> ---
>
> Index: linux-2.6.29-rc5/drivers/rtc/rtc-imx.c
> ===================================================================

Care to rebase with latest kernel version?

> +

> +
> +
> +static int imx_rtc_set_per_state(struct device *dev, int enabled)
> +{
> +       struct imx_priv *priv = dev_get_drvdata(dev);
> +
> +       /* periodic interrupts are not supported if crystal is 32kHz */
> +       if (priv->_32khz)
> +               return -ENXIO;
> +
> +       if (enabled)
> +       {

scripts/checkpatch.pl please.

> +               /* See Hardware bugs/workarounds No.2 for details */
> +               if (priv->per_frq == 2)
> +                       imx_rtc_int_set(priv, RTCI_2HZ | RTCI_SAM(0));
> +               else
> +                       imx_rtc_int_set(priv, RTCI_SAM(ilog2(priv->per_frq) - 2));
> +       } else {
> +               imx_rtc_int_clear(priv, RTCI_SAM(7) | RTCI_SAM(6) |
> +                                       RTCI_SAM(5) | RTCI_SAM(4) |
> +                                       RTCI_SAM(3) | RTCI_SAM(2) |
> +                                       RTCI_SAM(1) | RTCI_SAM(0) | RTCI_2HZ);
> +       }
> +
> +       return 0;
> +}
> +

> +
> +static int imx_rtc_suspend(struct platform_device *pdev, pm_message_t state)
> +{

#ifdef CONFIG_PM around suspend and resume hooks?

> +
> +static int __init imx_rtc_probe(struct platform_device *pdev)>
> +{
> +       priv->sam_irq = -1;
> +       priv->size = res->end - res->start + 1;

You may like to use resource_size(...).

> +
> +       /* Reset the RTC if it has invalid values */
> +       imx_rtc_read_time(&pdev->dev, &tm);
> +       if (rtc_valid_tm(&tm))
> +       {


Please run this patch through scripts/checkpatch.pl.


> +               dev_dbg(&pdev->dev, "Invalid time. Resetting RTC.\n");
> +               /* See Hardware bugs/workarounds No.3 for details */

What is No.3 ? and where do we find it?

> +               __raw_writel(0, priv->ioaddr + SECONDS);
> +               __raw_writel(0, priv->ioaddr + HOURMIN);
> +               __raw_writel(0, priv->ioaddr + DAYR);
> +       }
> +
> +       /* Initialize RTC */
> +       if (priv->_32khz) {

I don't know but underscore before the var name doesn't look good to me.

> +               __raw_writel(RCCTL_EN | RCCTL_XTL_32KHZ, priv->ioaddr + RCCTL);
> +       } else {

> +}
> +
> +static int __exit imx_rtc_remove(struct platform_device *pdev)
> +{
> +       struct imx_priv *priv = platform_get_drvdata(pdev);
> +
> +       if (priv->sam_irq >= 0)
> +               free_irq(priv->sam_irq, priv);
> +       free_irq(priv->irq, priv);
> +       rtc_device_unregister(priv->rtc);
> +       iounmap(priv->ioaddr);
> +       release_mem_region(priv->baseaddr, priv->size);
> +       kfree(priv);
> +       return 0;
> +}
> +
Paulius Zaleckas March 24, 2009, 5:25 p.m. UTC | #2
Trilok Soni wrote:

>> +               dev_dbg(&pdev->dev, "Invalid time. Resetting RTC.\n");
>> +               /* See Hardware bugs/workarounds No.3 for details */
> 
> What is No.3 ? and where do we find it?

At the top of the patch.

--~--~---------~--~----~------------~-------~--~----~
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 mbox

Patch

Index: linux-2.6.29-rc5/drivers/rtc/rtc-imx.c
===================================================================
--- /dev/null
+++ linux-2.6.29-rc5/drivers/rtc/rtc-imx.c
@@ -0,0 +1,550 @@ 
+/*
+ * i.MX SoC RTC driver
+ *
+ * Copyright (c) 2008 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * This file may be distributed under the terms of the GNU General
+ * Public License, version 2.
+ *
+ * Hardware bugs/workarounds:
+ * 1. When alarm seconds is set to odd number false interrupt is generated one
+ *    second before the real one. Workaround is to compare if actual seconds
+ *    is equal to alarm seconds to be sure this is real alarm interrupt. This
+ *    bug and workaround is described in MX1/L erratum.
+ * 2. 2Hz flag in ISR doesn't generate interrupt. It is needed to generate
+ *    RTC 2Hz periodic interrupts. Workaround is to enable 4Hz sampling
+ *    interrupt and check if 2Hz ISR flag is set.
+ * 3. SWR (software reset) bit in RTC control register doesn't reset time/date
+ *    registers. Workaround is to reset them one by one manually.
+ * 4. Not really a bug, but... There is no registers for year and month.
+ *    So we have to calculate month from the days register (value 0-511).
+ *    And it is not possible to have years here... so we will have to handle
+ *    this to platform specific code through set_year() and get_year() functions
+ *    passed by the platform_data.
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <mach/rtc.h>
+
+/*
+ * RTC registers
+ */
+
+#define RCCTL		0x10	/* RTC Control Register */
+#define RTCISR		0x14	/* RTC Interrupt Status Register */
+#define RTCIENR		0x18	/* RTC Interrupt Enable Register */
+#define SECONDS		0x04	/* RTC Seconds Counter Register */
+#define HOURMIN		0x00	/* RTC Hours and Minutes Counter Register */
+#define DAYR		0x20	/* RTC Days Counter Register */
+#define ALRM_SEC	0x0C	/* RTC Seconds Alarm Register */
+#define ALRM_HM		0x08	/* RTC Hours and Minutes Alarm Register */
+#define DAYALARM	0x24	/* RTC Day Alarm Register */
+
+#define RCCTL_EN	(1 << 7)
+#define RCCTL_XTL_32KHZ	(1 << 5)
+
+#define RTCI_SAM(x)	(1 << ((x) + 8))
+#define RTCI_2HZ	(1 << 7)
+#define RTCI_1HZ	(1 << 4)
+#define RTCI_ALM	(1 << 2)
+
+/* See Hardware bugs/workarounds No.2 for details */
+#define RTC_IRQ_MASK		0x007F
+#define RTC_SAM_IRQ_MASK	0xFF80
+
+#define MIN_MASK	0x3f
+#define HOUR_MASK	0x1f
+#define HOUR_SHIFT	8
+#define DAY_MAX		511	/* we have 9bit register for days */
+
+#define DRV_NAME "rtc-imx"
+
+struct imx_priv {
+	struct rtc_device *rtc;
+	void __iomem *ioaddr;
+	int irq;
+	int sam_irq;
+	size_t size;
+	unsigned long baseaddr;
+	struct imxrtc_platform_data *pdata;
+	int year;
+	int _32khz;
+	int per_frq;
+	unsigned int save_irq;
+};
+
+static void imx_rtc_int_set(struct imx_priv *rtc, unsigned int rtc_int)
+{
+	unsigned int temp;
+
+	temp = __raw_readl(rtc->ioaddr + RTCIENR);
+	__raw_writel(temp | rtc_int, rtc->ioaddr + RTCIENR);
+}
+
+static void imx_rtc_int_clear(struct imx_priv *rtc, unsigned int rtc_int)
+{
+	unsigned int temp;
+
+	temp = __raw_readl(rtc->ioaddr + RTCIENR);
+	__raw_writel(temp & ~rtc_int, rtc->ioaddr + RTCIENR);
+}
+
+static irqreturn_t imx_rtc_per_irq(int irq, void *data)
+{
+	struct imx_priv *priv = data;
+	unsigned int status;
+
+	/* we serve only interrupts belonging to this irq handler */
+	status = __raw_readl(priv->ioaddr + RTCISR) & RTC_SAM_IRQ_MASK;
+
+	/* clear irq status register */
+	__raw_writel(status, priv->ioaddr + RTCISR);
+
+	dev_dbg(&priv->rtc->dev, "Sampling irq status: 0x%x\n", status);
+
+	if (unlikely(!status))
+		return IRQ_NONE;
+
+	/* See Hardware bugs/workarounds No.2 for details */
+	if ((__raw_readl(priv->ioaddr + RTCIENR) & RTCI_2HZ) &&
+	    (status & RTCI_SAM(0)) && !(status & RTCI_2HZ))
+		return IRQ_HANDLED;
+
+	rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_PF);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t imx_rtc_irq(int irq, void *data)
+{
+	struct imx_priv *priv = data;
+	unsigned long events = 0;
+	unsigned long number = 0;
+	unsigned int status;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* we serve only interrupts belonging to this irq handler */
+	status = __raw_readl(priv->ioaddr + RTCISR) & RTC_IRQ_MASK;
+
+	/* clear irq status register */
+	__raw_writel(status, priv->ioaddr + RTCISR);
+
+	dev_dbg(&priv->rtc->dev, "RTC irq status: 0x%x\n", status);
+
+	if (status & RTCI_ALM) {
+		/* See Hardware bugs/workarounds No.1 for details */
+		if (__raw_readl(priv->ioaddr + SECONDS) ==
+		    __raw_readl(priv->ioaddr + ALRM_SEC)) {
+			events |= RTC_AF | RTC_IRQF;
+			number++;
+			/*
+			 * disable irq, because it will be triggered again after
+			 * 512 days. Alarm is single-shot event.
+			 */
+			imx_rtc_int_clear(priv, RTCI_ALM);
+		} else {
+			ret = IRQ_HANDLED;
+		}
+	}
+
+	if (status & RTCI_1HZ) {
+		events |= RTC_IRQF | RTC_UF;
+		number++;
+	}
+
+	if (events) {
+		rtc_update_irq(priv->rtc, number, events);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static int imx_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+	int temp;
+
+	tm->tm_sec = __raw_readl(priv->ioaddr + SECONDS);
+	temp = __raw_readl(priv->ioaddr + HOURMIN);
+	tm->tm_min = temp & MIN_MASK;
+	tm->tm_hour = (temp >> HOUR_SHIFT) & HOUR_MASK;
+	tm->tm_yday = __raw_readl(priv->ioaddr + DAYR);
+	/*
+	 * Maybe our day counter leaped over the year?
+	 * See Hardware bugs/workarounds No.4 for details
+	 */
+	temp = rtc_year_days(1, 12, priv->year);
+	if (tm->tm_yday > temp) {
+		priv->year++;
+		if (priv->pdata && priv->pdata->set_year)
+			priv->pdata->set_year(dev, priv->year);
+		tm->tm_yday -= temp;
+		/* Write new days value back to register */
+		__raw_writel(tm->tm_yday, priv->ioaddr + DAYR);
+	}
+	tm->tm_year = priv->year;
+	/* calculate month and month day from year days */
+	tm->tm_mon = 0;
+	tm->tm_mday = tm->tm_yday;
+	temp = rtc_month_days(0, tm->tm_year);
+	while(tm->tm_mday > temp) {
+		tm->tm_mon++;
+		tm->tm_mday -= temp;
+		temp = rtc_month_days(tm->tm_mon, tm->tm_year);
+	}
+	/* first day in month is 1 */
+	tm->tm_mday++;
+
+	return 0;
+}
+
+static int imx_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+	int temp;
+
+	__raw_writel(tm->tm_sec, priv->ioaddr + SECONDS);
+	temp = tm->tm_min | (tm->tm_hour << HOUR_SHIFT);
+	__raw_writel(temp, priv->ioaddr + HOURMIN);
+	temp = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+	__raw_writel(temp, priv->ioaddr + DAYR);
+	priv->year = tm->tm_year;
+	/* See Hardware bugs/workarounds No.4 for details */
+	if (priv->pdata && priv->pdata->set_year)
+		priv->pdata->set_year(dev, priv->year);
+
+	return 0;
+}
+
+static int imx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alm->time;
+	int temp;
+
+	tm->tm_sec = __raw_readl(priv->ioaddr + ALRM_SEC);
+	temp = __raw_readl(priv->ioaddr + ALRM_HM);
+	tm->tm_min = temp & MIN_MASK;
+	tm->tm_hour = (temp >> HOUR_SHIFT) & HOUR_MASK;
+	tm->tm_yday = __raw_readl(priv->ioaddr + DAYALARM);
+	tm->tm_year = priv->year;
+	/* Maybe our day counter leaped over the year? */
+	temp = rtc_year_days(1, 12, priv->year);
+	if (tm->tm_yday > temp) {
+		tm->tm_year++;
+		tm->tm_yday -= temp;
+	}
+	/* calculate month and month day from year days */
+	tm->tm_mon = 0;
+	tm->tm_mday = tm->tm_yday;
+	temp = rtc_month_days(0, tm->tm_year);
+	while(tm->tm_mday > temp) {
+		tm->tm_mon++;
+		tm->tm_mday -= temp;
+		temp = rtc_month_days(tm->tm_mon, tm->tm_year);
+	}
+	/* first day in month is 1 */
+	tm->tm_mday++;
+
+	alm->enabled = !!(__raw_readl(priv->ioaddr + RTCIENR) & RTCI_ALM);
+
+	return 0;
+}
+
+static int imx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+	struct rtc_time *tm = &alm->time;
+	int temp = 0;
+
+	if (tm->tm_year != priv->year) {
+		/* This RTC doesn't support alarms longer than 2 years */
+		if (tm->tm_year - priv->year > 1)
+			return -EINVAL;
+		temp = rtc_year_days(1, 12, priv->year);
+	}
+	temp += rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+	/* check if we will fit in days register */
+	if (temp > DAY_MAX)
+		return -EINVAL;
+	__raw_writel(temp, priv->ioaddr + DAYALARM);
+	__raw_writel(tm->tm_sec, priv->ioaddr + ALRM_SEC);
+	temp = tm->tm_min | (tm->tm_hour << HOUR_SHIFT);
+	__raw_writel(temp, priv->ioaddr + ALRM_HM);
+
+	if (alm->enabled)
+		imx_rtc_int_set(priv, RTCI_ALM);
+	else
+		imx_rtc_int_clear(priv, RTCI_ALM);
+
+	return 0;
+}
+
+static int imx_rtc_set_per_state(struct device *dev, int enabled)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+
+	/* periodic interrupts are not supported if crystal is 32kHz */
+	if (priv->_32khz)
+		return -ENXIO;
+
+	if (enabled)
+	{
+		/* See Hardware bugs/workarounds No.2 for details */
+		if (priv->per_frq == 2)
+			imx_rtc_int_set(priv, RTCI_2HZ | RTCI_SAM(0));
+		else
+			imx_rtc_int_set(priv, RTCI_SAM(ilog2(priv->per_frq) - 2));
+	} else {
+		imx_rtc_int_clear(priv, RTCI_SAM(7) | RTCI_SAM(6) |
+					RTCI_SAM(5) | RTCI_SAM(4) |
+					RTCI_SAM(3) | RTCI_SAM(2) |
+					RTCI_SAM(1) | RTCI_SAM(0) | RTCI_2HZ);
+	}
+
+	return 0;
+}
+
+static int imx_rtc_set_per_freq(struct device *dev, int freq)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+
+	/* periodic interrupts are not supported if crystal is 32kHz */
+	if (priv->_32khz)
+		return -ENXIO;
+
+	if (freq > 512)
+		return -EINVAL;
+
+	priv->per_frq = freq;
+
+	/* reinitialize irq for new frequency if periodic irq was enabled */
+	if (__raw_readl(priv->ioaddr + RTCIENR) & (RTCI_SAM(7) | RTCI_SAM(6) |
+	    RTCI_SAM(5) | RTCI_SAM(4) |	RTCI_SAM(3) | RTCI_SAM(2) |
+	    RTCI_SAM(1) | RTCI_SAM(0) | RTCI_2HZ)) {
+		imx_rtc_set_per_state(dev, 0);
+		imx_rtc_set_per_state(dev, 1);
+	}
+
+	return 0;
+}
+
+static int
+imx_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+	struct imx_priv *priv = dev_get_drvdata(dev);
+
+	switch (cmd) {
+	/* AIE = Alarm Interrupt Enable */
+	case RTC_AIE_OFF:
+		imx_rtc_int_clear(priv, RTCI_ALM);
+		break;
+	case RTC_AIE_ON:
+		imx_rtc_int_set(priv, RTCI_ALM);
+		break;
+	/* UIE = Update Interrupt Enable (1/second) */
+	case RTC_UIE_OFF:
+		imx_rtc_int_clear(priv, RTCI_1HZ);
+		break;
+	case RTC_UIE_ON:
+		imx_rtc_int_set(priv, RTCI_1HZ);
+		break;
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+static const struct rtc_class_ops imx_rtc_ops = {
+	.read_time	= imx_rtc_read_time,
+	.set_time	= imx_rtc_set_time,
+	.read_alarm	= imx_rtc_read_alarm,
+	.set_alarm	= imx_rtc_set_alarm,
+	.irq_set_state	= imx_rtc_set_per_state,
+	.irq_set_freq	= imx_rtc_set_per_freq,
+	.ioctl		= imx_rtc_ioctl,
+};
+
+static int imx_rtc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct imx_priv *priv = platform_get_drvdata(pdev);
+
+	priv->save_irq = __raw_readl(priv->ioaddr + RTCIENR);
+
+	if (device_may_wakeup(&pdev->dev) && (priv->save_irq & RTCI_ALM)) {
+		priv->save_irq &= ~RTCI_ALM;
+		enable_irq_wake(priv->irq);
+	}
+
+	imx_rtc_int_clear(priv, priv->save_irq);
+
+	return 0;
+}
+
+static int imx_rtc_resume(struct platform_device *pdev)
+{
+	struct imx_priv *priv = platform_get_drvdata(pdev);
+
+	if (device_may_wakeup(&pdev->dev) && (priv->save_irq & RTCI_ALM))
+		disable_irq_wake(priv->irq);
+
+	imx_rtc_int_set(priv, priv->save_irq);
+
+	return 0;
+}
+
+static int __init imx_rtc_probe(struct platform_device *pdev)
+{
+	struct rtc_device *rtc;
+	struct resource *res;
+	int irq;
+	struct imx_priv *priv;
+	struct rtc_time tm;
+	int ret = 0;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+	priv = kzalloc(sizeof *priv, GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->sam_irq = -1;
+	priv->size = res->end - res->start + 1;
+	if (!request_mem_region(res->start, priv->size, pdev->name)) {
+		ret = -EBUSY;
+		goto out;
+	}
+	priv->baseaddr = res->start;
+	priv->irq = irq;
+	priv->ioaddr = ioremap(priv->baseaddr, priv->size);
+	if (!priv->ioaddr) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	rtc = rtc_device_register(pdev->name, &pdev->dev,
+				  &imx_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		ret = PTR_ERR(rtc);
+		goto out;
+	}
+	priv->rtc = rtc;
+	platform_set_drvdata(pdev, priv);
+
+	/* request irq */
+	ret = request_irq(priv->irq, imx_rtc_irq, 0, DRV_NAME, priv);
+	if (ret)
+		goto out;
+
+	priv->pdata = pdev->dev.platform_data;
+
+	priv->year = 70;
+
+	if (!priv->pdata) {
+		dev_warn(&pdev->dev, "No platform data provided\n");
+		priv->_32khz = 0;
+	} else {
+		priv->_32khz = priv->pdata->freq_is_32khz;
+		/* See Hardware bugs/workarounds No.4 for details */
+		if (priv->pdata->get_year)
+			priv->year = priv->pdata->get_year(&pdev->dev);
+	}
+
+	/* Reset the RTC if it has invalid values */
+	imx_rtc_read_time(&pdev->dev, &tm);
+	if (rtc_valid_tm(&tm))
+	{
+		dev_dbg(&pdev->dev, "Invalid time. Resetting RTC.\n");
+		/* See Hardware bugs/workarounds No.3 for details */
+		__raw_writel(0, priv->ioaddr + SECONDS);
+		__raw_writel(0, priv->ioaddr + HOURMIN);
+		__raw_writel(0, priv->ioaddr + DAYR);
+	}
+
+	/* Initialize RTC */
+	if (priv->_32khz) {
+		__raw_writel(RCCTL_EN | RCCTL_XTL_32KHZ, priv->ioaddr + RCCTL);
+	} else {
+		__raw_writel(RCCTL_EN, priv->ioaddr + RCCTL);
+		priv->sam_irq = platform_get_irq(pdev, 1);
+		if (priv->sam_irq >= 0)
+			if(request_irq(priv->sam_irq, imx_rtc_per_irq, 0,
+				       DRV_NAME, priv))
+				priv->sam_irq = -1;
+		/*
+		 * hack to disable periodic irq if we failed to
+		 * register RTC sampling interrupt.
+		 */
+		if (priv->sam_irq < 0) {
+			dev_warn(&pdev->dev, "Failed to register i.MX RTC "
+				 "sampling interrupt\n");
+			priv->_32khz = 1;
+		}
+	}
+
+	return 0;
+
+out:
+	if (priv->sam_irq >= 0)
+		free_irq(priv->sam_irq, priv);
+	if (priv->rtc)
+		rtc_device_unregister(priv->rtc);
+	if (priv->ioaddr)
+		iounmap(priv->ioaddr);
+	if (priv->baseaddr)
+		release_mem_region(priv->baseaddr, priv->size);
+	kfree(priv);
+	return ret;
+}
+
+static int __exit imx_rtc_remove(struct platform_device *pdev)
+{
+	struct imx_priv *priv = platform_get_drvdata(pdev);
+
+	if (priv->sam_irq >= 0)
+		free_irq(priv->sam_irq, priv);
+	free_irq(priv->irq, priv);
+	rtc_device_unregister(priv->rtc);
+	iounmap(priv->ioaddr);
+	release_mem_region(priv->baseaddr, priv->size);
+	kfree(priv);
+	return 0;
+}
+
+static struct platform_driver imx_rtc_platform_driver = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+	},
+	.remove		= __exit_p(imx_rtc_remove),
+	.suspend	= imx_rtc_suspend,
+	.resume		= imx_rtc_resume,
+};
+
+static int __init imx_rtc_init(void)
+{
+	return platform_driver_probe(&imx_rtc_platform_driver, imx_rtc_probe);
+}
+
+static void __exit imx_rtc_exit(void)
+{
+	platform_driver_unregister(&imx_rtc_platform_driver);
+}
+
+module_init(imx_rtc_init);
+module_exit(imx_rtc_exit);
+
+MODULE_AUTHOR("Paulius Zaleckas <paulius.zaleckas@teltonika.lt>");
+MODULE_DESCRIPTION("i.MX SoC RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
Index: linux-2.6.29-rc5/drivers/rtc/Kconfig
===================================================================
--- linux-2.6.29-rc5.orig/drivers/rtc/Kconfig
+++ linux-2.6.29-rc5/drivers/rtc/Kconfig
@@ -736,4 +736,14 @@  config RTC_DRV_MV
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-mv.
 
+config RTC_DRV_IMX
+	tristate "i.MX On-Chip RTC"
+	depends on ARCH_MX1
+	help
+	  If you say yes here you will get support for the
+	  i.MX On-Chip Real Time Clock.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-imx.
+
 endif # RTC_CLASS
Index: linux-2.6.29-rc5/drivers/rtc/Makefile
===================================================================
--- linux-2.6.29-rc5.orig/drivers/rtc/Makefile
+++ linux-2.6.29-rc5/drivers/rtc/Makefile
@@ -38,6 +38,7 @@  obj-$(CONFIG_RTC_DRV_DS1742)	+= rtc-ds17
 obj-$(CONFIG_RTC_DRV_DS3234)	+= rtc-ds3234.o
 obj-$(CONFIG_RTC_DRV_EP93XX)	+= rtc-ep93xx.o
 obj-$(CONFIG_RTC_DRV_FM3130)	+= rtc-fm3130.o
+obj-$(CONFIG_RTC_DRV_IMX)	+= rtc-imx.o
 obj-$(CONFIG_RTC_DRV_ISL1208)	+= rtc-isl1208.o
 obj-$(CONFIG_RTC_DRV_M41T80)	+= rtc-m41t80.o
 obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o
Index: linux-2.6.29-rc5/arch/arm/plat-mxc/include/mach/rtc.h
===================================================================
--- /dev/null
+++ linux-2.6.29-rc5/arch/arm/plat-mxc/include/mach/rtc.h
@@ -0,0 +1,19 @@ 
+/*
+ * rtc.h - i.MX RTC driver header file
+ *
+ * Copyright (c) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * This file is released under the GPLv2
+ */
+
+#ifndef __ASM_ARCH_RTC_H_
+#define __ASM_ARCH_RTC_H_
+
+struct imxrtc_platform_data {
+	void (*set_year)(struct device *, int);
+	int (*get_year)(struct device *);
+
+	int freq_is_32khz;
+};
+
+#endif /* __ASM_ARCH_RTC_H_ */