mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
514 lines
12 KiB
514 lines
12 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (c) 2019-2020 NVIDIA Corporation. All rights reserved. |
|
*/ |
|
|
|
#include <linux/clocksource.h> |
|
#include <linux/module.h> |
|
#include <linux/interrupt.h> |
|
#include <linux/io.h> |
|
#include <linux/of.h> |
|
#include <linux/of_device.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/pm.h> |
|
#include <linux/watchdog.h> |
|
|
|
/* shared registers */ |
|
#define TKETSC0 0x000 |
|
#define TKETSC1 0x004 |
|
#define TKEUSEC 0x008 |
|
#define TKEOSC 0x00c |
|
|
|
#define TKEIE(x) (0x100 + ((x) * 4)) |
|
#define TKEIE_WDT_MASK(x, y) ((y) << (16 + 4 * (x))) |
|
|
|
/* timer registers */ |
|
#define TMRCR 0x000 |
|
#define TMRCR_ENABLE BIT(31) |
|
#define TMRCR_PERIODIC BIT(30) |
|
#define TMRCR_PTV(x) ((x) & 0x0fffffff) |
|
|
|
#define TMRSR 0x004 |
|
#define TMRSR_INTR_CLR BIT(30) |
|
|
|
#define TMRCSSR 0x008 |
|
#define TMRCSSR_SRC_USEC (0 << 0) |
|
|
|
/* watchdog registers */ |
|
#define WDTCR 0x000 |
|
#define WDTCR_SYSTEM_POR_RESET_ENABLE BIT(16) |
|
#define WDTCR_SYSTEM_DEBUG_RESET_ENABLE BIT(15) |
|
#define WDTCR_REMOTE_INT_ENABLE BIT(14) |
|
#define WDTCR_LOCAL_FIQ_ENABLE BIT(13) |
|
#define WDTCR_LOCAL_INT_ENABLE BIT(12) |
|
#define WDTCR_PERIOD_MASK (0xff << 4) |
|
#define WDTCR_PERIOD(x) (((x) & 0xff) << 4) |
|
#define WDTCR_TIMER_SOURCE_MASK 0xf |
|
#define WDTCR_TIMER_SOURCE(x) ((x) & 0xf) |
|
|
|
#define WDTCMDR 0x008 |
|
#define WDTCMDR_DISABLE_COUNTER BIT(1) |
|
#define WDTCMDR_START_COUNTER BIT(0) |
|
|
|
#define WDTUR 0x00c |
|
#define WDTUR_UNLOCK_PATTERN 0x0000c45a |
|
|
|
struct tegra186_timer_soc { |
|
unsigned int num_timers; |
|
unsigned int num_wdts; |
|
}; |
|
|
|
struct tegra186_tmr { |
|
struct tegra186_timer *parent; |
|
void __iomem *regs; |
|
unsigned int index; |
|
unsigned int hwirq; |
|
}; |
|
|
|
struct tegra186_wdt { |
|
struct watchdog_device base; |
|
|
|
void __iomem *regs; |
|
unsigned int index; |
|
bool locked; |
|
|
|
struct tegra186_tmr *tmr; |
|
}; |
|
|
|
static inline struct tegra186_wdt *to_tegra186_wdt(struct watchdog_device *wdd) |
|
{ |
|
return container_of(wdd, struct tegra186_wdt, base); |
|
} |
|
|
|
struct tegra186_timer { |
|
const struct tegra186_timer_soc *soc; |
|
struct device *dev; |
|
void __iomem *regs; |
|
|
|
struct tegra186_wdt *wdt; |
|
struct clocksource usec; |
|
struct clocksource tsc; |
|
struct clocksource osc; |
|
}; |
|
|
|
static void tmr_writel(struct tegra186_tmr *tmr, u32 value, unsigned int offset) |
|
{ |
|
writel_relaxed(value, tmr->regs + offset); |
|
} |
|
|
|
static void wdt_writel(struct tegra186_wdt *wdt, u32 value, unsigned int offset) |
|
{ |
|
writel_relaxed(value, wdt->regs + offset); |
|
} |
|
|
|
static u32 wdt_readl(struct tegra186_wdt *wdt, unsigned int offset) |
|
{ |
|
return readl_relaxed(wdt->regs + offset); |
|
} |
|
|
|
static struct tegra186_tmr *tegra186_tmr_create(struct tegra186_timer *tegra, |
|
unsigned int index) |
|
{ |
|
unsigned int offset = 0x10000 + index * 0x10000; |
|
struct tegra186_tmr *tmr; |
|
|
|
tmr = devm_kzalloc(tegra->dev, sizeof(*tmr), GFP_KERNEL); |
|
if (!tmr) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
tmr->parent = tegra; |
|
tmr->regs = tegra->regs + offset; |
|
tmr->index = index; |
|
tmr->hwirq = 0; |
|
|
|
return tmr; |
|
} |
|
|
|
static const struct watchdog_info tegra186_wdt_info = { |
|
.options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, |
|
.identity = "NVIDIA Tegra186 WDT", |
|
}; |
|
|
|
static void tegra186_wdt_disable(struct tegra186_wdt *wdt) |
|
{ |
|
/* unlock and disable the watchdog */ |
|
wdt_writel(wdt, WDTUR_UNLOCK_PATTERN, WDTUR); |
|
wdt_writel(wdt, WDTCMDR_DISABLE_COUNTER, WDTCMDR); |
|
|
|
/* disable timer */ |
|
tmr_writel(wdt->tmr, 0, TMRCR); |
|
} |
|
|
|
static void tegra186_wdt_enable(struct tegra186_wdt *wdt) |
|
{ |
|
struct tegra186_timer *tegra = wdt->tmr->parent; |
|
u32 value; |
|
|
|
/* unmask hardware IRQ, this may have been lost across powergate */ |
|
value = TKEIE_WDT_MASK(wdt->index, 1); |
|
writel(value, tegra->regs + TKEIE(wdt->tmr->hwirq)); |
|
|
|
/* clear interrupt */ |
|
tmr_writel(wdt->tmr, TMRSR_INTR_CLR, TMRSR); |
|
|
|
/* select microsecond source */ |
|
tmr_writel(wdt->tmr, TMRCSSR_SRC_USEC, TMRCSSR); |
|
|
|
/* configure timer (system reset happens on the fifth expiration) */ |
|
value = TMRCR_PTV(wdt->base.timeout * USEC_PER_SEC / 5) | |
|
TMRCR_PERIODIC | TMRCR_ENABLE; |
|
tmr_writel(wdt->tmr, value, TMRCR); |
|
|
|
if (!wdt->locked) { |
|
value = wdt_readl(wdt, WDTCR); |
|
|
|
/* select the proper timer source */ |
|
value &= ~WDTCR_TIMER_SOURCE_MASK; |
|
value |= WDTCR_TIMER_SOURCE(wdt->tmr->index); |
|
|
|
/* single timer period since that's already configured */ |
|
value &= ~WDTCR_PERIOD_MASK; |
|
value |= WDTCR_PERIOD(1); |
|
|
|
/* enable local interrupt for WDT petting */ |
|
value |= WDTCR_LOCAL_INT_ENABLE; |
|
|
|
/* enable local FIQ and remote interrupt for debug dump */ |
|
if (0) |
|
value |= WDTCR_REMOTE_INT_ENABLE | |
|
WDTCR_LOCAL_FIQ_ENABLE; |
|
|
|
/* enable system debug reset (doesn't properly reboot) */ |
|
if (0) |
|
value |= WDTCR_SYSTEM_DEBUG_RESET_ENABLE; |
|
|
|
/* enable system POR reset */ |
|
value |= WDTCR_SYSTEM_POR_RESET_ENABLE; |
|
|
|
wdt_writel(wdt, value, WDTCR); |
|
} |
|
|
|
wdt_writel(wdt, WDTCMDR_START_COUNTER, WDTCMDR); |
|
} |
|
|
|
static int tegra186_wdt_start(struct watchdog_device *wdd) |
|
{ |
|
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd); |
|
|
|
tegra186_wdt_enable(wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra186_wdt_stop(struct watchdog_device *wdd) |
|
{ |
|
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd); |
|
|
|
tegra186_wdt_disable(wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra186_wdt_ping(struct watchdog_device *wdd) |
|
{ |
|
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd); |
|
|
|
tegra186_wdt_disable(wdt); |
|
tegra186_wdt_enable(wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static int tegra186_wdt_set_timeout(struct watchdog_device *wdd, |
|
unsigned int timeout) |
|
{ |
|
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd); |
|
|
|
if (watchdog_active(&wdt->base)) |
|
tegra186_wdt_disable(wdt); |
|
|
|
wdt->base.timeout = timeout; |
|
|
|
if (watchdog_active(&wdt->base)) |
|
tegra186_wdt_enable(wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct watchdog_ops tegra186_wdt_ops = { |
|
.owner = THIS_MODULE, |
|
.start = tegra186_wdt_start, |
|
.stop = tegra186_wdt_stop, |
|
.ping = tegra186_wdt_ping, |
|
.set_timeout = tegra186_wdt_set_timeout, |
|
}; |
|
|
|
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra, |
|
unsigned int index) |
|
{ |
|
unsigned int offset = 0x10000, source; |
|
struct tegra186_wdt *wdt; |
|
u32 value; |
|
int err; |
|
|
|
offset += tegra->soc->num_timers * 0x10000 + index * 0x10000; |
|
|
|
wdt = devm_kzalloc(tegra->dev, sizeof(*wdt), GFP_KERNEL); |
|
if (!wdt) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
wdt->regs = tegra->regs + offset; |
|
wdt->index = index; |
|
|
|
/* read the watchdog configuration since it might be locked down */ |
|
value = wdt_readl(wdt, WDTCR); |
|
|
|
if (value & WDTCR_LOCAL_INT_ENABLE) |
|
wdt->locked = true; |
|
|
|
source = value & WDTCR_TIMER_SOURCE_MASK; |
|
|
|
wdt->tmr = tegra186_tmr_create(tegra, source); |
|
if (IS_ERR(wdt->tmr)) |
|
return ERR_CAST(wdt->tmr); |
|
|
|
wdt->base.info = &tegra186_wdt_info; |
|
wdt->base.ops = &tegra186_wdt_ops; |
|
wdt->base.min_timeout = 1; |
|
wdt->base.max_timeout = 255; |
|
wdt->base.parent = tegra->dev; |
|
|
|
err = watchdog_init_timeout(&wdt->base, 5, tegra->dev); |
|
if (err < 0) { |
|
dev_err(tegra->dev, "failed to initialize timeout: %d\n", err); |
|
return ERR_PTR(err); |
|
} |
|
|
|
err = devm_watchdog_register_device(tegra->dev, &wdt->base); |
|
if (err < 0) { |
|
dev_err(tegra->dev, "failed to register WDT: %d\n", err); |
|
return ERR_PTR(err); |
|
} |
|
|
|
return wdt; |
|
} |
|
|
|
static u64 tegra186_timer_tsc_read(struct clocksource *cs) |
|
{ |
|
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer, |
|
tsc); |
|
u32 hi, lo, ss; |
|
|
|
hi = readl_relaxed(tegra->regs + TKETSC1); |
|
|
|
/* |
|
* The 56-bit value of the TSC is spread across two registers that are |
|
* not synchronized. In order to read them atomically, ensure that the |
|
* high 24 bits match before and after reading the low 32 bits. |
|
*/ |
|
do { |
|
/* snapshot the high 24 bits */ |
|
ss = hi; |
|
|
|
lo = readl_relaxed(tegra->regs + TKETSC0); |
|
hi = readl_relaxed(tegra->regs + TKETSC1); |
|
} while (hi != ss); |
|
|
|
return (u64)hi << 32 | lo; |
|
} |
|
|
|
static int tegra186_timer_tsc_init(struct tegra186_timer *tegra) |
|
{ |
|
tegra->tsc.name = "tsc"; |
|
tegra->tsc.rating = 300; |
|
tegra->tsc.read = tegra186_timer_tsc_read; |
|
tegra->tsc.mask = CLOCKSOURCE_MASK(56); |
|
tegra->tsc.flags = CLOCK_SOURCE_IS_CONTINUOUS; |
|
|
|
return clocksource_register_hz(&tegra->tsc, 31250000); |
|
} |
|
|
|
static u64 tegra186_timer_osc_read(struct clocksource *cs) |
|
{ |
|
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer, |
|
osc); |
|
|
|
return readl_relaxed(tegra->regs + TKEOSC); |
|
} |
|
|
|
static int tegra186_timer_osc_init(struct tegra186_timer *tegra) |
|
{ |
|
tegra->osc.name = "osc"; |
|
tegra->osc.rating = 300; |
|
tegra->osc.read = tegra186_timer_osc_read; |
|
tegra->osc.mask = CLOCKSOURCE_MASK(32); |
|
tegra->osc.flags = CLOCK_SOURCE_IS_CONTINUOUS; |
|
|
|
return clocksource_register_hz(&tegra->osc, 38400000); |
|
} |
|
|
|
static u64 tegra186_timer_usec_read(struct clocksource *cs) |
|
{ |
|
struct tegra186_timer *tegra = container_of(cs, struct tegra186_timer, |
|
usec); |
|
|
|
return readl_relaxed(tegra->regs + TKEUSEC); |
|
} |
|
|
|
static int tegra186_timer_usec_init(struct tegra186_timer *tegra) |
|
{ |
|
tegra->usec.name = "usec"; |
|
tegra->usec.rating = 300; |
|
tegra->usec.read = tegra186_timer_usec_read; |
|
tegra->usec.mask = CLOCKSOURCE_MASK(32); |
|
tegra->usec.flags = CLOCK_SOURCE_IS_CONTINUOUS; |
|
|
|
return clocksource_register_hz(&tegra->usec, USEC_PER_SEC); |
|
} |
|
|
|
static irqreturn_t tegra186_timer_irq(int irq, void *data) |
|
{ |
|
struct tegra186_timer *tegra = data; |
|
|
|
if (watchdog_active(&tegra->wdt->base)) { |
|
tegra186_wdt_disable(tegra->wdt); |
|
tegra186_wdt_enable(tegra->wdt); |
|
} |
|
|
|
return IRQ_HANDLED; |
|
} |
|
|
|
static int tegra186_timer_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct tegra186_timer *tegra; |
|
unsigned int irq; |
|
int err; |
|
|
|
tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL); |
|
if (!tegra) |
|
return -ENOMEM; |
|
|
|
tegra->soc = of_device_get_match_data(dev); |
|
dev_set_drvdata(dev, tegra); |
|
tegra->dev = dev; |
|
|
|
tegra->regs = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(tegra->regs)) |
|
return PTR_ERR(tegra->regs); |
|
|
|
err = platform_get_irq(pdev, 0); |
|
if (err < 0) |
|
return err; |
|
|
|
irq = err; |
|
|
|
/* create a watchdog using a preconfigured timer */ |
|
tegra->wdt = tegra186_wdt_create(tegra, 0); |
|
if (IS_ERR(tegra->wdt)) { |
|
err = PTR_ERR(tegra->wdt); |
|
dev_err(dev, "failed to create WDT: %d\n", err); |
|
return err; |
|
} |
|
|
|
err = tegra186_timer_tsc_init(tegra); |
|
if (err < 0) { |
|
dev_err(dev, "failed to register TSC counter: %d\n", err); |
|
return err; |
|
} |
|
|
|
err = tegra186_timer_osc_init(tegra); |
|
if (err < 0) { |
|
dev_err(dev, "failed to register OSC counter: %d\n", err); |
|
goto unregister_tsc; |
|
} |
|
|
|
err = tegra186_timer_usec_init(tegra); |
|
if (err < 0) { |
|
dev_err(dev, "failed to register USEC counter: %d\n", err); |
|
goto unregister_osc; |
|
} |
|
|
|
err = devm_request_irq(dev, irq, tegra186_timer_irq, 0, |
|
"tegra186-timer", tegra); |
|
if (err < 0) { |
|
dev_err(dev, "failed to request IRQ#%u: %d\n", irq, err); |
|
goto unregister_usec; |
|
} |
|
|
|
return 0; |
|
|
|
unregister_usec: |
|
clocksource_unregister(&tegra->usec); |
|
unregister_osc: |
|
clocksource_unregister(&tegra->osc); |
|
unregister_tsc: |
|
clocksource_unregister(&tegra->tsc); |
|
return err; |
|
} |
|
|
|
static int tegra186_timer_remove(struct platform_device *pdev) |
|
{ |
|
struct tegra186_timer *tegra = platform_get_drvdata(pdev); |
|
|
|
clocksource_unregister(&tegra->usec); |
|
clocksource_unregister(&tegra->osc); |
|
clocksource_unregister(&tegra->tsc); |
|
|
|
return 0; |
|
} |
|
|
|
static int __maybe_unused tegra186_timer_suspend(struct device *dev) |
|
{ |
|
struct tegra186_timer *tegra = dev_get_drvdata(dev); |
|
|
|
if (watchdog_active(&tegra->wdt->base)) |
|
tegra186_wdt_disable(tegra->wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static int __maybe_unused tegra186_timer_resume(struct device *dev) |
|
{ |
|
struct tegra186_timer *tegra = dev_get_drvdata(dev); |
|
|
|
if (watchdog_active(&tegra->wdt->base)) |
|
tegra186_wdt_enable(tegra->wdt); |
|
|
|
return 0; |
|
} |
|
|
|
static SIMPLE_DEV_PM_OPS(tegra186_timer_pm_ops, tegra186_timer_suspend, |
|
tegra186_timer_resume); |
|
|
|
static const struct tegra186_timer_soc tegra186_timer = { |
|
.num_timers = 10, |
|
.num_wdts = 3, |
|
}; |
|
|
|
static const struct tegra186_timer_soc tegra234_timer = { |
|
.num_timers = 16, |
|
.num_wdts = 3, |
|
}; |
|
|
|
static const struct of_device_id tegra186_timer_of_match[] = { |
|
{ .compatible = "nvidia,tegra186-timer", .data = &tegra186_timer }, |
|
{ .compatible = "nvidia,tegra234-timer", .data = &tegra234_timer }, |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(of, tegra186_timer_of_match); |
|
|
|
static struct platform_driver tegra186_wdt_driver = { |
|
.driver = { |
|
.name = "tegra186-timer", |
|
.pm = &tegra186_timer_pm_ops, |
|
.of_match_table = tegra186_timer_of_match, |
|
}, |
|
.probe = tegra186_timer_probe, |
|
.remove = tegra186_timer_remove, |
|
}; |
|
module_platform_driver(tegra186_wdt_driver); |
|
|
|
MODULE_AUTHOR("Thierry Reding <[email protected]>"); |
|
MODULE_DESCRIPTION("NVIDIA Tegra186 timers driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|