diff mbox

[10/12] mpc5121: Add MPC5121 Real time clock driver.

Message ID 1241640919-4650-11-git-send-email-wd@denx.de
State Accepted, archived
Headers show

Commit Message

Wolfgang Denk May 6, 2009, 8:15 p.m. UTC
From: John Rigby <jrigby@freescale.com>

Based on Domen Puncer's rtc driver for 5200 posted to
the ppclinux mailing list:
	http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
but never commited anywhere.

Changes to Domen's original:

    Changed filenames/routine names from mpc5200* to mpc5121*
    Changed match to only care about compatible and use "fsl,"
    convention for compatible.

    Make alarms more sane by dealing with lack of second alarm resolution.

    Deal with the fact that most of the 5121 rtc registers are not persistent
    across a reset even with a battery attached:

	Use actual_time register for time keeping
	and target_time register as an offset to linux time

	The target_time register would normally be used for hibernation
	but hibernation does not work on current silicon

Signed-off-by: John Rigby <jrigby@freescale.com>
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com>
Signed-off-by: Wolfgang Denk <wd@denx.de>
Cc: <rtc-linux@googlegroups.com>
Cc: Grant Likely <grant.likely@secretlab.ca>
Cc: John Rigby <jcrigby@gmail.com>
---
 drivers/rtc/Kconfig       |   10 +
 drivers/rtc/Makefile      |    1 +
 drivers/rtc/rtc-mpc5121.c |  408 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 419 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-mpc5121.c

Comments

Grant Likely May 6, 2009, 9:03 p.m. UTC | #1
On Wed, May 6, 2009 at 2:15 PM, Wolfgang Denk <wd@denx.de> wrote:
> From: John Rigby <jrigby@freescale.com>
>
> Based on Domen Puncer's rtc driver for 5200 posted to
> the ppclinux mailing list:
>        http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
> but never commited anywhere.
>
> Changes to Domen's original:
>
>    Changed filenames/routine names from mpc5200* to mpc5121*
>    Changed match to only care about compatible and use "fsl,"
>    convention for compatible.
>
>    Make alarms more sane by dealing with lack of second alarm resolution.
>
>    Deal with the fact that most of the 5121 rtc registers are not persistent
>    across a reset even with a battery attached:
>
>        Use actual_time register for time keeping
>        and target_time register as an offset to linux time
>
>        The target_time register would normally be used for hibernation
>        but hibernation does not work on current silicon
>
> Signed-off-by: John Rigby <jrigby@freescale.com>
> Signed-off-by: Piotr Ziecik <kosmo@semihalf.com>
> Signed-off-by: Wolfgang Denk <wd@denx.de>
> Cc: <rtc-linux@googlegroups.com>
> Cc: Grant Likely <grant.likely@secretlab.ca>
> Cc: John Rigby <jcrigby@gmail.com>

On a *very* cursory review, I don't see anything here I object to.
And it does not look dangerous.

Acked-by: Grant Likely <grant.likely@secretlab.ca>

