404 lines
10 KiB
Diff
404 lines
10 KiB
Diff
|
--- /dev/null
|
||
|
+++ b/Documentation/devicetree/bindings/rtc/armada-380-rtc.txt
|
||
|
@@ -0,0 +1,22 @@
|
||
|
+* Real Time Clock of the Armada 38x SoCs
|
||
|
+
|
||
|
+RTC controller for the Armada 38x SoCs
|
||
|
+
|
||
|
+Required properties:
|
||
|
+- compatible : Should be "marvell,armada-380-rtc"
|
||
|
+- reg: a list of base address and size pairs, one for each entry in
|
||
|
+ reg-names
|
||
|
+- reg names: should contain:
|
||
|
+ * "rtc" for the RTC registers
|
||
|
+ * "rtc-soc" for the SoC related registers and among them the one
|
||
|
+ related to the interrupt.
|
||
|
+- interrupts: IRQ line for the RTC.
|
||
|
+
|
||
|
+Example:
|
||
|
+
|
||
|
+rtc@a3800 {
|
||
|
+ compatible = "marvell,armada-380-rtc";
|
||
|
+ reg = <0xa3800 0x20>, <0x184a0 0x0c>;
|
||
|
+ reg-names = "rtc", "rtc-soc";
|
||
|
+ interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
|
||
|
+};
|
||
|
--- a/drivers/rtc/Kconfig
|
||
|
+++ b/drivers/rtc/Kconfig
|
||
|
@@ -1262,6 +1262,16 @@ config RTC_DRV_MV
|
||
|
This driver can also be built as a module. If so, the module
|
||
|
will be called rtc-mv.
|
||
|
|
||
|
+config RTC_DRV_ARMADA38X
|
||
|
+ tristate "Armada 38x Marvell SoC RTC"
|
||
|
+ depends on ARCH_MVEBU
|
||
|
+ help
|
||
|
+ If you say yes here you will get support for the in-chip RTC
|
||
|
+ that can be found in the Armada 38x Marvell's SoC device
|
||
|
+
|
||
|
+ This driver can also be built as a module. If so, the module
|
||
|
+ will be called armada38x-rtc.
|
||
|
+
|
||
|
config RTC_DRV_PS3
|
||
|
tristate "PS3 RTC"
|
||
|
depends on PPC_PS3
|
||
|
--- a/drivers/rtc/Makefile
|
||
|
+++ b/drivers/rtc/Makefile
|
||
|
@@ -24,6 +24,7 @@ obj-$(CONFIG_RTC_DRV_88PM860X) += rtc-8
|
||
|
obj-$(CONFIG_RTC_DRV_88PM80X) += rtc-88pm80x.o
|
||
|
obj-$(CONFIG_RTC_DRV_AB3100) += rtc-ab3100.o
|
||
|
obj-$(CONFIG_RTC_DRV_AB8500) += rtc-ab8500.o
|
||
|
+obj-$(CONFIG_RTC_DRV_ARMADA38X) += rtc-armada38x.o
|
||
|
obj-$(CONFIG_RTC_DRV_AS3722) += rtc-as3722.o
|
||
|
obj-$(CONFIG_RTC_DRV_AT32AP700X)+= rtc-at32ap700x.o
|
||
|
obj-$(CONFIG_RTC_DRV_AT91RM9200)+= rtc-at91rm9200.o
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/rtc/rtc-armada38x.c
|
||
|
@@ -0,0 +1,320 @@
|
||
|
+/*
|
||
|
+ * RTC driver for the Armada 38x Marvell SoCs
|
||
|
+ *
|
||
|
+ * Copyright (C) 2015 Marvell
|
||
|
+ *
|
||
|
+ * Gregory Clement <gregory.clement@free-electrons.com>
|
||
|
+ *
|
||
|
+ * This program is free software; you can redistribute it and/or
|
||
|
+ * modify it under the terms of the GNU General Public License as
|
||
|
+ * published by the Free Software Foundation; either version 2 of the
|
||
|
+ * License, or (at your option) any later version.
|
||
|
+ *
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/delay.h>
|
||
|
+#include <linux/io.h>
|
||
|
+#include <linux/module.h>
|
||
|
+#include <linux/of.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/rtc.h>
|
||
|
+
|
||
|
+#define RTC_STATUS 0x0
|
||
|
+#define RTC_STATUS_ALARM1 BIT(0)
|
||
|
+#define RTC_STATUS_ALARM2 BIT(1)
|
||
|
+#define RTC_IRQ1_CONF 0x4
|
||
|
+#define RTC_IRQ1_AL_EN BIT(0)
|
||
|
+#define RTC_IRQ1_FREQ_EN BIT(1)
|
||
|
+#define RTC_IRQ1_FREQ_1HZ BIT(2)
|
||
|
+#define RTC_TIME 0xC
|
||
|
+#define RTC_ALARM1 0x10
|
||
|
+
|
||
|
+#define SOC_RTC_INTERRUPT 0x8
|
||
|
+#define SOC_RTC_ALARM1 BIT(0)
|
||
|
+#define SOC_RTC_ALARM2 BIT(1)
|
||
|
+#define SOC_RTC_ALARM1_MASK BIT(2)
|
||
|
+#define SOC_RTC_ALARM2_MASK BIT(3)
|
||
|
+
|
||
|
+struct armada38x_rtc {
|
||
|
+ struct rtc_device *rtc_dev;
|
||
|
+ void __iomem *regs;
|
||
|
+ void __iomem *regs_soc;
|
||
|
+ spinlock_t lock;
|
||
|
+ int irq;
|
||
|
+};
|
||
|
+
|
||
|
+/*
|
||
|
+ * According to the datasheet, the OS should wait 5us after every
|
||
|
+ * register write to the RTC hard macro so that the required update
|
||
|
+ * can occur without holding off the system bus
|
||
|
+ */
|
||
|
+static void rtc_delayed_write(u32 val, struct armada38x_rtc *rtc, int offset)
|
||
|
+{
|
||
|
+ writel(val, rtc->regs + offset);
|
||
|
+ udelay(5);
|
||
|
+}
|
||
|
+
|
||
|
+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);
|
||
|
+
|
||
|
+ spin_unlock_irqrestore(&rtc->lock, flags);
|
||
|
+
|
||
|
+ rtc_time_to_tm(time_check, tm);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+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, flags;
|
||
|
+
|
||
|
+ 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 100ms before writing to the time register to be
|
||
|
+ * sure that the data will be taken into account.
|
||
|
+ */
|
||
|
+ spin_lock_irqsave(&rtc->lock, flags);
|
||
|
+
|
||
|
+ rtc_delayed_write(0, rtc, RTC_STATUS);
|
||
|
+
|
||
|
+ spin_unlock_irqrestore(&rtc->lock, flags);
|
||
|
+
|
||
|
+ msleep(100);
|
||
|
+
|
||
|
+ spin_lock_irqsave(&rtc->lock, flags);
|
||
|
+
|
||
|
+ rtc_delayed_write(time, rtc, RTC_TIME);
|
||
|
+
|
||
|
+ spin_unlock_irqrestore(&rtc->lock, flags);
|
||
|
+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;
|
||
|
+}
|
||
|
+
|
||
|
+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)
|
||
|
+ rtc_delayed_write(RTC_IRQ1_AL_EN, rtc, RTC_IRQ1_CONF);
|
||
|
+ else
|
||
|
+ rtc_delayed_write(0, rtc, 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);
|
||
|
+
|
||
|
+ spin_lock(&rtc->lock);
|
||
|
+
|
||
|
+ val = readl(rtc->regs_soc + SOC_RTC_INTERRUPT);
|
||
|
+
|
||
|
+ writel(val & ~SOC_RTC_ALARM1, rtc->regs_soc + SOC_RTC_INTERRUPT);
|
||
|
+ 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 */
|
||
|
+ rtc_delayed_write(RTC_STATUS_ALARM1, rtc, 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 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_byname(pdev, IORESOURCE_MEM, "rtc");
|
||
|
+ rtc->regs = devm_ioremap_resource(&pdev->dev, res);
|
||
|
+ if (IS_ERR(rtc->regs))
|
||
|
+ return PTR_ERR(rtc->regs);
|
||
|
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rtc-soc");
|
||
|
+ 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;
|
||
|
+ /*
|
||
|
+ * If there is no interrupt available then we can't
|
||
|
+ * use the alarm
|
||
|
+ */
|
||
|
+ armada38x_rtc_ops.set_alarm = NULL;
|
||
|
+ armada38x_rtc_ops.alarm_irq_enable = NULL;
|
||
|
+ }
|
||
|
+ platform_set_drvdata(pdev, rtc);
|
||
|
+ if (rtc->irq != -1)
|
||
|
+ device_init_wakeup(&pdev->dev, 1);
|
||
|
+
|
||
|
+ 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);
|
||
|
+ 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);
|
||
|
+ }
|
||
|
+
|
||
|
+ 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@free-electrons.com>");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
--- a/MAINTAINERS
|
||
|
+++ b/MAINTAINERS
|
||
|
@@ -1136,6 +1136,7 @@ M: Sebastian Hesselbarth <sebastian.hess
|
||
|
L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
|
||
|
S: Maintained
|
||
|
F: arch/arm/mach-mvebu/
|
||
|
+F: drivers/rtc/armada38x-rtc
|
||
|
|
||
|
ARM/Marvell Berlin SoC support
|
||
|
M: Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
|
||
|
--- a/arch/arm/boot/dts/armada-38x.dtsi
|
||
|
+++ b/arch/arm/boot/dts/armada-38x.dtsi
|
||
|
@@ -420,6 +420,13 @@
|
||
|
clocks = <&gateclk 4>;
|
||
|
};
|
||
|
|
||
|
+ rtc@a3800 {
|
||
|
+ compatible = "marvell,armada-380-rtc";
|
||
|
+ reg = <0xa3800 0x20>, <0x184a0 0x0c>;
|
||
|
+ reg-names = "rtc", "rtc-soc";
|
||
|
+ interrupts = <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
|
||
|
+ };
|
||
|
+
|
||
|
sata@a8000 {
|
||
|
compatible = "marvell,armada-380-ahci";
|
||
|
reg = <0xa8000 0x2000>;
|