Patchwork Add rtc driver for Coldfire M5441x.

login
register
mail settings
Submitter Steven King
Date June 17, 2012, 7:50 a.m.
Message ID <201206170050.45652.sfking@fdwdc.com>
Download mbox | patch
Permalink /patch/165328/
State New
Headers show

Comments

Steven King - June 17, 2012, 7:50 a.m.
Add support for the Coldfire m5441x on-chip rtc.

Signed-off-by: Stven King <sfking@fdwdc.com>
---
 drivers/rtc/Kconfig      |   10 ++
 drivers/rtc/Makefile     |    1 +
 drivers/rtc/rtc-m5441x.c |  363 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 374 insertions(+)
Geert Uytterhoeven - June 17, 2012, 9:09 a.m.
On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
> +config RTC_DRV_M5441x
> +       tristate "Freescale Coldfire M5441x RTC support"
> +       depends on M5441x
> +       help
> +         This enables support for the RTC on the Freescale Coldfire 5441x
> +         (54410/54415/54416/54417/54418).
> +
> +         This driver can also be built as a module.  If so, the module
> +         will be called rtc-m5441x.

But the platform device is called  differently:

> +static struct platform_driver m5441x_rtc_driver = {
> +       .driver.name    = "mcfrtc",
> +       .driver.owner   = THIS_MODULE,
> +       .remove         = __devexit_p(m5441x_rtc_remove),
> +};

Is there a specific reason for that?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Steven King - June 17, 2012, 9:23 a.m.
On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
> > +config RTC_DRV_M5441x
> > +       tristate "Freescale Coldfire M5441x RTC support"
> > +       depends on M5441x
> > +       help
> > +         This enables support for the RTC on the Freescale Coldfire
> > 5441x +         (54410/54415/54416/54417/54418).
> > +
> > +         This driver can also be built as a module.  If so, the module
> > +         will be called rtc-m5441x.
>
> But the platform device is called  differently:
> > +static struct platform_driver m5441x_rtc_driver = {
> > +       .driver.name    = "mcfrtc",
> > +       .driver.owner   = THIS_MODULE,
> > +       .remove         = __devexit_p(m5441x_rtc_remove),
> > +};
>
> Is there a specific reason for that?

You mean the "mcfrtc" bit?  Thats the same as what we do for all of the other 
Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.
Steven King - June 17, 2012, 10:01 a.m.
On Sunday 17 June 2012 12:50:45 am Steven King wrote:
> Add support for the Coldfire m5441x on-chip rtc.
>
> Signed-off-by: Stven King <sfking@fdwdc.com>

D'oh!  that should be Steven not Stven.
Geert Uytterhoeven - June 17, 2012, 11:15 a.m.
On Sun, Jun 17, 2012 at 11:23 AM, Steven King <sfking@fdwdc.com> wrote:
> On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
>> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
>> > +config RTC_DRV_M5441x
>> > +       tristate "Freescale Coldfire M5441x RTC support"
>> > +       depends on M5441x
>> > +       help
>> > +         This enables support for the RTC on the Freescale Coldfire
>> > 5441x +         (54410/54415/54416/54417/54418).
>> > +
>> > +         This driver can also be built as a module.  If so, the module
>> > +         will be called rtc-m5441x.
>>
>> But the platform device is called  differently:
>> > +static struct platform_driver m5441x_rtc_driver = {
>> > +       .driver.name    = "mcfrtc",
>> > +       .driver.owner   = THIS_MODULE,
>> > +       .remove         = __devexit_p(m5441x_rtc_remove),
>> > +};
>>
>> Is there a specific reason for that?
>
> You mean the "mcfrtc" bit?  Thats the same as what we do for all of the other

That's what I meant.

> Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.

