root/drivers/clocksource/timer-npcm7xx.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2014-2018 Nuvoton Technologies tomer.maimon@nuvoton.com
 * All rights reserved.
 *
 * Copyright 2017 Google, Inc.
 */

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/clockchips.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include "timer-of.h"

/* Timers registers */
#define NPCM7XX_REG_TCSR0       0x0 /* Timer 0 Control and Status Register */
#define NPCM7XX_REG_TICR0       0x8 /* Timer 0 Initial Count Register */
#define NPCM7XX_REG_TCSR1       0x4 /* Timer 1 Control and Status Register */
#define NPCM7XX_REG_TICR1       0xc /* Timer 1 Initial Count Register */
#define NPCM7XX_REG_TDR1        0x14 /* Timer 1 Data Register */
#define NPCM7XX_REG_TISR        0x18 /* Timer Interrupt Status Register */

/* Timers control */
#define NPCM7XX_Tx_RESETINT             0x1f
#define NPCM7XX_Tx_PERIOD               BIT(27)
#define NPCM7XX_Tx_INTEN                BIT(29)
#define NPCM7XX_Tx_COUNTEN              BIT(30)
#define NPCM7XX_Tx_ONESHOT              0x0
#define NPCM7XX_Tx_OPER                 GENMASK(28, 27)
#define NPCM7XX_Tx_MIN_PRESCALE         0x1
#define NPCM7XX_Tx_TDR_MASK_BITS        24
#define NPCM7XX_Tx_MAX_CNT              0xFFFFFF
#define NPCM7XX_T0_CLR_INT              0x1
#define NPCM7XX_Tx_CLR_CSR              0x0

/* Timers operating mode */
#define NPCM7XX_START_PERIODIC_Tx (NPCM7XX_Tx_PERIOD | NPCM7XX_Tx_COUNTEN | \
                                        NPCM7XX_Tx_INTEN | \
                                        NPCM7XX_Tx_MIN_PRESCALE)

#define NPCM7XX_START_ONESHOT_Tx (NPCM7XX_Tx_ONESHOT | NPCM7XX_Tx_COUNTEN | \
                                        NPCM7XX_Tx_INTEN | \
                                        NPCM7XX_Tx_MIN_PRESCALE)

#define NPCM7XX_START_Tx (NPCM7XX_Tx_COUNTEN | NPCM7XX_Tx_PERIOD | \
                                NPCM7XX_Tx_MIN_PRESCALE)

#define NPCM7XX_DEFAULT_CSR (NPCM7XX_Tx_CLR_CSR | NPCM7XX_Tx_MIN_PRESCALE)

static int npcm7xx_timer_resume(struct clock_event_device *evt)
{
        struct timer_of *to = to_timer_of(evt);
        u32 val;

        val = readl(timer_of_base(to) + NPCM7XX_REG_TCSR0);
        val |= NPCM7XX_Tx_COUNTEN;
        writel(val, timer_of_base(to) + NPCM7XX_REG_TCSR0);

        return 0;
}

static int npcm7xx_timer_shutdown(struct clock_event_device *evt)
{
        struct timer_of *to = to_timer_of(evt);
        u32 val;

        val = readl(timer_of_base(to) + NPCM7XX_REG_TCSR0);
        val &= ~NPCM7XX_Tx_COUNTEN;
        writel(val, timer_of_base(to) + NPCM7XX_REG_TCSR0);

        return 0;
}

static int npcm7xx_timer_oneshot(struct clock_event_device *evt)
{
        struct timer_of *to = to_timer_of(evt);
        u32 val;

        val = readl(timer_of_base(to) + NPCM7XX_REG_TCSR0);
        val &= ~NPCM7XX_Tx_OPER;
        val |= NPCM7XX_START_ONESHOT_Tx;
        writel(val, timer_of_base(to) + NPCM7XX_REG_TCSR0);

        return 0;
}

static int npcm7xx_timer_periodic(struct clock_event_device *evt)
{
        struct timer_of *to = to_timer_of(evt);
        u32 val;

        writel(timer_of_period(to), timer_of_base(to) + NPCM7XX_REG_TICR0);

        val = readl(timer_of_base(to) + NPCM7XX_REG_TCSR0);
        val &= ~NPCM7XX_Tx_OPER;
        val |= NPCM7XX_START_PERIODIC_Tx;
        writel(val, timer_of_base(to) + NPCM7XX_REG_TCSR0);

        return 0;
}

