aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThierry Reding <treding@nvidia.com>2017-01-12 11:07:43 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-04-21 03:31:24 -0400
commitef793e6e113473f7cb08edf1ca4a8737c57ce51c (patch)
treec2c66db2a3fa18784f0315c243a0553cc3e15a2c
parent0dd962118a201a19d8c5c01e4909bfc07c288d6c (diff)
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> [bwh: Backported to 4.9: adjust context] Signed-off-by: Ben Hutchings <ben@decadent.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--drivers/rtc/rtc-tegra.c28
1 files changed, 26 insertions, 2 deletions
diff --git a/drivers/rtc/rtc-tegra.c b/drivers/rtc/rtc-tegra.c
index 3853ba963bb5..19e03d0b956b 100644
--- a/drivers/rtc/rtc-tegra.c
+++ b/drivers/rtc/rtc-tegra.c
@@ -18,6 +18,7 @@
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 */ 19 */
20#include <linux/kernel.h> 20#include <linux/kernel.h>
21#include <linux/clk.h>
21#include <linux/init.h> 22#include <linux/init.h>
22#include <linux/module.h> 23#include <linux/module.h>
23#include <linux/slab.h> 24#include <linux/slab.h>
@@ -59,6 +60,7 @@ struct tegra_rtc_info {
59 struct platform_device *pdev; 60 struct platform_device *pdev;
60 struct rtc_device *rtc_dev; 61 struct rtc_device *rtc_dev;
61 void __iomem *rtc_base; /* NULL if not initialized. */ 62 void __iomem *rtc_base; /* NULL if not initialized. */
63 struct clk *clk;
62 int tegra_rtc_irq; /* alarm and periodic irq */ 64 int tegra_rtc_irq; /* alarm and periodic irq */
63 spinlock_t tegra_rtc_lock; 65 spinlock_t tegra_rtc_lock;
64}; 66};
@@ -326,6 +328,14 @@ static int __init tegra_rtc_probe(struct platform_device *pdev)
326 if (info->tegra_rtc_irq <= 0) 328 if (info->tegra_rtc_irq <= 0)
327 return -EBUSY; 329 return -EBUSY;
328 330
331 info->clk = devm_clk_get(&pdev->dev, NULL);
332 if (IS_ERR(info->clk))
333 return PTR_ERR(info->clk);
334
335 ret = clk_prepare_enable(info->clk);
336 if (ret < 0)
337 return ret;
338
329 /* set context info. */ 339 /* set context info. */
330 info->pdev = pdev; 340 info->pdev = pdev;
331 spin_lock_init(&info->tegra_rtc_lock); 341 spin_lock_init(&info->tegra_rtc_lock);
@@ -346,7 +356,7 @@ static int __init tegra_rtc_probe(struct platform_device *pdev)
346 ret = PTR_ERR(info->rtc_dev); 356 ret = PTR_ERR(info->rtc_dev);
347 dev_err(&pdev->dev, "Unable to register device (err=%d).\n", 357 dev_err(&pdev->dev, "Unable to register device (err=%d).\n",
348 ret); 358 ret);
349 return ret; 359 goto disable_clk;
350 } 360 }
351 361
352 ret = devm_request_irq(&pdev->dev, info->tegra_rtc_irq, 362 ret = devm_request_irq(&pdev->dev, info->tegra_rtc_irq,
@@ -356,12 +366,25 @@ static int __init tegra_rtc_probe(struct platform_device *pdev)
356 dev_err(&pdev->dev, 366 dev_err(&pdev->dev,
357 "Unable to request interrupt for device (err=%d).\n", 367 "Unable to request interrupt for device (err=%d).\n",
358 ret); 368 ret);
359 return ret; 369 goto disable_clk;
360 } 370 }
361 371
362 dev_notice(&pdev->dev, "Tegra internal Real Time Clock\n"); 372 dev_notice(&pdev->dev, "Tegra internal Real Time Clock\n");
363 373
364 return 0; 374 return 0;
375
376disable_clk:
377 clk_disable_unprepare(info->clk);
378 return ret;
379}
380
381static int tegra_rtc_remove(struct platform_device *pdev)
382{
383 struct tegra_rtc_info *info = platform_get_drvdata(pdev);
384
385 clk_disable_unprepare(info->clk);
386
387 return 0;
365} 388}
366 389
367#ifdef CONFIG_PM_SLEEP 390#ifdef CONFIG_PM_SLEEP
@@ -413,6 +436,7 @@ static void tegra_rtc_shutdown(struct platform_device *pdev)
413 436
414MODULE_ALIAS("platform:tegra_rtc"); 437MODULE_ALIAS("platform:tegra_rtc");
415static struct platform_driver tegra_rtc_driver = { 438static struct platform_driver tegra_rtc_driver = {
439 .remove = tegra_rtc_remove,
416 .shutdown = tegra_rtc_shutdown, 440 .shutdown = tegra_rtc_shutdown,
417 .driver = { 441 .driver = {
418 .name = "tegra_rtc", 442 .name = "tegra_rtc",