> ---
>  drivers/rtc/Kconfig       |   10 +
>  drivers/rtc/Makefile      |    1 +
>  drivers/rtc/rtc-mpc5121.c |  408 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 419 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/rtc/rtc-mpc5121.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 4e9851f..900d5b8 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -750,4 +750,14 @@ config RTC_DRV_PS3
>          This driver can also be built as a module. If so, the module
>          will be called rtc-ps3.
>
> +config RTC_DRV_MPC5121
> +       tristate "Freescale MPC5121 built-in RTC"
> +       depends on RTC_CLASS
> +       help
> +         If you say yes here you will get support for the
> +         built-in RTC MPC5121.
> +
> +         This driver can also be built as a module. If so, the module
> +         will be called rtc-mpc5121.
> +
>  endif # RTC_CLASS
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 6c0639a..8c6d6a7 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -51,6 +51,7 @@ obj-$(CONFIG_RTC_DRV_STARFIRE)        += rtc-starfire.o
>  obj-$(CONFIG_RTC_DRV_MAX6900)  += rtc-max6900.o
>  obj-$(CONFIG_RTC_DRV_MAX6902)  += rtc-max6902.o
>  obj-$(CONFIG_RTC_DRV_MV)       += rtc-mv.o
> +obj-$(CONFIG_RTC_DRV_MPC5121)  += rtc-mpc5121.o
>  obj-$(CONFIG_RTC_DRV_OMAP)     += rtc-omap.o
>  obj-$(CONFIG_RTC_DRV_PCF8563)  += rtc-pcf8563.o
>  obj-$(CONFIG_RTC_DRV_PCF8583)  += rtc-pcf8583.o
> diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c
> new file mode 100644
> index 0000000..63460cb
> --- /dev/null
> +++ b/drivers/rtc/rtc-mpc5121.c
> @@ -0,0 +1,408 @@
> +/*
> + * Real-time clock driver for MPC5121
> + *
> + * Copyright 2007, Domen Puncer <domen.puncer@telargo.com>
> + * Copyright 2008, Freescale Semiconductor, Inc. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +/*
> + * History:
> + *
> + * Based on mpc5200_rtc.c written by Domen Puncer <domen.puncer@telargo.com>
> + *   posted to linuxppc-embedded mailing list:
> + *     http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
> + *   but never committed to any public tree.
> + *
> + * Author: John Rigby <jrigby@freescale.com>
> + *   Converted to 5121 rtc driver.
> + *
> + *   Make alarms more sane by dealing with lack of second alarm resolution.
> + *
> + *   Use actual_time time register for time keeping since it is persistent
> + *   and the normal rtc registers are not.  Use target_time register as an
> + *   offset to linux time.
> + *
> + */
> +
> +#include <linux/module.h>
> +#include <linux/rtc.h>
> +#include <linux/of_device.h>
> +#include <linux/of_platform.h>
> +#include <linux/io.h>
> +
> +struct mpc5121_rtc_regs {
> +       u8 set_time;            /* RTC + 0x00 */
> +       u8 hour_set;            /* RTC + 0x01 */
> +       u8 minute_set;          /* RTC + 0x02 */
> +       u8 second_set;          /* RTC + 0x03 */
> +
> +       u8 set_date;            /* RTC + 0x04 */
> +       u8 month_set;           /* RTC + 0x05 */
> +       u8 weekday_set;         /* RTC + 0x06 */
> +       u8 date_set;            /* RTC + 0x07 */
> +
> +       u8 write_sw;            /* RTC + 0x08 */
> +       u8 sw_set;              /* RTC + 0x09 */
> +       u16 year_set;           /* RTC + 0x0a */
> +
> +       u8 alm_enable;          /* RTC + 0x0c */
> +       u8 alm_hour_set;        /* RTC + 0x0d */
> +       u8 alm_min_set;         /* RTC + 0x0e */
> +       u8 int_enable;          /* RTC + 0x0f */
> +
> +       u8 reserved1;
> +       u8 hour;                /* RTC + 0x11 */
> +       u8 minute;              /* RTC + 0x12 */
> +       u8 second;              /* RTC + 0x13 */
> +
> +       u8 month;               /* RTC + 0x14 */
> +       u8 wday_mday;           /* RTC + 0x15 */
> +       u16 year;               /* RTC + 0x16 */
> +
> +       u8 int_alm;             /* RTC + 0x18 */
> +       u8 int_sw;              /* RTC + 0x19 */
> +       u8 alm_status;          /* RTC + 0x1a */
> +       u8 sw_minute;           /* RTC + 0x1b */
> +
> +       u8 bus_error_1;         /* RTC + 0x1c */
> +       u8 int_day;             /* RTC + 0x1d */
> +       u8 int_min;             /* RTC + 0x1e */
> +       u8 int_sec;             /* RTC + 0x1f */
> +
> +       /*
> +        * target_time:
> +        *      intended to be used for hibernation but hibernation
> +        *      does not work on silicon rev 1.5 so use it for non-volatile
> +        *      storage of offset between the actual_time register and linux
> +        *      time
> +        */
> +       u32 target_time;        /* RTC + 0x20 */
> +       /*
> +        * actual_time:
> +        *      readonly time since VBAT_RTC was last connected
> +        */
> +       u32 actual_time;        /* RTC + 0x24 */
> +       u32 keep_alive;         /* RTC + 0x28 */
> +};
> +
> +struct mpc5121_rtc_data {
> +       unsigned irq;
> +       unsigned irq_periodic;
> +       struct mpc5121_rtc_regs __iomem *regs;
> +       struct rtc_device *rtc;
> +       struct rtc_wkalrm wkalarm;
> +};
> +
> +/*
> + * Update second/minute/hour registers.
> + *
> + * This is just so alarm will work.
> + */
> +static void mpc5121_rtc_update_smh(struct mpc5121_rtc_regs __iomem *regs,
> +       struct rtc_time *tm)
> +{
> +       out_8(&regs->second_set, tm->tm_sec);
> +       out_8(&regs->minute_set, tm->tm_min);
> +       out_8(&regs->hour_set, tm->tm_hour);
> +
> +       /* set time sequence */
> +       out_8(&regs->set_time, 0x1);
> +       out_8(&regs->set_time, 0x3);
> +       out_8(&regs->set_time, 0x1);
> +       out_8(&regs->set_time, 0x0);
> +}
> +
> +static int mpc5121_rtc_read_time(struct device *dev, struct rtc_time *tm)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +       unsigned long now;
> +
> +       /*
> +        * linux time is actual_time plus the offset saved in target_time
> +        */
> +       now = in_be32(&regs->actual_time) + in_be32(&regs->target_time);
> +
> +       rtc_time_to_tm(now, tm);
> +
> +       /*
> +        * update second minute hour registers
> +        * so alarms will work
> +        */
> +       mpc5121_rtc_update_smh(regs, tm);
> +
> +       return 0;
> +}
> +
> +static int mpc5121_rtc_set_time(struct device *dev, struct rtc_time *tm)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +       int ret;
> +       unsigned long now;
> +
> +
> +       /*
> +        * The actual_time register is read only so we write the offset
> +        * between it and linux time to the target_time register.
> +        */
> +       ret = rtc_tm_to_time(tm, &now);
> +       if (ret == 0)
> +               out_be32(&regs->target_time, now - in_be32(&regs->actual_time));
> +
> +       /*
> +        * update second minute hour registers
> +        * so alarms will work
> +        */
> +       mpc5121_rtc_update_smh(regs, tm);
> +
> +       return 0;
> +}
> +
> +static int mpc5121_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       *alarm = rtc->wkalarm;
> +
> +       alarm->pending = in_8(&regs->alm_status);
> +
> +       return 0;
> +}
> +
> +static int mpc5121_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       /*
> +        * the alarm has no seconds so deal with it
> +        */
> +       if (alarm->time.tm_sec) {
> +               alarm->time.tm_sec = 0;
> +               alarm->time.tm_min++;
> +               if (alarm->time.tm_min >= 60) {
> +                       alarm->time.tm_min = 0;
> +                       alarm->time.tm_hour++;
> +                       if (alarm->time.tm_hour >= 24)
> +                               alarm->time.tm_hour = 0;
> +               }
> +       }
> +
> +       alarm->time.tm_mday = -1;
> +       alarm->time.tm_mon = -1;
> +       alarm->time.tm_year = -1;
> +
> +       out_8(&regs->alm_min_set, alarm->time.tm_min);
> +       out_8(&regs->alm_hour_set, alarm->time.tm_hour);
> +
> +       out_8(&regs->alm_enable, alarm->enabled);
> +
> +       rtc->wkalarm = *alarm;
> +       return 0;
> +}
> +
> +static irqreturn_t mpc5121_rtc_handler(int irq, void *dev)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       if (in_8(&regs->int_alm)) {
> +               /* acknowledge and clear status */
> +               out_8(&regs->int_alm, 1);
> +               out_8(&regs->alm_status, 1);
> +
> +               rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF);
> +               return IRQ_HANDLED;
> +       }
> +
> +       return IRQ_NONE;
> +}
> +
> +static irqreturn_t mpc5121_rtc_handler_upd(int irq, void *dev)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       if (in_8(&regs->int_sec) && (in_8(&regs->int_enable) & 0x1)) {
> +               /* acknowledge */
> +               out_8(&regs->int_sec, 1);
> +
> +               rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_UF);
> +               return IRQ_HANDLED;
> +       }
> +
> +       return IRQ_NONE;
> +}
> +
> +static int mpc5121_rtc_ioctl(struct device *dev, unsigned int cmd,
> +                                                       unsigned long arg)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       switch (cmd) {
> +               /* alarm interrupt */
> +       case RTC_AIE_ON:
> +               out_8(&regs->alm_enable, 1);
> +               rtc->wkalarm.enabled = 1;
> +               break;
> +       case RTC_AIE_OFF:
> +               out_8(&regs->alm_enable, 0);
> +               rtc->wkalarm.enabled = 0;
> +               break;
> +
> +               /* update interrupt */
> +       case RTC_UIE_ON:
> +               out_8(&regs->int_enable,
> +                               (in_8(&regs->int_enable) & ~0x8) | 0x1);
> +               break;
> +       case RTC_UIE_OFF:
> +               out_8(&regs->int_enable, in_8(&regs->int_enable) & ~0x1);
> +               break;
> +
> +               /* no periodic interrupts */
> +       case RTC_IRQP_READ:
> +       case RTC_IRQP_SET:
> +               return -ENOTTY;
> +
> +       default:
> +               return -ENOIOCTLCMD;
> +       }
> +       return 0;
> +}
> +
> +static const struct rtc_class_ops mpc5121_rtc_ops = {
> +       .read_time = mpc5121_rtc_read_time,
> +       .set_time = mpc5121_rtc_set_time,
> +       .read_alarm = mpc5121_rtc_read_alarm,
> +       .set_alarm = mpc5121_rtc_set_alarm,
> +       .ioctl = mpc5121_rtc_ioctl,
> +};
> +
> +static int __devinit mpc5121_rtc_probe(struct of_device *op,
> +                                       const struct of_device_id *match)
> +{
> +       struct mpc5121_rtc_data *rtc;
> +       int err = 0;
> +       u32 ka;
> +
> +       rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
> +       if (!rtc) {
> +               err = -ENOMEM;
> +               goto out;
> +       }
> +
> +       rtc->regs = of_iomap(op->node, 0);
> +
> +       if (!rtc->regs) {
> +               printk(KERN_ERR "%s: couldn't map io space\n", __func__);
> +               err = -ENOSYS;
> +               goto out_free;
> +       }
> +
> +       device_init_wakeup(&op->dev, 1);
> +
> +       rtc->rtc = rtc_device_register("mpc5121-rtc", &op->dev,
> +                                               &mpc5121_rtc_ops, THIS_MODULE);
> +       if (IS_ERR(rtc->rtc)) {
> +               err = PTR_ERR(rtc->rtc);
> +               goto out_unmap;
> +       }
> +
> +       dev_set_drvdata(&op->dev, rtc);
> +
> +       rtc->irq = irq_of_parse_and_map(op->node, 1);
> +       err = request_irq(rtc->irq, mpc5121_rtc_handler, IRQF_DISABLED,
> +                                               "mpc5121-rtc", &op->dev);
> +       if (err) {
> +               printk(KERN_ERR "%s: could not request irq: %i\n",
> +                                                       __func__, rtc->irq);
> +               goto out_dispose;
> +       }
> +
> +       rtc->irq_periodic = irq_of_parse_and_map(op->node, 0);
> +       err = request_irq(rtc->irq_periodic, mpc5121_rtc_handler_upd,
> +                                IRQF_DISABLED, "mpc5121-rtc_upd", &op->dev);
> +       if (err) {
> +               printk(KERN_ERR "%s: could not request irq: %i\n",
> +                                               __func__, rtc->irq_periodic);
> +               goto out_dispose2;
> +       }
> +
> +       ka = in_be32(&rtc->regs->keep_alive);
> +       if (ka & 0x02) {
> +               printk(KERN_WARNING
> +                       "mpc5121-rtc: Battery or oscillator failure!\n");
> +               out_be32(&rtc->regs->keep_alive, ka);
> +       }
> +
> +       goto out;
> +
> +out_dispose2:
> +       irq_dispose_mapping(rtc->irq_periodic);
> +       free_irq(rtc->irq, &op->dev);
> +out_dispose:
> +       irq_dispose_mapping(rtc->irq);
> +out_unmap:
> +       iounmap(rtc->regs);
> +out_free:
> +       kfree(rtc);
> +out:
> +       return err;
> +}
> +
> +static int __devexit mpc5121_rtc_remove(struct of_device *op)
> +{
> +       struct mpc5121_rtc_data *rtc = dev_get_drvdata(&op->dev);
> +       struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
> +
> +       /* disable interrupt, so there are no nasty surprises */
> +       out_8(&regs->alm_enable, 0);
> +       out_8(&regs->int_enable, in_8(&regs->int_enable) & ~0x1);
> +
> +       rtc_device_unregister(rtc->rtc);
> +       iounmap(rtc->regs);
> +       free_irq(rtc->irq, &op->dev);
> +       free_irq(rtc->irq_periodic, &op->dev);
> +       irq_dispose_mapping(rtc->irq);
> +       irq_dispose_mapping(rtc->irq_periodic);
> +       dev_set_drvdata(&op->dev, NULL);
> +       kfree(rtc);
> +
> +       return 0;
> +}
> +
> +static struct of_device_id mpc5121_rtc_match[] = {
> +       { .compatible = "fsl,mpc5121-rtc", },
> +       {},
> +};
> +
> +static struct of_platform_driver mpc5121_rtc_driver = {
> +       .owner = THIS_MODULE,
> +       .name = "mpc5121-rtc",
> +       .match_table = mpc5121_rtc_match,
> +       .probe = mpc5121_rtc_probe,
> +       .remove = mpc5121_rtc_remove,
> +};
> +
> +static int __init mpc5121_rtc_init(void)
> +{
> +       return of_register_platform_driver(&mpc5121_rtc_driver);
> +}
> +
> +static void __exit mpc5121_rtc_exit(void)
> +{
> +       of_unregister_platform_driver(&mpc5121_rtc_driver);
> +}
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("John Rigby <jrigby@freescale.com>");
> +
> +module_init(mpc5121_rtc_init);
> +module_exit(mpc5121_rtc_exit);
> --
> 1.6.0.6
>
>
Wolfram Sang May 6, 2009, 9:06 p.m. UTC | #2
On Wed, May 06, 2009 at 10:15:17PM +0200, Wolfgang Denk wrote:
> From: John Rigby <jrigby@freescale.com>
> 
> Based on Domen Puncer's rtc driver for 5200 posted to
> the ppclinux mailing list:
> 	http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
> but never commited anywhere.
> 
> Changes to Domen's original:
> 
>     Changed filenames/routine names from mpc5200* to mpc5121*