So why not call the driver mcf-rtc?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Steven King - June 17, 2012, 11:40 a.m.
On Sunday 17 June 2012 4:15:03 am Geert Uytterhoeven wrote:
> On Sun, Jun 17, 2012 at 11:23 AM, Steven King <sfking@fdwdc.com> wrote:
> > On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
> >> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
> >> > +config RTC_DRV_M5441x
> >> > +       tristate "Freescale Coldfire M5441x RTC support"
> >> > +       depends on M5441x
> >> > +       help
> >> > +         This enables support for the RTC on the Freescale Coldfire
> >> > 5441x +         (54410/54415/54416/54417/54418).
> >> > +
> >> > +         This driver can also be built as a module.  If so, the
> >> > module +         will be called rtc-m5441x.
> >>
> >> But the platform device is called  differently:
> >> > +static struct platform_driver m5441x_rtc_driver = {
> >> > +       .driver.name    = "mcfrtc",
> >> > +       .driver.owner   = THIS_MODULE,
> >> > +       .remove         = __devexit_p(m5441x_rtc_remove),
> >> > +};
> >>
> >> Is there a specific reason for that?
> >
> > You mean the "mcfrtc" bit?  Thats the same as what we do for all of the
> > other
>
> That's what I meant.
>
> > Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.
>
> So why not call the driver mcf-rtc?

Because that rtc implementation is specific to the m5441x; should someone 
implement a driver for the rtc on the 532x or 54455 which are somewhat 
different than the m5441x, then they might well need a separate rtc-m532x or 
rtc-m54455.
Geert Uytterhoeven - June 17, 2012, 11:50 a.m.
Hi Steven,

On Sun, Jun 17, 2012 at 1:40 PM, Steven King <sfking@fdwdc.com> wrote:
> On Sunday 17 June 2012 4:15:03 am Geert Uytterhoeven wrote:
>> On Sun, Jun 17, 2012 at 11:23 AM, Steven King <sfking@fdwdc.com> wrote:
>> > On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
>> >> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
>> >> > +config RTC_DRV_M5441x
>> >> > +       tristate "Freescale Coldfire M5441x RTC support"
>> >> > +       depends on M5441x
>> >> > +       help
>> >> > +         This enables support for the RTC on the Freescale Coldfire
>> >> > 5441x +         (54410/54415/54416/54417/54418).
>> >> > +
>> >> > +         This driver can also be built as a module.  If so, the
>> >> > module +         will be called rtc-m5441x.
>> >>
>> >> But the platform device is called  differently:
>> >> > +static struct platform_driver m5441x_rtc_driver = {
>> >> > +       .driver.name    = "mcfrtc",
>> >> > +       .driver.owner   = THIS_MODULE,
>> >> > +       .remove         = __devexit_p(m5441x_rtc_remove),
>> >> > +};
>> >>
>> >> Is there a specific reason for that?
>> >
>> > You mean the "mcfrtc" bit?  Thats the same as what we do for all of the
>> > other
>>
>> That's what I meant.
>>
>> > Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.
>>
>> So why not call the driver mcf-rtc?
>
> Because that rtc implementation is specific to the m5441x; should someone
> implement a driver for the rtc on the 532x or 54455 which are somewhat
> different than the m5441x, then they might well need a separate rtc-m532x or
> rtc-m54455.

At that point, you'll have to call the platform device rtc-m532x or rtc-m54455
as well, as mcfrtc is already taken for the m5441x, right?

So why not call the platform device rtc-m5441x now?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Steven King - June 17, 2012, 12:10 p.m.
On Sunday 17 June 2012 4:50:27 am Geert Uytterhoeven wrote:
> Hi Steven,
>
> On Sun, Jun 17, 2012 at 1:40 PM, Steven King <sfking@fdwdc.com> wrote:
> > On Sunday 17 June 2012 4:15:03 am Geert Uytterhoeven wrote:
> >> On Sun, Jun 17, 2012 at 11:23 AM, Steven King <sfking@fdwdc.com> wrote:
> >> > On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
> >> >> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
> >> >> > +config RTC_DRV_M5441x
> >> >> > +       tristate "Freescale Coldfire M5441x RTC support"
> >> >> > +       depends on M5441x
> >> >> > +       help
> >> >> > +         This enables support for the RTC on the Freescale
> >> >> > Coldfire 5441x +         (54410/54415/54416/54417/54418).
> >> >> > +
> >> >> > +         This driver can also be built as a module.  If so, the
> >> >> > module +         will be called rtc-m5441x.
> >> >>
> >> >> But the platform device is called  differently:
> >> >> > +static struct platform_driver m5441x_rtc_driver = {
> >> >> > +       .driver.name    = "mcfrtc",
> >> >> > +       .driver.owner   = THIS_MODULE,
> >> >> > +       .remove         = __devexit_p(m5441x_rtc_remove),
> >> >> > +};
> >> >>
> >> >> Is there a specific reason for that?
> >> >
> >> > You mean the "mcfrtc" bit?  Thats the same as what we do for all of
> >> > the other
> >>
> >> That's what I meant.
> >>
> >> > Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.
> >>
> >> So why not call the driver mcf-rtc?
> >
> > Because that rtc implementation is specific to the m5441x; should someone
> > implement a driver for the rtc on the 532x or 54455 which are somewhat
> > different than the m5441x, then they might well need a separate rtc-m532x
> > or rtc-m54455.
>
> At that point, you'll have to call the platform device rtc-m532x or
> rtc-m54455 as well, as mcfrtc is already taken for the m5441x, right?

