121 lines
3.9 KiB
Diff
121 lines
3.9 KiB
Diff
From: Thierry Reding <treding@nvidia.com>
|
|
Date: Thu, 12 Jan 2017 17:07:43 +0100
|
|
Subject: rtc: tegra: Implement clock handling
|
|
Origin: https://git.kernel.org/linus/5fa4086987506b2ab8c92f8f99f2295db9918856
|
|
Bug-Debian: https://bugs.debian.org/858514
|
|
|
|
Accessing the registers of the RTC block on Tegra requires the module
|
|
clock to be enabled. This only works because the RTC module clock will
|
|
be enabled by default during early boot. However, because the clock is
|
|
unused, the CCF will disable it at late_init time. This causes the RTC
|
|
to become unusable afterwards. This can easily be reproduced by trying
|
|
to use the RTC:
|
|
|
|
$ hwclock --rtc /dev/rtc1
|
|
|
|
This will hang the system. I ran into this by following up on a report
|
|
by Martin Michlmayr that reboot wasn't working on Tegra210 systems. It
|
|
turns out that the rtc-tegra driver's ->shutdown() implementation will
|
|
hang the CPU, because of the disabled clock, before the system can be
|
|
rebooted.
|
|
|
|
What confused me for a while is that the same driver is used on prior
|
|
Tegra generations where the hang can not be observed. However, as Peter
|
|
De Schrijver pointed out, this is because on 32-bit Tegra chips the RTC
|
|
clock is enabled by the tegra20_timer.c clocksource driver, which uses
|
|
the RTC to provide a persistent clock. This code is never enabled on
|
|
64-bit Tegra because the persistent clock infrastructure does not exist
|
|
on 64-bit ARM.
|
|
|
|
The proper fix for this is to add proper clock handling to the RTC
|
|
driver in order to ensure that the clock is enabled when the driver
|
|
requires it. All device trees contain the clock already, therefore
|
|
no additional changes are required.
|
|
|
|
Reported-by: Martin Michlmayr <tbm@cyrius.com>
|
|
Acked-By Peter De Schrijver <pdeschrijver@nvidia.com>
|
|
Signed-off-by: Thierry Reding <treding@nvidia.com>
|
|
Signed-off-by: Alexandre Belloni <alexandre.belloni@free-electrons.com>
|
|
[bwh: Backported to 4.9: adjust context]
|
|
---
|
|
drivers/rtc/rtc-tegra.c | 28 ++++++++++++++++++++++++++--
|
|
1 file changed, 26 insertions(+), 2 deletions(-)
|
|
|
|
--- a/drivers/rtc/rtc-tegra.c
|
|
+++ b/drivers/rtc/rtc-tegra.c
|
|
@@ -18,6 +18,7 @@
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include <linux/kernel.h>
|
|
+#include <linux/clk.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
@@ -59,6 +60,7 @@ struct tegra_rtc_info {
|
|
struct platform_device *pdev;
|
|
struct rtc_device *rtc_dev;
|
|
void __iomem *rtc_base; /* NULL if not initialized. */
|
|
+ struct clk *clk;
|
|
int tegra_rtc_irq; /* alarm and periodic irq */
|
|
spinlock_t tegra_rtc_lock;
|
|
};
|
|
@@ -326,6 +328,14 @@ static int __init tegra_rtc_probe(struct
|
|
if (info->tegra_rtc_irq <= 0)
|
|
return -EBUSY;
|
|
|
|
+ info->clk = devm_clk_get(&pdev->dev, NULL);
|
|
+ if (IS_ERR(info->clk))
|
|
+ return PTR_ERR(info->clk);
|
|
+
|
|
+ ret = clk_prepare_enable(info->clk);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
/* set context info. */
|
|
info->pdev = pdev;
|
|
spin_lock_init(&info->tegra_rtc_lock);
|
|
@@ -346,7 +356,7 @@ static int __init tegra_rtc_probe(struct
|
|
ret = PTR_ERR(info->rtc_dev);
|
|
dev_err(&pdev->dev, "Unable to register device (err=%d).\n",
|
|
ret);
|
|
- return ret;
|
|
+ goto disable_clk;
|
|
}
|
|
|
|
ret = devm_request_irq(&pdev->dev, info->tegra_rtc_irq,
|
|
@@ -356,12 +366,25 @@ static int __init tegra_rtc_probe(struct
|
|
dev_err(&pdev->dev,
|
|
"Unable to request interrupt for device (err=%d).\n",
|
|
ret);
|
|
- return ret;
|
|
+ goto disable_clk;
|
|
}
|
|
|
|
dev_notice(&pdev->dev, "Tegra internal Real Time Clock\n");
|
|
|
|
return 0;
|
|
+
|
|
+disable_clk:
|
|
+ clk_disable_unprepare(info->clk);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int tegra_rtc_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct tegra_rtc_info *info = platform_get_drvdata(pdev);
|
|
+
|
|
+ clk_disable_unprepare(info->clk);
|
|
+
|
|
+ return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
@@ -413,6 +436,7 @@ static void tegra_rtc_shutdown(struct pl
|
|
|
|
MODULE_ALIAS("platform:tegra_rtc");
|
|
static struct platform_driver tegra_rtc_driver = {
|
|
+ .remove = tegra_rtc_remove,
|
|
.shutdown = tegra_rtc_shutdown,
|
|
.driver = {
|
|
.name = "tegra_rtc",
|