static int npcm7xx_clockevent_set_next_event(unsigned long evt,
                struct clock_event_device *clk)
{
        struct timer_of *to = to_timer_of(clk);
        u32 val;

        writel(evt, timer_of_base(to) + NPCM7XX_REG_TICR0);
        val = readl(timer_of_base(to) + NPCM7XX_REG_TCSR0);
        val |= NPCM7XX_START_Tx;
        writel(val, timer_of_base(to) + NPCM7XX_REG_TCSR0);

        return 0;
}

static irqreturn_t npcm7xx_timer0_interrupt(int irq, void *dev_id)
{
        struct clock_event_device *evt = (struct clock_event_device *)dev_id;
        struct timer_of *to = to_timer_of(evt);

        writel(NPCM7XX_T0_CLR_INT, timer_of_base(to) + NPCM7XX_REG_TISR);

        evt->event_handler(evt);

        return IRQ_HANDLED;
}

static struct timer_of npcm7xx_to = {
        .flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK,

        .clkevt = {
                .name               = "npcm7xx-timer0",
                .features           = CLOCK_EVT_FEAT_PERIODIC |
                                      CLOCK_EVT_FEAT_ONESHOT,
                .set_next_event     = npcm7xx_clockevent_set_next_event,
                .set_state_shutdown = npcm7xx_timer_shutdown,
                .set_state_periodic = npcm7xx_timer_periodic,
                .set_state_oneshot  = npcm7xx_timer_oneshot,
                .tick_resume        = npcm7xx_timer_resume,
                .rating             = 300,
        },

        .of_irq = {
                .handler = npcm7xx_timer0_interrupt,
                .flags = IRQF_TIMER | IRQF_IRQPOLL,
        },
};

static void __init npcm7xx_clockevents_init(void)
{
        writel(NPCM7XX_DEFAULT_CSR,
                timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TCSR0);

        writel(NPCM7XX_Tx_RESETINT,
                timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TISR);

        npcm7xx_to.clkevt.cpumask = cpumask_of(0);
        clockevents_config_and_register(&npcm7xx_to.clkevt,
                                        timer_of_rate(&npcm7xx_to),
                                        0x1, NPCM7XX_Tx_MAX_CNT);
}

static void __init npcm7xx_clocksource_init(void)
{
        u32 val;

        writel(NPCM7XX_DEFAULT_CSR,
                timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TCSR1);
        writel(NPCM7XX_Tx_MAX_CNT,
                timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TICR1);

        val = readl(timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TCSR1);
        val |= NPCM7XX_START_Tx;
        writel(val, timer_of_base(&npcm7xx_to) + NPCM7XX_REG_TCSR1);

        clocksource_mmio_init(timer_of_base(&npcm7xx_to) +
                                NPCM7XX_REG_TDR1,
                                "npcm7xx-timer1", timer_of_rate(&npcm7xx_to),
                                200, (unsigned int)NPCM7XX_Tx_TDR_MASK_BITS,
                                clocksource_mmio_readl_down);
}

static int __init npcm7xx_timer_init(struct device_node *np)
{
        struct clk *clk;
        int ret;

        ret = timer_of_init(np, &npcm7xx_to);
        if (ret)
                return ret;

        /* Clock input is divided by PRESCALE + 1 before it is fed */
        /* to the counter */
        npcm7xx_to.of_clk.rate = npcm7xx_to.of_clk.rate /
                (NPCM7XX_Tx_MIN_PRESCALE + 1);

        /* Enable the clock for timer1, if it exists */
        clk = of_clk_get(np, 1);
        if (clk) {
                if (!IS_ERR(clk))
                        clk_prepare_enable(clk);
                else
                        pr_warn("%pOF: Failed to get clock for timer1: %pe", np, clk);
        }

        npcm7xx_clocksource_init();
        npcm7xx_clockevents_init();

        pr_info("Enabling NPCM7xx clocksource timer base: %px, IRQ: %d ",
                timer_of_base(&npcm7xx_to), timer_of_irq(&npcm7xx_to));

        return 0;
}

TIMER_OF_DECLARE(wpcm450, "nuvoton,wpcm450-timer", npcm7xx_timer_init);
TIMER_OF_DECLARE(npcm7xx, "nuvoton,npcm750-timer", npcm7xx_timer_init);