Message ID | 1241640919-4650-11-git-send-email-wd@denx.de |
---|---|
State | Accepted, archived |
Headers | show |
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(®s->second_set, tm->tm_sec); > + out_8(®s->minute_set, tm->tm_min); > + out_8(®s->hour_set, tm->tm_hour); > + > + /* set time sequence */ > + out_8(®s->set_time, 0x1); > + out_8(®s->set_time, 0x3); > + out_8(®s->set_time, 0x1); > + out_8(®s->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(®s->actual_time) + in_be32(®s->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(®s->target_time, now - in_be32(®s->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(®s->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(®s->alm_min_set, alarm->time.tm_min); > + out_8(®s->alm_hour_set, alarm->time.tm_hour); > + > + out_8(®s->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(®s->int_alm)) { > + /* acknowledge and clear status */ > + out_8(®s->int_alm, 1); > + out_8(®s->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(®s->int_sec) && (in_8(®s->int_enable) & 0x1)) { > + /* acknowledge */ > + out_8(®s->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(®s->alm_enable, 1); > + rtc->wkalarm.enabled = 1; > + break; > + case RTC_AIE_OFF: > + out_8(®s->alm_enable, 0); > + rtc->wkalarm.enabled = 0; > + break; > + > + /* update interrupt */ > + case RTC_UIE_ON: > + out_8(®s->int_enable, > + (in_8(®s->int_enable) & ~0x8) | 0x1); > + break; > + case RTC_UIE_OFF: > + out_8(®s->int_enable, in_8(®s->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(®s->alm_enable, 0); > + out_8(®s->int_enable, in_8(®s->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 > >
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
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.
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. -~----------~----~----~----~------~----~------~--~---
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 >
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 ?
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.
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 --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(®s->second_set, tm->tm_sec); + out_8(®s->minute_set, tm->tm_min); + out_8(®s->hour_set, tm->tm_hour); + + /* set time sequence */ + out_8(®s->set_time, 0x1); + out_8(®s->set_time, 0x3); + out_8(®s->set_time, 0x1); + out_8(®s->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(®s->actual_time) + in_be32(®s->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(®s->target_time, now - in_be32(®s->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(®s->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(®s->alm_min_set, alarm->time.tm_min); + out_8(®s->alm_hour_set, alarm->time.tm_hour); + + out_8(®s->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(®s->int_alm)) { + /* acknowledge and clear status */ + out_8(®s->int_alm, 1); + out_8(®s->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(®s->int_sec) && (in_8(®s->int_enable) & 0x1)) { + /* acknowledge */ + out_8(®s->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(®s->alm_enable, 1); + rtc->wkalarm.enabled = 1; + break; + case RTC_AIE_OFF: + out_8(®s->alm_enable, 0); + rtc->wkalarm.enabled = 0; + break; + + /* update interrupt */ + case RTC_UIE_ON: + out_8(®s->int_enable, + (in_8(®s->int_enable) & ~0x8) | 0x1); + break; + case RTC_UIE_OFF: + out_8(®s->int_enable, in_8(®s->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(®s->alm_enable, 0); + out_8(®s->int_enable, in_8(®s->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);