Why not changing it to mpc5xxx? From a glimpse, it should still work on
MPC5200, too.

Regards,

   Wolfram
Grant Likely May 6, 2009, 10:40 p.m. UTC | #3
On Wed, May 6, 2009 at 3:06 PM, Wolfram Sang <w.sang@pengutronix.de> wrote:
> On Wed, May 06, 2009 at 10:15:17PM +0200, Wolfgang Denk wrote:
>> From: John Rigby <jrigby@freescale.com>
>>
>> Based on Domen Puncer's rtc driver for 5200 posted to
>> the ppclinux mailing list:
>>       http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
>> but never commited anywhere.
>>
>> Changes to Domen's original:
>>
>>     Changed filenames/routine names from mpc5200* to mpc5121*
>
> Why not changing it to mpc5xxx? From a glimpse, it should still work on
> MPC5200, too.

If this is true, the I heartily agree with Wolfram.  :-)

g.
John Rigby May 8, 2009, 2:41 a.m. UTC | #4
Can we get 5121 support in and add 5200 support later?  They are not
identical.

On Wed, May 6, 2009 at 4:40 PM, Grant Likely <grant.likely@secretlab.ca>wrote:

>
> On Wed, May 6, 2009 at 3:06 PM, Wolfram Sang <w.sang@pengutronix.de>
> wrote:
> > On Wed, May 06, 2009 at 10:15:17PM +0200, Wolfgang Denk wrote:
> >> From: John Rigby <jrigby@freescale.com>
> >>
> >> Based on Domen Puncer's rtc driver for 5200 posted to
> >> the ppclinux mailing list:
> >>       http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
> >> but never commited anywhere.
> >>
> >> Changes to Domen's original:
> >>
> >>     Changed filenames/routine names from mpc5200* to mpc5121*
> >
> > Why not changing it to mpc5xxx? From a glimpse, it should still work on
> > MPC5200, too.
>
> If this is true, the I heartily agree with Wolfram.  :-)
>
> g.
>
> --
> Grant Likely, B.Sc., P.Eng.
> Secret Lab Technologies Ltd.
>
> >
>

