[PATCH 2/4] drivers/rtc/rtc-armada38x: Add a new RTC driver for recent mvebu SoCs
Gregory CLEMENT
gregory.clement at free-electrons.com
Wed Jan 14 06:36:13 PST 2015
Hi Arnaud,
[...]
>> +struct armada38x_rtc {
>> + struct rtc_device *rtc_dev;
>> + void __iomem *regs;
>> + void __iomem *regs_soc;
>> + spinlock_t lock;
>
> out of curiosity, why use a spinlock and not a mutex?
To be able to use it in the interrupt context.
>
>
>> + int irq;
>> +};
>> +
>> +/*
>> + * According to the datasheet, we need to wait for 5us between each
>> + * write
>> + */
>> +static void rtc_delayed_write(u32 val, struct armada38x_rtc *rtc, int offset)
>> +{
>> + writel(val, rtc->regs + offset);
>> + udelay(5);
>> +}
>
> In the remaining of the driver, you have various direct calls to writel()
> w/o that 5µs protection, to update/modifiy rtc registers. Is that 5µs
> trick specific to a given subpart of the registers? In that case, I
> guess it would help to update the comment.
This direct call were done because there was not a call to write just after.
As explained in the comment the 5us is needed only _between_ two consecutive
write. I can emphasize it needed.
>
>
>> +
>> +static int armada38x_rtc_read_time(struct device *dev, struct rtc_time *tm)
>> +{
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> + unsigned long time, time_check, flags;
>> +
>> + spin_lock_irqsave(&rtc->lock, flags);
>> +
>> + time = readl(rtc->regs + RTC_TIME);
>> + /*
>> + * WA for failing time set attempts. As stated in HW ERRATA if
>> + * more than one second between two time reads is detected
>> + * then read once again.
>> + */
>> + time_check = readl(rtc->regs + RTC_TIME);
>> + if ((time_check - time) > 1)
>> + time_check = readl(rtc->regs + RTC_TIME);
>
> Bear with me; I don't have access to HW ERRATA.
>
> Are you guaranteed to get a valid value at third read if you got a bad
> one before, i.e. no need to put a while loop around that workaround?
I don't have enough information to answer you, maybe the Marvell designer can
answer it. I will ask.
>
> Additionally, the workaround seems to be related to time
> setting. Looking at time setting code below, it seems the issue is also
> created by the fact the writel() call is not under the protection of the
> spinlock.
>
>> +
>> + spin_unlock_irqrestore(&rtc->lock, flags);
>> +
>> + rtc_time_to_tm(time_check, tm);
>> +
>> + return 0;
>
> Does the block provide anyway to detect the oscillator was not running,
> i.e. the value we are reading is not valid?
I didn't see anything related to this, but here again I will ask.
>
>
>> +}
>> +
>> +static int armada38x_rtc_set_time(struct device *dev, struct rtc_time *tm)
>> +{
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> + int ret = 0;
>> + unsigned long time;
>> +
>> + ret = rtc_tm_to_time(tm, &time);
>
>
>> +
>> + if (ret)
>> + goto out;
>> + /*
>> + * Setting the RTC time not always succeeds. According to the
>> + * errata we need to first write on the status register and
>> + * then wait for 1s before writing to the time register to be
>> + * sure that the data will be taken into account.
>> + */
>> + writel(0, rtc->regs + RTC_STATUS);
>> + ssleep(1);
>> +
>> + writel(time, rtc->regs + RTC_TIME);
>
> I think writel(time + 1, ...) would correct the one second shift you
> introduce using you ssleep() above.
I will just move the rtc_tm_to_time-) function here, before the second write.
>
> Additionally, as discussed above, I do not see why you're not protecting
> the write using you spinlock.
Initially the spinlock was around the 2 writel, but you can't have sleep
in a critical section hold by spinlock so I removed it. However it would be
possible to use them around each writel, but I am not sure that it makes sens.
>
>
>> +
>> +out:
>> + return ret;
>> +}
>> +
>> +static int armada38x_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
>> +{
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> + unsigned long time, flags;
>> + u32 val;
>> +
>> + spin_lock_irqsave(&rtc->lock, flags);
>> +
>> + time = readl(rtc->regs + RTC_ALARM1);
>> + val = readl(rtc->regs + RTC_IRQ1_CONF) & RTC_IRQ1_AL_EN;
>> +
>> + spin_unlock_irqrestore(&rtc->lock, flags);
>> +
>> + alrm->enabled = val ? 1 : 0;
>> + rtc_time_to_tm(time, &alrm->time);
>> +
>> + return 0;
>> +}
>> +
>
> In the two functions below, regarding RTC interrupt activation, do you
> need a check that a valid IRQ was passed in the .dts and that
> devm_request_irq() went ok in probe(), i.e. that rtc->irq > 0?
You're right is the irq is not valid, I can even removed these 2 functions
from armada38x_rtc_ops.
>
>> +static int armada38x_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
>> +{
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> + unsigned long time, flags;
>> + int ret = 0;
>> + u32 val;
>> +
>> + ret = rtc_tm_to_time(&alrm->time, &time);
>> +
>> + if (ret)
>> + goto out;
>> +
>> + spin_lock_irqsave(&rtc->lock, flags);
>> +
>> + rtc_delayed_write(time, rtc, RTC_ALARM1);
>> +
>> + if (alrm->enabled) {
>> + rtc_delayed_write(RTC_IRQ1_AL_EN , rtc, RTC_IRQ1_CONF);
>> + val = readl(rtc->regs_soc + SOC_RTC_INTERRUPT);
>> + writel(val | SOC_RTC_ALARM1_MASK,
>> + rtc->regs_soc + SOC_RTC_INTERRUPT);
>> + }
>> +
>> + spin_unlock_irqrestore(&rtc->lock, flags);
>> +
>> +out:
>> + return ret;
>> +}
>> +
>> +static int armada38x_rtc_alarm_irq_enable(struct device *dev,
>> + unsigned int enabled)
>> +{
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&rtc->lock, flags);
>> +
>> + if (enabled)
>> + writel(RTC_IRQ1_AL_EN, rtc->regs + RTC_IRQ1_CONF);
>> + else
>> + writel(0, rtc->regs + RTC_IRQ1_CONF);
>
> I find the following more readable, but it is subjective:
>
> writel(enabled ? RTC_IRQ1_AL_EN : 0, rtc->regs + RTC_IRQ1_CONF);
>
>
>> +
>> + spin_unlock_irqrestore(&rtc->lock, flags);
>> +
>> + return 0;
>> +}
>> +
>> +static irqreturn_t armada38x_rtc_alarm_irq(int irq, void *data)
>> +{
>> + struct armada38x_rtc *rtc = data;
>> + u32 val;
>> + int event = RTC_IRQF | RTC_AF;
>> +
>> + dev_dbg(&rtc->rtc_dev->dev, "%s:irq(%d)\n", __func__, irq);
>> + val = readl(rtc->regs_soc + SOC_RTC_INTERRUPT);
>> +
>> + spin_lock(&rtc->lock);
>> +
>> + writel(val & ~SOC_RTC_ALARM1, rtc->regs_soc + SOC_RTC_INTERRUPT);
>
> Why not putting the readl() above under protection of the spinlock to
> protect a change that could occur between the readl() and the writel()?
Indeed it would be better.
>
>
>> + val = readl(rtc->regs + RTC_IRQ1_CONF);
>> + /* disable all the interrupts for alarm 1 */
>> + rtc_delayed_write(0, rtc, RTC_IRQ1_CONF);
>> + /* Ack the event */
>> + writel(RTC_STATUS_ALARM1, rtc->regs + RTC_STATUS);
>> +
>> + spin_unlock(&rtc->lock);
>> +
>> + if (val & RTC_IRQ1_FREQ_EN) {
>> + if (val & RTC_IRQ1_FREQ_1HZ)
>> + event |= RTC_UF;
>> + else
>> + event |= RTC_PF;
>> + }
>> +
>> + rtc_update_irq(rtc->rtc_dev, 1, event);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static const struct rtc_class_ops armada38x_rtc_ops = {
>> + .read_time = armada38x_rtc_read_time,
>> + .set_time = armada38x_rtc_set_time,
>> + .read_alarm = armada38x_rtc_read_alarm,
>> + .set_alarm = armada38x_rtc_set_alarm,
>> + .alarm_irq_enable = armada38x_rtc_alarm_irq_enable,
>> +};
>> +
>> +static __init int armada38x_rtc_probe(struct platform_device *pdev)
>> +{
>> + struct resource *res;
>> + struct armada38x_rtc *rtc;
>> + int ret;
>> +
>> + rtc = devm_kzalloc(&pdev->dev, sizeof(struct armada38x_rtc),
>> + GFP_KERNEL);
>> + if (!rtc)
>> + return -ENOMEM;
>> +
>> + spin_lock_init(&rtc->lock);
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + rtc->regs = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(rtc->regs))
>> + return PTR_ERR(rtc->regs);
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> + rtc->regs_soc = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(rtc->regs_soc))
>> + return PTR_ERR(rtc->regs_soc);
>> +
>> + rtc->irq = platform_get_irq(pdev, 0);
>> +
>> + if (rtc->irq < 0) {
>> + dev_err(&pdev->dev, "no irq\n");
>> + return rtc->irq;
>> + }
>> + if (devm_request_irq(&pdev->dev, rtc->irq, armada38x_rtc_alarm_irq,
>> + 0, pdev->name, rtc) < 0) {
>> + dev_warn(&pdev->dev, "Interrupt not available.\n");
>> + rtc->irq = -1;
>> + }
>> + platform_set_drvdata(pdev, rtc);
>> +
>> + device_init_wakeup(&pdev->dev, 1);
>
> if devm_request_irq() returns an error, should device_init_wakeup() be
> called? See comment below under armada38x_rtc_suspend().
We should not called it and also set the set_alarm and alarm_irq_enable operation
to NULL.
>
>> + rtc->rtc_dev = devm_rtc_device_register(&pdev->dev, pdev->name,
>> + &armada38x_rtc_ops, THIS_MODULE);
>> + if (IS_ERR(rtc->rtc_dev)) {
>> + ret = PTR_ERR(rtc->rtc_dev);
>> + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret);
>> + if (ret == 0)
>> + ret = -EINVAL;
>
> I may be missing something but I do not see how ret can be 0 above
> because the initial check uses IS_ERR() and not IS_ERR_OR_NULL().
Indeed I can remove this. The devm_rtc_device_register won't return NULL.
>
>
>> + return ret;
>> + }
>> + return 0;
>> +}
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int armada38x_rtc_suspend(struct device *dev)
>> +{
>> + if (device_may_wakeup(dev)) {
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> +
>> + return enable_irq_wake(rtc->irq);
>> + }
>
> AFAICT, rtc->irq maybe -1 in the call above. You need to either call
> device_init_wakeup() when devm_request_irq() succeed or check rtc->irq
> is valid in the "if" above.
I won't call device_init_wakeup() if rtc->irq is -1.
Thanks,
Gregory
>
>> +
>> + return 0;
>> +}
>> +
>> +static int armada38x_rtc_resume(struct device *dev)
>> +{
>> + if (device_may_wakeup(dev)) {
>> + struct armada38x_rtc *rtc = dev_get_drvdata(dev);
>> +
>> + return disable_irq_wake(rtc->irq);
>> + }
>> +
>> + return 0;
>> +}
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(armada38x_rtc_pm_ops,
>> + armada38x_rtc_suspend, armada38x_rtc_resume);
>> +
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id armada38x_rtc_of_match_table[] = {
>> + { .compatible = "marvell,armada-380-rtc", },
>> + {}
>> +};
>> +#endif
>> +
>> +static struct platform_driver armada38x_rtc_driver = {
>> + .driver = {
>> + .name = "armada38x-rtc",
>> + .pm = &armada38x_rtc_pm_ops,
>> + .of_match_table = of_match_ptr(armada38x_rtc_of_match_table),
>> + },
>> +};
>> +
>> +module_platform_driver_probe(armada38x_rtc_driver, armada38x_rtc_probe);
>> +
>> +MODULE_DESCRIPTION("Marvell Armada 38x RTC driver");
>> +MODULE_AUTHOR("Gregory CLEMENT <gregory.clement at free-electrons.com>");
>> +MODULE_LICENSE("GPL");
--
Gregory Clement, Free Electrons
Kernel, drivers, real-time and embedded Linux
development, consulting, training and support.
http://free-electrons.com
More information about the linux-arm-kernel
mailing list