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

Grant Likely grant.likely at secretlab.ca
Thu May 7 07:03:56 EST 2009


On Wed, May 6, 2009 at 2:15 PM, Wolfgang Denk <wd at denx.de> wrote:
> From: John Rigby <jrigby at 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 at freescale.com>
> Signed-off-by: Piotr Ziecik <kosmo at semihalf.com>
> Signed-off-by: Wolfgang Denk <wd at denx.de>
> Cc: <rtc-linux at googlegroups.com>
> Cc: Grant Likely <grant.likely at secretlab.ca>
> Cc: John Rigby <jcrigby at 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 at 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 at 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 at 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 at 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 at freescale.com>");
> +
> +module_init(mpc5121_rtc_init);
> +module_exit(mpc5121_rtc_exit);
> --
> 1.6.0.6
>
>



-- 
Grant Likely, B.Sc., P.Eng.
Secret Lab Technologies Ltd.



More information about the Linuxppc-dev mailing list