--~--~---------~--~----~------------~-------~--~----~
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.
-~----------~----~----~----~------~----~------~--~---
Grant Likely May 8, 2009, 3:53 p.m. UTC | #5
On Thu, May 7, 2009 at 8:41 PM, John Rigby <jcrigby@gmail.com> wrote:
> Can we get 5121 support in and add 5200 support later?  They are not
> identical.

That is fine by me, but since it is known that it will eventually
support both, I'd like it to be renamed to 5xxx before it is merged to
avoid a later rename patch.

g.

> On Wed, May 6, 2009 at 4:40 PM, Grant Likely <grant.likely@secretlab.ca>
> wrote:
>>
>> On Wed, May 6, 2009 at 3:06 PM, Wolfram Sang <w.sang@pengutronix.de>
>> wrote:
>> > On Wed, May 06, 2009 at 10:15:17PM +0200, Wolfgang Denk wrote:
>> >> From: John Rigby <jrigby@freescale.com>
>> >>
>> >> Based on Domen Puncer's rtc driver for 5200 posted to
>> >> the ppclinux mailing list:
>> >>       http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
>> >> but never commited anywhere.
>> >>
>> >> Changes to Domen's original:
>> >>
>> >>     Changed filenames/routine names from mpc5200* to mpc5121*
>> >
>> > Why not changing it to mpc5xxx? From a glimpse, it should still work on
>> > MPC5200, too.
>>
>> If this is true, the I heartily agree with Wolfram.  :-)
>>
>> g.
>>
>> --
>> Grant Likely, B.Sc., P.Eng.
>> Secret Lab Technologies Ltd.
>>
>> >>
>
>
> _______________________________________________
> Linuxppc-dev mailing list
> Linuxppc-dev@ozlabs.org
> https://ozlabs.org/mailman/listinfo/linuxppc-dev
>
Alessandro Zummo May 8, 2009, 4:09 p.m. UTC | #6
On Fri, 8 May 2009 09:53:20 -0600
Grant Likely <grant.likely@secretlab.ca> wrote:

> That is fine by me, but since it is known that it will eventually
> support both, I'd like it to be renamed to 5xxx before it is merged to
> avoid a later rename patch.

 or 52xx ?
Alessandro Zummo May 8, 2009, 4:10 p.m. UTC | #7
On Fri, 8 May 2009 09:53:20 -0600
Grant Likely <grant.likely@secretlab.ca> wrote:

> That is fine by me, but since it is known that it will eventually
> support both, I'd like it to be renamed to 5xxx before it is merged to
> avoid a later rename patch.

 sorry, forget my last email, I misread the codes.
Wolfgang Denk May 8, 2009, 7:18 p.m. UTC | #8
Dear Alessandro Zummo,

In message <20090508180944.1304a8b7@i1501.lan.towertech.it> you wrote:
> On Fri, 8 May 2009 09:53:20 -0600
> Grant Likely <grant.likely@secretlab.ca> wrote:
> 
> > That is fine by me, but since it is known that it will eventually
> > support both, I'd like it to be renamed to 5xxx before it is merged to
> > avoid a later rename patch.
> 
>  or 52xx ?

No, because the current version is for MPC5>1<2x

Best regards,

Wolfgang Denk
diff mbox

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4e9851f..900d5b8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -750,4 +750,14 @@  config RTC_DRV_PS3
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-ps3.
 