No.   The others would still use mcfrtc as their device name.  Thats the whole 
point.  The platform code in arch/m68k/platform/coldfire/device.c can have 
single definition of an 'mcfrtc' device and which ever coldfire version is 
selected determines which rtc would be availible.

> So why not call the platform device rtc-m5441x now?

Because that would be wrong.
Geert Uytterhoeven - June 17, 2012, 12:17 p.m.
Hi Steven,

On Sun, Jun 17, 2012 at 2:10 PM, Steven King <sfking@fdwdc.com> wrote:
> On Sunday 17 June 2012 4:50:27 am Geert Uytterhoeven wrote:
>> On Sun, Jun 17, 2012 at 1:40 PM, Steven King <sfking@fdwdc.com> wrote:
>> > On Sunday 17 June 2012 4:15:03 am Geert Uytterhoeven wrote:
>> >> On Sun, Jun 17, 2012 at 11:23 AM, Steven King <sfking@fdwdc.com> wrote:
>> >> > On Sunday 17 June 2012 2:09:51 am Geert Uytterhoeven wrote:
>> >> >> On Sun, Jun 17, 2012 at 9:50 AM, Steven King <sfking@fdwdc.com> wrote:
>> >> >> > +config RTC_DRV_M5441x
>> >> >> > +       tristate "Freescale Coldfire M5441x RTC support"
>> >> >> > +       depends on M5441x
>> >> >> > +       help
>> >> >> > +         This enables support for the RTC on the Freescale
>> >> >> > Coldfire 5441x +         (54410/54415/54416/54417/54418).
>> >> >> > +
>> >> >> > +         This driver can also be built as a module.  If so, the
>> >> >> > module +         will be called rtc-m5441x.
>> >> >>
>> >> >> But the platform device is called  differently:
>> >> >> > +static struct platform_driver m5441x_rtc_driver = {
>> >> >> > +       .driver.name    = "mcfrtc",
>> >> >> > +       .driver.owner   = THIS_MODULE,
>> >> >> > +       .remove         = __devexit_p(m5441x_rtc_remove),
>> >> >> > +};
>> >> >>
>> >> >> Is there a specific reason for that?
>> >> >
>> >> > You mean the "mcfrtc" bit?  Thats the same as what we do for all of
>> >> > the other
>> >>
>> >> That's what I meant.
>> >>
>> >> > Coldfire peripherals, ie "mcfqspi", "mcfuart", etc.
>> >>
>> >> So why not call the driver mcf-rtc?
>> >
>> > Because that rtc implementation is specific to the m5441x; should someone
>> > implement a driver for the rtc on the 532x or 54455 which are somewhat
>> > different than the m5441x, then they might well need a separate rtc-m532x
>> > or rtc-m54455.
>>
>> At that point, you'll have to call the platform device rtc-m532x or
>> rtc-m54455 as well, as mcfrtc is already taken for the m5441x, right?
>
> No.   The others would still use mcfrtc as their device name.  Thats the whole
> point.  The platform code in arch/m68k/platform/coldfire/device.c can have
> single definition of an 'mcfrtc' device and which ever coldfire version is
> selected determines which rtc would be availible.

You cannot have 2 platform drivers with the same name driving different
hardware, as that would cause conflicts if you build a kernel with both drivers.

I see you have "config RTC_DRV_M5441x depends on M5441x" to enforce
this limitation, but in general, it's encouraged to make drivers build
on as many
platforms as possible, as this tends to reveal bugs.

>> So why not call the platform device rtc-m5441x now?
>
> Because that would be wrong.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 08cbdb9..da2b3c4 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1087,4 +1087,14 @@  config RTC_DRV_MXC
 	   This driver can also be built as a module, if so, the module
 	   will be called "rtc-mxc".
 
