From 8c684f42e574be8a6bac73eaa26cac86edbc18eb Mon Sep 17 00:00:00 2001
From: Imre Kaloz <kaloz@openwrt.org>
Date: Mon, 13 Apr 2015 13:06:34 +0000
Subject: [PATCH] mvebu: add support for the in-CPU RTC on the Armada 38x

Signed-off-by: Imre Kaloz <kaloz@openwrt.org>

SVN-Revision: 45415
---
 package/kernel/linux/modules/other.mk         |  17 +
 .../patches-3.18/600-armada_38x_rtc.patch     | 403 ++++++++++++++++++
 2 files changed, 420 insertions(+)
 create mode 100644 target/linux/mvebu/patches-3.18/600-armada_38x_rtc.patch

diff --git a/package/kernel/linux/modules/other.mk b/package/kernel/linux/modules/other.mk
index bfdda2c389..a93583bf74 100644
--- a/package/kernel/linux/modules/other.mk
+++ b/package/kernel/linux/modules/other.mk
@@ -573,6 +573,23 @@ endef
 
 $(eval $(call KernelPackage,rtc-marvell))
 
+
+define KernelPackage/rtc-armada38x
+  SUBMENU:=$(OTHER_MENU)
+  TITLE:=Marvell Armada 38x SoC built-in RTC support
+  DEPENDS:=@RTC_SUPPORT @TARGET_mvebu
+  KCONFIG:=CONFIG_RTC_DRV_ARMADA38X
+  FILES:=$(LINUX_DIR)/drivers/rtc/rtc-armada38x.ko
+  AUTOLOAD:=$(call AutoProbe,rtc-armada38x)
+endef
+
+define KernelPackage/rtc-armada38x/description
+ Kernel module for Marvell Armada 38x SoC built-in RTC.
+endef
+
+$(eval $(call KernelPackage,rtc-armada38x))
+
+
 define KernelPackage/rtc-pcf8563
   SUBMENU:=$(OTHER_MENU)
   TITLE:=Philips PCF8563/Epson RTC8564 RTC support
diff --git a/target/linux/mvebu/patches-3.18/600-armada_38x_rtc.patch b/target/linux/mvebu/patches-3.18/600-armada_38x_rtc.patch
new file mode 100644
index 0000000000..399421db27
--- /dev/null
+++ b/target/linux/mvebu/patches-3.18/600-armada_38x_rtc.patch
@@ -0,0 +1,403 @@
+--- /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>;
-- 
2.30.2