+config RTC_DRV_MPC5121
+	tristate "Freescale MPC5121 built-in RTC"
+	depends on RTC_CLASS
+	help
+	  If you say yes here you will get support for the
+	  built-in RTC MPC5121.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-mpc5121.
+
 endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6c0639a..8c6d6a7 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -51,6 +51,7 @@  obj-$(CONFIG_RTC_DRV_STARFIRE)	+= rtc-starfire.o
 obj-$(CONFIG_RTC_DRV_MAX6900)	+= rtc-max6900.o
 obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
 obj-$(CONFIG_RTC_DRV_MV)	+= rtc-mv.o
+obj-$(CONFIG_RTC_DRV_MPC5121)	+= rtc-mpc5121.o
 obj-$(CONFIG_RTC_DRV_OMAP)	+= rtc-omap.o
 obj-$(CONFIG_RTC_DRV_PCF8563)	+= rtc-pcf8563.o
 obj-$(CONFIG_RTC_DRV_PCF8583)	+= rtc-pcf8583.o
diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c
new file mode 100644
index 0000000..63460cb
--- /dev/null
+++ b/drivers/rtc/rtc-mpc5121.c
@@ -0,0 +1,408 @@ 
+/*
+ * Real-time clock driver for MPC5121
+ *
+ * Copyright 2007, Domen Puncer <domen.puncer@telargo.com>
+ * Copyright 2008, Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/*
+ * History:
+ *
+ * Based on mpc5200_rtc.c written by Domen Puncer <domen.puncer@telargo.com>
+ *   posted to linuxppc-embedded mailing list:
+ *     http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
+ *   but never committed to any public tree.
+ *
+ * Author: John Rigby <jrigby@freescale.com>
+ *   Converted to 5121 rtc driver.
+ *
+ *   Make alarms more sane by dealing with lack of second alarm resolution.
+ *
+ *   Use actual_time time register for time keeping since it is persistent
+ *   and the normal rtc registers are not.  Use target_time register as an
+ *   offset to linux time.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/io.h>
+
+struct mpc5121_rtc_regs {
+	u8 set_time;		/* RTC + 0x00 */
+	u8 hour_set;		/* RTC + 0x01 */
+	u8 minute_set;		/* RTC + 0x02 */
+	u8 second_set;		/* RTC + 0x03 */
+
+	u8 set_date;		/* RTC + 0x04 */
+	u8 month_set;		/* RTC + 0x05 */
+	u8 weekday_set;		/* RTC + 0x06 */
+	u8 date_set;		/* RTC + 0x07 */
+
+	u8 write_sw;		/* RTC + 0x08 */
+	u8 sw_set;		/* RTC + 0x09 */
+	u16 year_set;		/* RTC + 0x0a */
+
+	u8 alm_enable;		/* RTC + 0x0c */
+	u8 alm_hour_set;	/* RTC + 0x0d */
+	u8 alm_min_set;		/* RTC + 0x0e */
+	u8 int_enable;		/* RTC + 0x0f */
+
+	u8 reserved1;
+	u8 hour;		/* RTC + 0x11 */
+	u8 minute;		/* RTC + 0x12 */
+	u8 second;		/* RTC + 0x13 */
+
+	u8 month;		/* RTC + 0x14 */
+	u8 wday_mday;		/* RTC + 0x15 */
+	u16 year;		/* RTC + 0x16 */
+
+	u8 int_alm;		/* RTC + 0x18 */
+	u8 int_sw;		/* RTC + 0x19 */
+	u8 alm_status;		/* RTC + 0x1a */
+	u8 sw_minute;		/* RTC + 0x1b */
+
+	u8 bus_error_1;		/* RTC + 0x1c */
+	u8 int_day;		/* RTC + 0x1d */
+	u8 int_min;		/* RTC + 0x1e */
+	u8 int_sec;		/* RTC + 0x1f */
+
+	/*
+	 * target_time:
+	 *	intended to be used for hibernation but hibernation
+	 *	does not work on silicon rev 1.5 so use it for non-volatile
+	 *	storage of offset between the actual_time register and linux
+	 *	time
+	 */
+	u32 target_time;	/* RTC + 0x20 */
+	/*
+	 * actual_time:
+	 * 	readonly time since VBAT_RTC was last connected
+	 */
+	u32 actual_time;	/* RTC + 0x24 */
+	u32 keep_alive;		/* RTC + 0x28 */
+};
+
+struct mpc5121_rtc_data {
+	unsigned irq;
+	unsigned irq_periodic;
+	struct mpc5121_rtc_regs __iomem *regs;
+	struct rtc_device *rtc;
+	struct rtc_wkalrm wkalarm;
+};
+
+/*
+ * Update second/minute/hour registers.
+ *
+ * This is just so alarm will work.
+ */
+static void mpc5121_rtc_update_smh(struct mpc5121_rtc_regs __iomem *regs,
+	struct rtc_time *tm)
+{
+	out_8(&regs->second_set, tm->tm_sec);
+	out_8(&regs->minute_set, tm->tm_min);
+	out_8(&regs->hour_set, tm->tm_hour);
+
+	/* set time sequence */
+	out_8(&regs->set_time, 0x1);
+	out_8(&regs->set_time, 0x3);
+	out_8(&regs->set_time, 0x1);
+	out_8(&regs->set_time, 0x0);
+}
+
+static int mpc5121_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+	unsigned long now;
+
+	/*
+	 * linux time is actual_time plus the offset saved in target_time
+	 */
+	now = in_be32(&regs->actual_time) + in_be32(&regs->target_time);
+
+	rtc_time_to_tm(now, tm);
+
+	/*
+	 * update second minute hour registers
+	 * so alarms will work
+	 */
+	mpc5121_rtc_update_smh(regs, tm);
+
+	return 0;
+}
+
+static int mpc5121_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+	int ret;
+	unsigned long now;
+
+
+	/*
+	 * The actual_time register is read only so we write the offset
+	 * between it and linux time to the target_time register.
+	 */
+	ret = rtc_tm_to_time(tm, &now);
+	if (ret == 0)
+		out_be32(&regs->target_time, now - in_be32(&regs->actual_time));
+
+	/*
+	 * update second minute hour registers
+	 * so alarms will work
+	 */
+	mpc5121_rtc_update_smh(regs, tm);
+
+	return 0;
+}
+
+static int mpc5121_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	*alarm = rtc->wkalarm;
+
+	alarm->pending = in_8(&regs->alm_status);
+
+	return 0;
+}
+
+static int mpc5121_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	/*
+	 * the alarm has no seconds so deal with it
+	 */
+	if (alarm->time.tm_sec) {
+		alarm->time.tm_sec = 0;
+		alarm->time.tm_min++;
+		if (alarm->time.tm_min >= 60) {
+			alarm->time.tm_min = 0;
+			alarm->time.tm_hour++;
+			if (alarm->time.tm_hour >= 24)
+				alarm->time.tm_hour = 0;
+		}
+	}
+
+	alarm->time.tm_mday = -1;
+	alarm->time.tm_mon = -1;
+	alarm->time.tm_year = -1;
+
+	out_8(&regs->alm_min_set, alarm->time.tm_min);
+	out_8(&regs->alm_hour_set, alarm->time.tm_hour);
+
+	out_8(&regs->alm_enable, alarm->enabled);
+
+	rtc->wkalarm = *alarm;
+	return 0;
+}
+
+static irqreturn_t mpc5121_rtc_handler(int irq, void *dev)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	if (in_8(&regs->int_alm)) {
+		/* acknowledge and clear status */
+		out_8(&regs->int_alm, 1);
+		out_8(&regs->alm_status, 1);
+
+		rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static irqreturn_t mpc5121_rtc_handler_upd(int irq, void *dev)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	if (in_8(&regs->int_sec) && (in_8(&regs->int_enable) & 0x1)) {
+		/* acknowledge */
+		out_8(&regs->int_sec, 1);
+
+		rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_UF);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int mpc5121_rtc_ioctl(struct device *dev, unsigned int cmd,
+							unsigned long arg)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	switch (cmd) {
+		/* alarm interrupt */
+	case RTC_AIE_ON:
+		out_8(&regs->alm_enable, 1);
+		rtc->wkalarm.enabled = 1;
+		break;
+	case RTC_AIE_OFF:
+		out_8(&regs->alm_enable, 0);
+		rtc->wkalarm.enabled = 0;
+		break;
+
+		/* update interrupt */
+	case RTC_UIE_ON:
+		out_8(&regs->int_enable,
+				(in_8(&regs->int_enable) & ~0x8) | 0x1);
+		break;
+	case RTC_UIE_OFF:
+		out_8(&regs->int_enable, in_8(&regs->int_enable) & ~0x1);
+		break;
+
+		/* no periodic interrupts */
+	case RTC_IRQP_READ:
+	case RTC_IRQP_SET:
+		return -ENOTTY;
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static const struct rtc_class_ops mpc5121_rtc_ops = {
+	.read_time = mpc5121_rtc_read_time,
+	.set_time = mpc5121_rtc_set_time,
+	.read_alarm = mpc5121_rtc_read_alarm,
+	.set_alarm = mpc5121_rtc_set_alarm,
+	.ioctl = mpc5121_rtc_ioctl,
+};
+
+static int __devinit mpc5121_rtc_probe(struct of_device *op,
+					const struct of_device_id *match)
+{
+	struct mpc5121_rtc_data *rtc;
+	int err = 0;
+	u32 ka;
+
+	rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+	if (!rtc) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	rtc->regs = of_iomap(op->node, 0);
+
+	if (!rtc->regs) {
+		printk(KERN_ERR "%s: couldn't map io space\n", __func__);
+		err = -ENOSYS;
+		goto out_free;
+	}
+
+	device_init_wakeup(&op->dev, 1);
+
+	rtc->rtc = rtc_device_register("mpc5121-rtc", &op->dev,
+						&mpc5121_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc->rtc)) {
+		err = PTR_ERR(rtc->rtc);
+		goto out_unmap;
+	}
+
+	dev_set_drvdata(&op->dev, rtc);
+
+	rtc->irq = irq_of_parse_and_map(op->node, 1);
+	err = request_irq(rtc->irq, mpc5121_rtc_handler, IRQF_DISABLED,
+						"mpc5121-rtc", &op->dev);
+	if (err) {
+		printk(KERN_ERR "%s: could not request irq: %i\n",
+							__func__, rtc->irq);
+		goto out_dispose;
+	}
+
+	rtc->irq_periodic = irq_of_parse_and_map(op->node, 0);
+	err = request_irq(rtc->irq_periodic, mpc5121_rtc_handler_upd,
+				 IRQF_DISABLED, "mpc5121-rtc_upd", &op->dev);
+	if (err) {
+		printk(KERN_ERR "%s: could not request irq: %i\n",
+						__func__, rtc->irq_periodic);
+		goto out_dispose2;
+	}
+
+	ka = in_be32(&rtc->regs->keep_alive);
+	if (ka & 0x02) {
+		printk(KERN_WARNING
+			"mpc5121-rtc: Battery or oscillator failure!\n");
+		out_be32(&rtc->regs->keep_alive, ka);
+	}
+
+	goto out;
+
+out_dispose2:
+	irq_dispose_mapping(rtc->irq_periodic);
+	free_irq(rtc->irq, &op->dev);
+out_dispose:
+	irq_dispose_mapping(rtc->irq);
+out_unmap:
+	iounmap(rtc->regs);
+out_free:
+	kfree(rtc);
+out:
+	return err;
+}
+
+static int __devexit mpc5121_rtc_remove(struct of_device *op)
+{
+	struct mpc5121_rtc_data *rtc = dev_get_drvdata(&op->dev);
+	struct mpc5121_rtc_regs __iomem *regs = rtc->regs;
+
+	/* disable interrupt, so there are no nasty surprises */
+	out_8(&regs->alm_enable, 0);
+	out_8(&regs->int_enable, in_8(&regs->int_enable) & ~0x1);
+
+	rtc_device_unregister(rtc->rtc);
+	iounmap(rtc->regs);
+	free_irq(rtc->irq, &op->dev);
+	free_irq(rtc->irq_periodic, &op->dev);
+	irq_dispose_mapping(rtc->irq);
+	irq_dispose_mapping(rtc->irq_periodic);
+	dev_set_drvdata(&op->dev, NULL);
+	kfree(rtc);
+
+	return 0;
+}
+
+static struct of_device_id mpc5121_rtc_match[] = {
+	{ .compatible = "fsl,mpc5121-rtc", },
+	{},
+};
+
+static struct of_platform_driver mpc5121_rtc_driver = {
+	.owner = THIS_MODULE,
+	.name = "mpc5121-rtc",
+	.match_table = mpc5121_rtc_match,
+	.probe = mpc5121_rtc_probe,
+	.remove = mpc5121_rtc_remove,
+};
+
+static int __init mpc5121_rtc_init(void)
+{
+	return of_register_platform_driver(&mpc5121_rtc_driver);
+}
+
+static void __exit mpc5121_rtc_exit(void)
+{
+	of_unregister_platform_driver(&mpc5121_rtc_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("John Rigby <jrigby@freescale.com>");
+
+module_init(mpc5121_rtc_init);
+module_exit(mpc5121_rtc_exit);