+config RTC_DRV_M5441x
+	tristate "Freescale Coldfire M5441x RTC support"
+	depends on M5441x
+	help
+	  This enables support for the RTC on the Freescale Coldfire 5441x
+	  (54410/54415/54416/54417/54418).
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called rtc-m5441x.
+
 endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2973921..a9ded2c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -61,6 +61,7 @@  obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o
 obj-$(CONFIG_RTC_DRV_M48T35)	+= rtc-m48t35.o
 obj-$(CONFIG_RTC_DRV_M48T59)	+= rtc-m48t59.o
 obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o
+obj-$(CONFIG_RTC_DRV_M5441x)	+= rtc-m5441x.o
 obj-$(CONFIG_RTC_DRV_MXC)	+= rtc-mxc.o
 obj-$(CONFIG_RTC_DRV_MAX6900)	+= rtc-max6900.o
 obj-$(CONFIG_RTC_DRV_MAX8925)	+= rtc-max8925.o
diff --git a/drivers/rtc/rtc-m5441x.c b/drivers/rtc/rtc-m5441x.c
new file mode 100644
index 0000000..ba677f3
--- /dev/null
+++ b/drivers/rtc/rtc-m5441x.c
@@ -0,0 +1,363 @@ 
+/*
+ * RTC driver for the Freescale Coldfire 5441x SoCs.
+ *
+ * Copyright(C) 2012, Steven King <sfking@fdwdc.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/rtc.h>
+
+static void __iomem *m5441x_rtc_base;
+static int m5441x_rtc_irq;
+static struct clk *m5441x_rtc_clk;
+
+#define M5441X_RTC_YEARMON	(m5441x_rtc_base + 0x00)
+#define M5441X_RTC_DAYS		(m5441x_rtc_base + 0x02)
+#define M5441X_RTC_HOURMIN	(m5441x_rtc_base + 0x04)
+#define M5441X_RTC_SECONDS	(m5441x_rtc_base + 0x06)
+#define M5441X_RTC_ALM_YRMON	(m5441x_rtc_base + 0x08)
+#define M5441X_RTC_ALM_DAYS	(m5441x_rtc_base + 0x0a)
+#define M5441X_RTC_ALM_HM	(m5441x_rtc_base + 0x0c)
+#define M5441X_RTC_ALM_SEC	(m5441x_rtc_base + 0x0e)
+#define M5441X_RTC_CR		(m5441x_rtc_base + 0x10)
+#define M5441X_RTC_CR_AM0	(0 << 2)	/* secs/mins/hrs */
+#define M5441X_RTC_CR_AM1	(1 << 2)	/* secs/mins/days */
+#define M5441X_RTC_CR_AM2	(2 << 2)	/* secs/mins/days/mons */
+#define M5441X_RTC_CR_AM3	(3 << 2)	/* alarm match all */
+#define M5441X_RTC_CR_DTDEN	(1 << 6)	/* DST enable */
+#define M5441X_RTC_CR_BCDEN	(1 << 7)	/* BCD enable */
+#define M5441X_RTC_CR_SWR	(1 << 8)	/* software reset */
+#define M5441X_RTC_SR		(m5441x_rtc_base + 0x12)
+#define M5441X_RTC_SR_INVAL	(1 << 0)	/* time invalid */
+#define M5441X_RTC_SR_CDON	(1 << 1)	/* Compensation done */
+#define M5441X_RTC_SR_BERR	(1 << 2)	/* Bus err */
+#define M5441X_RTC_SR_WPE	(1 << 4)	/* write protect enabled */
+#define M5441X_RTC_SR_PORB	(1 << 6)	/* power on reset boot */
+#define M5441X_RTC_ISR		(m5441x_rtc_base + 0x14)
+#define M5441X_RTC_ISR_STW	(1 << 1)	/* Stop watch */
+#define M5441X_RTC_ISR_ALM	(1 << 2)	/* Alarm */
+#define M5441X_RTC_ISR_DAY	(1 << 3)	/* day counter inc */
+#define M5441X_RTC_ISR_HR	(1 << 4)	/* hour counter inc */
+#define M5441X_RTC_ISR_MIN	(1 << 5)	/* minute counter inc */
+#define M5441X_RTC_ISR_1HZ	(1 << 6)	/* second counter inc */
+#define M5441X_RTC_ISR_2HZ	(1 << 7)	/* 2Hz counter inc */
+#define M5441X_RTC_IER		(m5441x_rtc_base + 0x16)
+#define M5441X_RTC_IER_STW	(1 << 1)	/* Stop watch */
+#define M5441X_RTC_IER_ALM	(1 << 2)	/* Alarm */
+#define M5441X_RTC_IER_DAY	(1 << 3)	/* day counter inc */
+#define M5441X_RTC_IER_HR	(1 << 4)	/* hour counter inc */
+#define M5441X_RTC_IER_MIN	(1 << 5)	/* minute counter inc */
+#define M5441X_RTC_IER_1HZ	(1 << 6)	/* second counter inc */
+#define M5441X_RTC_IER_2HZ	(1 << 7)	/* 2Hz counter inc */
+#define M5441X_RTC_COUNT_DN	(m5441x_rtc_base + 0x18)
+#define M5441X_RTC_CFG_DATA	(m5441x_rtc_base + 0x20)
+#define M5441X_RTC_DST_HOUR	(m5441x_rtc_base + 0x22)
+#define M5441X_RTC_DST_MON	(m5441x_rtc_base + 0x24)
+#define M5441X_RTC_DST_DAY	(m5441x_rtc_base + 0x26)
+#define M5441X_RTC_RTC_COMPEN	(m5441x_rtc_base + 0x28)
+#define M5441X_RTC_UP_CNTRH	(m5441x_rtc_base + 0x32)
+#define M5441X_RTC_UP_CNTRL	(m5441x_rtc_base + 0x24)
+#define M5441X_RTC_Standy_RAM	(m5441x_rtc_base + 0x40)
+
+static int m5441x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	u16 r[4];
+
+	do {
+		r[0] = __raw_readw(M5441X_RTC_SECONDS);
+		r[1] = __raw_readw(M5441X_RTC_HOURMIN);
+		r[2] = __raw_readw(M5441X_RTC_DAYS);
+		r[3] = __raw_readw(M5441X_RTC_YEARMON);
+	} while (r[0] != __raw_readw(M5441X_RTC_SECONDS));
+
+	tm->tm_sec = r[0];
+	tm->tm_min = r[1] & 0xff;
+	tm->tm_hour = r[1] >> 8;
+	tm->tm_mday = (r[2] & 0xff) - 1;
+	tm->tm_wday = r[2] >> 8;
+	tm->tm_mon = (r[3] & 0xff) - 1;
+	tm->tm_year = (((s16)r[3]) >> 8) + 212;
+
+	return rtc_valid_tm(tm);
+}
+
+static int m5441x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	u16 r[4];
+
+	if ((tm->tm_year < 84) || (tm->tm_year > 339))
+		return -EINVAL; /* 1984 <-> 2239 */
+
+	r[0] = tm->tm_sec;
+	r[1] = (tm->tm_hour << 8) | tm->tm_min;
+	r[2] = (tm->tm_wday << 8) | (tm->tm_mday + 1);
+	r[3] = ((tm->tm_year - 212) << 8) | (tm->tm_mon + 1);
+
+	/* disable write protect */
+	__raw_writew(0x0, M5441X_RTC_CR);
+	__raw_writew(0x1, M5441X_RTC_CR);
+	__raw_writew(0x3, M5441X_RTC_CR);
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	__raw_writew(r[0], M5441X_RTC_SECONDS);
+	__raw_writew(r[1], M5441X_RTC_HOURMIN);
+	__raw_writew(r[2], M5441X_RTC_DAYS);
+	__raw_writew(r[3], M5441X_RTC_YEARMON);
+
+	/* enable write protect */
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	return 0;
+}
+
+static int m5441x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	u16 r[4];
+
+	r[0] = __raw_readw(M5441X_RTC_ALM_SEC);
+	r[1] = __raw_readw(M5441X_RTC_ALM_HM);
+	r[2] = __raw_readw(M5441X_RTC_ALM_DAYS);
+	r[3] = __raw_readw(M5441X_RTC_ALM_YRMON);
+
+	alm->time.tm_sec = r[0] & 0xff;
+	alm->time.tm_min = r[1] & 0xff;
+	alm->time.tm_hour = r[1] >> 8;
+	alm->time.tm_mday = (r[2] & 0xff) - 1;
+	alm->time.tm_mon = (r[3] & 0xff) - 1;
+	alm->time.tm_year = (((s16)r[3]) >> 8) + 212;
+
+	alm->enabled = !!(__raw_readw(M5441X_RTC_IER) & M5441X_RTC_IER_ALM);
+
+	return 0;
+}
+
+static int m5441x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	u16 r[4];
+
+	if ((alm->time.tm_year < 84) || (alm->time.tm_year > 339))
+		return -EINVAL;
+
+	r[0] = alm->time.tm_sec;
+	r[1] = (alm->time.tm_hour << 8) | alm->time.tm_min;
+	r[2] = alm->time.tm_mday + 1;
+	r[3] = ((alm->time.tm_year - 212) << 8) | (alm->time.tm_mon + 1);
+
+	local_irq_disable();
+
+	/* disable write protect */
+	__raw_writew(0x0, M5441X_RTC_CR);
+	__raw_writew(0x1, M5441X_RTC_CR);
+	__raw_writew(0x3, M5441X_RTC_CR);
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	__raw_writew(r[0], M5441X_RTC_ALM_SEC);
+	__raw_writew(r[1], M5441X_RTC_ALM_HM);
+	__raw_writew(r[2], M5441X_RTC_ALM_DAYS);
+	__raw_writew(r[3], M5441X_RTC_ALM_YRMON);
+
+	r[0] = __raw_readw(M5441X_RTC_IER);
+	if (alm->enabled)
+		r[0] |= M5441X_RTC_IER_ALM;
+	else
+		r[0] &= ~M5441X_RTC_IER_ALM;
+	__raw_writew(r[0], M5441X_RTC_IER);
+
+	/* enable write protect */
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	local_irq_enable();
+
+	return 0;
+}
+
+static int m5441x_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+	u16 r;
+
+	local_irq_disable();
+
+	/* disable write protect */
+	__raw_writew(0x0, M5441X_RTC_CR);
+	__raw_writew(0x1, M5441X_RTC_CR);
+	__raw_writew(0x3, M5441X_RTC_CR);
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	r = __raw_readw(M5441X_RTC_IER);
+	if (enable)
+		r |= M5441X_RTC_IER_ALM;
+	else
+		r &= ~M5441X_RTC_IER_ALM;
+	__raw_writew(r, M5441X_RTC_IER);
+
+	/* enable write protect */
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	local_irq_enable();
+
+	return 0;
+}
+
+static struct rtc_class_ops m5441x_rtc_ops = {
+	.read_time		= m5441x_rtc_read_time,
+	.set_time		= m5441x_rtc_set_time,
+	.read_alarm		= m5441x_rtc_read_alarm,
+	.set_alarm		= m5441x_rtc_set_alarm,
+	.alarm_irq_enable	= m5441x_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t m5441x_rtc_irq_handler(int irq, void *rtc)
+{
+	unsigned long events = 0;
+	u16 r;
+
+	/* disable write protect */
+	__raw_writew(0x0, M5441X_RTC_CR);
+	__raw_writew(0x1, M5441X_RTC_CR);
+	__raw_writew(0x3, M5441X_RTC_CR);
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	while ((r = __raw_readw(M5441X_RTC_ISR)) != 0) {
+		if (r & M5441X_RTC_ISR_ALM)
+			events |= RTC_IRQF | RTC_AF;
+		if (r & M5441X_RTC_ISR_1HZ)
+			events |= RTC_IRQF | RTC_UF;
+		__raw_writew(r, M5441X_RTC_ISR);
+	}
+
+	/* enable write protect */
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	if (events)
+		rtc_update_irq(rtc, 1, events);
+	return IRQ_HANDLED;
+}
+
+static int __devinit m5441x_rtc_probe(struct platform_device *pdev)
+{
+	struct rtc_device *m5441x_rtc;
+	struct resource *res;
+	int status;
+
+	m5441x_rtc_irq = platform_get_irq(pdev, 0);
+	if (m5441x_rtc_irq < 0) {
+		dev_dbg(&pdev->dev, "platform_get_irq failed\n");
+		return -ENOENT;
+	}
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_dbg(&pdev->dev, "platform_get_resource failed\n");
+		return -ENOENT;
+	}
+	if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
+		dev_dbg(&pdev->dev, "request_mem_region failed\n");
+		return -EBUSY;
+	}
+
+	m5441x_rtc_base = ioremap(res->start, resource_size(res));
+	if (!m5441x_rtc_base) {
+		dev_dbg(&pdev->dev, "ioremap failed\n");
+		status = -ENXIO;
+		goto fail0;
+	}
+
+	m5441x_rtc_clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(m5441x_rtc_clk)) {
+		dev_dbg(&pdev->dev, "clk_get failed\n");
+		status = PTR_ERR(m5441x_rtc_clk);
+		goto fail1;
+	}
+
+	m5441x_rtc = rtc_device_register("mcfrtc", &pdev->dev, &m5441x_rtc_ops,
+			THIS_MODULE);
+	if (IS_ERR(m5441x_rtc)) {
+		dev_dbg(&pdev->dev, "rtc_device_register failed\n");
+		status = PTR_ERR(m5441x_rtc);
+		goto fail2;
+	}
+
+	platform_set_drvdata(pdev, m5441x_rtc);
+
+	if (__raw_readw(M5441X_RTC_SR) & M5441X_RTC_SR_PORB)
+		dev_info(&pdev->dev, "power lost? RTC time maybe invalid\n");
+
+	/* disable write protect */
+	__raw_writew(0x0, M5441X_RTC_CR);
+	__raw_writew(0x1, M5441X_RTC_CR);
+	__raw_writew(0x3, M5441X_RTC_CR);
+	__raw_writew(0x2, M5441X_RTC_CR);
+	/* clear any previous alarms */
+	__raw_writew(M5441X_RTC_CR_SWR, M5441X_RTC_CR);
+	/* alarm match on seconds/mmins/days/months/year */
+	__raw_writew(M5441X_RTC_CR_AM3, M5441X_RTC_CR);
+	/* 1Hz alarm */
+	__raw_writew(M5441X_RTC_IER_1HZ, M5441X_RTC_IER);
+	/* enable write protect */
+	__raw_writew(0x2, M5441X_RTC_CR);
+
+	status = request_irq(m5441x_rtc_irq, m5441x_rtc_irq_handler, 0,
+			pdev->name, m5441x_rtc);
+	if (status) {
+		dev_dbg(&pdev->dev, "request_irq failed\n");
+		goto fail3;
+	}
+	return 0;
+
+fail3:
+	platform_set_drvdata(pdev, NULL);
+	rtc_device_unregister(m5441x_rtc);
+fail2:
+	clk_disable(m5441x_rtc_clk);
+	clk_put(m5441x_rtc_clk);
+fail1:
+	iounmap(m5441x_rtc_base);
+fail0:
+	release_mem_region(res->start, resource_size(res));
+	dev_dbg(&pdev->dev, "probe failed\n");
+
+	return status;
+}
+
+static int __devexit m5441x_rtc_remove(struct platform_device *pdev)
+{
+	struct rtc_device *m5441x_rtc = platform_get_drvdata(pdev);
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	free_irq(m5441x_rtc_irq, m5441x_rtc);
+	platform_set_drvdata(pdev, NULL);
+	rtc_device_unregister(m5441x_rtc);
+	clk_disable(m5441x_rtc_clk);
+	clk_put(m5441x_rtc_clk);
+	iounmap(m5441x_rtc_base);
+	release_mem_region(res->start, resource_size(res));
+
+	return 0;
+}
+
+static struct platform_driver m5441x_rtc_driver = {
+	.driver.name	= "mcfrtc",
+	.driver.owner	= THIS_MODULE,
+	.remove		= __devexit_p(m5441x_rtc_remove),
+};
+
+static int __init m5441x_rtc_init(void)
+{
+	return platform_driver_probe(&m5441x_rtc_driver, m5441x_rtc_probe);
+}
+module_init(m5441x_rtc_init);
+
+static void __exit m5441x_rtc_exit(void)
+{
+	platform_driver_unregister(&m5441x_rtc_driver);
+}
+module_exit(m5441x_rtc_exit);
+
+MODULE_AUTHOR("Steven King <sfking@fdwdc.com>");
+MODULE_DESCRIPTION("M5441x RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rtc");