| From 1bfaa614ab08df8ec4ccd52b41aa2651769d3984 Mon Sep 17 00:00:00 2001 |
| From: Thierry Reding <treding@nvidia.com> |
| Date: Thu, 12 Jan 2017 17:07:43 +0100 |
| Subject: [PATCH] rtc: tegra: Implement clock handling |
| |
| commit 5fa4086987506b2ab8c92f8f99f2295db9918856 upstream. |
| |
| 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> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c |
| index 15ac597d54da..c24886770181 100644 |
| --- a/drivers/rtc/rtc-tegra.c |
| +++ b/drivers/rtc/rtc-tegra.c |
| @@ -17,6 +17,7 @@ |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| */ |
| +#include <linux/clk.h> |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/module.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 platform_device *pdev) |
| 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 platform_device *pdev) |
| 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 platform_device *pdev) |
| 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 platform_device *pdev) |
| |
| MODULE_ALIAS("platform:tegra_rtc"); |
| static struct platform_driver tegra_rtc_driver = { |
| + .remove = tegra_rtc_remove, |
| .shutdown = tegra_rtc_shutdown, |
| .driver = { |
| .name = "tegra_rtc", |
| -- |
| 2.12.0 |
| |