diff options
author | Imre Kaloz <kaloz@openwrt.org> | 2015-04-13 13:06:34 +0000 |
---|---|---|
committer | Imre Kaloz <kaloz@openwrt.org> | 2015-04-13 13:06:34 +0000 |
commit | 8c684f42e574be8a6bac73eaa26cac86edbc18eb (patch) | |
tree | e6c52bcbb6726e37044ca9380683ae5723321738 /target/linux | |
parent | c9e6c7c8364572eac35206627aac4b9d2e551d62 (diff) | |
download | upstream-8c684f42e574be8a6bac73eaa26cac86edbc18eb.tar.gz upstream-8c684f42e574be8a6bac73eaa26cac86edbc18eb.tar.bz2 upstream-8c684f42e574be8a6bac73eaa26cac86edbc18eb.zip |
mvebu: add support for the in-CPU RTC on the Armada 38x
Signed-off-by: Imre Kaloz <kaloz@openwrt.org>
SVN-Revision: 45415
Diffstat (limited to 'target/linux')
-rw-r--r-- | target/linux/mvebu/patches-3.18/600-armada_38x_rtc.patch | 403 |
1 files changed, 403 insertions, 0 deletions
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>; |