linux/drivers/clocksource/timer-tegra186.c
Linus Torvalds 7d8d20191c Timekeeping and clocksource/event driver updates the second batch:
- A trivial documentation fix in the timekeeping core
 
   - A really boring set of small fixes, enhancements and cleanups in the
     drivers code. No new clocksource/clockevent drivers for a change.
 -----BEGIN PGP SIGNATURE-----
 
 iQJHBAABCgAxFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmRLuTsTHHRnbHhAbGlu
 dXRyb25peC5kZQAKCRCmGPVMDXSYoQ+vEACSlqE5SN+6SxNQOwWcou79d1loB0Lk
 3kSlFvRH9CdPDdW5a0Qnr3YJx4mFXrN9mMdFsywhl5NGrZQcH3nGPEYN74B3ynhP
 WpE5PSDJDVOA9F/yK6kmf5xX39RPh0aVy+C6ShaHD/anqwX2mTlXVBAg/3nOGeNy
 iHNYHzP4AtQfE+EtgbEPEZaOUpzmGL/dZb1HAzJaFU1QBmsrXWHLs4xqGUR0A36+
 1I0TGK53WVSXHvEVciTx4lH7mHR1xzR3LvnotdET6rRsqLREptosqA4nBRqYZLGK
 uF+jNxVE/0OwVzge5gPvwL3YSAjiln9cZjhA/q7z3L/pdoj/kR3hXv4XyXGrLPN6
 L371RA/RLtjkrBb/rHcB/VNADBmtwLQjo7gJJ3UMzIuuvnkokzQrl3fxTxJjmegK
 ypR8dpMUaO5vlwIGqwSuQyKxkNEeuNzm2fv84IpZJNSKoQj5nGHPmk+0u6FLhJeG
 sqvIfDfuH/+Hc8fxbG5BKBu5lNvmCD4MZ3xxf3Wv80fykJBX6dvJs30B/iuJFQXr
 VylbUbxddCNjdHGtByswY5tLGfpWuou0g2XWqtsEB5P0aLs54R0gaoDeTPuBTzJW
 Io4tHnvRu7nZCSncxzHUuUfnve0WjMDBgJeSfa2Rx4Qz8M7G5l3XQLO4n+iFGzI5
 gdYnrztBLSegww==
 =LWO6
 -----END PGP SIGNATURE-----

Merge tag 'timers-core-2023-04-28' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip

Pull more timer updates from Thomas Gleixner:
 "Timekeeping and clocksource/event driver updates the second batch:

   - A trivial documentation fix in the timekeeping core

   - A really boring set of small fixes, enhancements and cleanups in
     the drivers code. No new clocksource/clockevent drivers for a
     change"

* tag 'timers-core-2023-04-28' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip:
  timekeeping: Fix references to nonexistent ktime_get_fast_ns()
  dt-bindings: timer: rockchip: Add rk3588 compatible
  dt-bindings: timer: rockchip: Drop superfluous rk3288 compatible
  clocksource/drivers/ti: Use of_property_read_bool() for boolean properties
  clocksource/drivers/timer-ti-dm: Fix finding alwon timer
  clocksource/drivers/davinci: Fix memory leak in davinci_timer_register when init fails
  clocksource/drivers/stm32-lp: Drop of_match_ptr for ID table
  clocksource/drivers/timer-ti-dm: Convert to platform remove callback returning void
  clocksource/drivers/timer-tegra186: Convert to platform remove callback returning void
  clocksource/drivers/timer-ti-dm: Improve error message in .remove
  clocksource/drivers/timer-stm32-lp: Mark driver as non-removable
  clocksource/drivers/sh_mtu2: Mark driver as non-removable
  clocksource/drivers/timer-ti-dm: Use of_address_to_resource()
  clocksource/drivers/timer-imx-gpt: Remove non-DT function
  clocksource/drivers/timer-mediatek: Split out CPUXGPT timers
  clocksource/drivers/exynos_mct: Explicitly return 0 for shared timer
2023-04-29 10:24:30 -07:00

512 lines
12 KiB
C

// 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 void 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);
}
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_new = tegra186_timer_remove,
};
module_platform_driver(tegra186_wdt_driver);
MODULE_AUTHOR("Thierry Reding <treding@nvidia.com>");
MODULE_DESCRIPTION("NVIDIA Tegra186 timers driver");