Skip to content
Snippets Groups Projects
intel-gptc-timer.c 27.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     *  Copyright(C) 2017 Intel Corporation.
    
     *  Lei Chuanhua <chuanhua.lei@intel.com>
    
    leichuan's avatar
    leichuan committed
     *  This program is free software; you can redistribute it and/or modify it
     *  under the terms and conditions of the GNU General Public License,
     *  version 2, as published by the Free Software Foundation.
     *
     *  This program is distributed in the hope it will be useful, but WITHOUT
     *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
     *  more details.
     *
     *  The full GNU General Public License is included in this distribution in
     *  the file called "COPYING".
    
    leichuan's avatar
    leichuan committed
    #include <linux/kernel.h>
    #include <linux/clk.h>
    #include <linux/clkdev.h>
    #include <linux/clocksource.h>
    #include <linux/clockchips.h>
    #include <linux/timer.h>
    #include <linux/interrupt.h>
    #include <linux/of.h>
    #include <linux/of_irq.h>
    #include <linux/of_address.h>
    #include <linux/irq.h>
    #include <linux/sched_clock.h>
    #include <linux/cpu.h>
    
    #include <linux/debugfs.h>
    
    leichuan's avatar
    leichuan committed
    
    #include <asm/time.h>
    
    
    Ma, Hua's avatar
    Ma, Hua committed
    #include <clocksource/intel-gptc-timer.h>
    #ifdef CONFIG_LTQ_VMB
    #include <asm/ltq_vmb.h>
    #include <linux/irqchip/mips-gic.h>
    #endif
    
    
    #define GPTC_CLC	0x00
    
    leichuan's avatar
    leichuan committed
    #define CLC_DIS		BIT(0)
    #define CLC_SUSPEND	BIT(4)
    #define CLC_RMC		BIT(8)
    #define CLC_SMC		BIT(16)
    
    
    #define GPTC_ID		0x08
    
    leichuan's avatar
    leichuan committed
    #define ID_VER		0x1Fu
    #define ID_VER_S	0
    #define ID_CFG		0xE0u
    #define ID_CFG_S	5
    #define ID_ID		0xFF00u
    #define ID_ID_S		8
    
    
    #define GPTC_CON(X)	(0x10 + (X) * 0x20)
    
    leichuan's avatar
    leichuan committed
    #define CON_EN_STAT	BIT(0) /* RO only */
    #define CON_COUNT_UP	BIT(1) /* Down up or down */
    #define CON_CNT		BIT(2)
    #define CON_ONESHOT	BIT(3) /* Stop or continue when overflowing */
    #define CON_EXT		BIT(4) /* 32 or 16 bit */
    #define CON_EDGE_RISE	BIT(6)
    #define CON_EDGE_FALL	BIT(7)
    #define CON_EDGE_ANY	(CON_EDGE_RISE | CON_EDGE_FALL)
    #define CON_SYNC	BIT(8) /* Signal sync to module clock or not */
    
    
    #define GPTC_RUN(X)	(0x18 + (X) * 0x20)
    
    leichuan's avatar
    leichuan committed
    #define RUN_EN		BIT(0)
    #define RUN_STOP	BIT(1)
    #define RUN_RELOAD	BIT(2)
    
    
    #define GPTC_RLD(X)	(0x20 + (X) * 0x20)
    #define GPTC_CNT(X)	(0x28 + (X) * 0x20)
    
    leichuan's avatar
    leichuan committed
    
    
    #define GPTC_IRNEN	0xf4
    #define GPTC_IRNICR	0xf8
    #define GPTC_IRNCR	0xfc
    
    leichuan's avatar
    leichuan committed
    
    #define TIMER_PER_GPTC	3
    
    
    #define GPTC_MAX	4
    
    leichuan's avatar
    leichuan committed
    
    enum gptc_cnt_dir {
    	GPTC_COUNT_DOWN = 0,
    	GPTC_COUNT_UP,
    };
    
    
    enum gptc_timer_type {
    	TIMER_TYPE_CLK_SRC,
    	TIMER_TYPE_CLK_EVT,
    	TIMER_TYPE_WDT,
    
    leichuan's avatar
    leichuan committed
    	TIMER_TYPE_HEARTBEAT,
    
    Ma, Hua's avatar
    Ma, Hua committed
    	TIMER_TYPE_HT_YIELD,
    
    	TIMER_TYPE_MAX,
    };
    
    /* Hardwre GPTC struct */
    struct gptc {
    	u32 id;
    	struct device_node *np;
    	void __iomem *base;
    	unsigned long phy_base;
    
    	struct clk *freqclk;
    
    	struct clk *gateclk;
    	u32 fpifreq;
    
    	spinlock_t lock; /* Shared register access */
    
    	struct list_head parent; /* Timers belonging to itsef */
    	struct list_head next; /* Link to next GPTC */
    	struct kref ref;
    };
    
    
    leichuan's avatar
    leichuan committed
    struct gptc_timer {
    
    	struct gptc *gptc; /* Point back to parent */
    
    leichuan's avatar
    leichuan committed
    	void __iomem *base;
    	unsigned long phy_base;
    	u32 gptcid;
    	u32 tid; /* 0, 1, 2 only */
    	u32 cpuid;
    	u32 irq;
    
    leichuan's avatar
    leichuan committed
    	u32 frequency;
    	enum gptc_cnt_dir dir;
    	bool used;
    
    Ma, Hua's avatar
    Ma, Hua committed
    	u32 yield_pin;
    	void (*call_back)(void *);
    	void *call_back_param;
    
    	struct list_head child; /* Node in parent list */
    	struct list_head clksrc; /* Node in clock source list */
    	struct list_head clkevt; /* Node in clock event list */
    	struct list_head wdt; /* Node in watchdog timer */
    
    leichuan's avatar
    leichuan committed
    	struct list_head heartbeat; /* Heartbeat */
    
    Ma, Hua's avatar
    Ma, Hua committed
    	struct list_head ht_yield; /* Heartbeat yield */
    
    leichuan's avatar
    leichuan committed
    };
    
    struct gptc_clocksource {
    
    	struct gptc_timer  *timer;
    
    leichuan's avatar
    leichuan committed
    	struct clocksource cs;
    };
    
    struct gptc_clockevent {
    
    	struct gptc_timer *timer;
    
    leichuan's avatar
    leichuan committed
    	u32 ticks_per_jiffy;
    	struct clock_event_device ce;
    	char name[16];
    };
    
    
    static LIST_HEAD(gptc_list);
    static LIST_HEAD(gptc_clksrc_list);
    static LIST_HEAD(gptc_clkevt_list);
    static LIST_HEAD(gptc_wdt_list);
    
    leichuan's avatar
    leichuan committed
    static LIST_HEAD(gptc_heartbeat_list);
    
    Ma, Hua's avatar
    Ma, Hua committed
    static LIST_HEAD(gptc_ht_yield_list);
    
    leichuan's avatar
    leichuan committed
    unsigned long gptc_phy_base;
    static bool gptc_clksrc_init;
    
    static DEFINE_PER_CPU(struct gptc_clockevent, gptc_event_device);
    
    static inline struct gptc_clockevent *to_gptc_clockevent(
    	struct clock_event_device *evt)
    {
    	return container_of(evt, struct gptc_clockevent, ce);
    }
    
    static inline struct gptc_timer *clkevt_to_gptc_timer(
    	struct clock_event_device *evt)
    {
    	struct gptc_clockevent *gptce = container_of(evt,
    						struct gptc_clockevent, ce);
    
    	return gptce->timer;
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline struct gptc_timer *clksrc_to_gptc_timer(struct clocksource *src)
    {
    	struct gptc_clocksource *gptcs = container_of(src,
    					struct gptc_clocksource, cs);
    
    	return gptcs->timer;
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline u32 gptc_readl(struct gptc_timer *timer, u32 offs)
    {
    	return ioread32(timer->base + offs);
    }
    
    static inline void gptc_writel(struct gptc_timer *timer, unsigned long val,
    
    			       u32 offs)
    
    leichuan's avatar
    leichuan committed
    {
    	iowrite32(val, timer->base + offs);
    }
    
    static u32 gptc_read_counter(struct gptc_timer *timer)
    {
    
    	return gptc_readl(timer,  GPTC_CNT(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_stop_counter(struct gptc_timer *timer)
    {
    
    	gptc_writel(timer, RUN_STOP, GPTC_RUN(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_reload_counter(struct gptc_timer *timer,
    
    				       unsigned long cycles)
    
    leichuan's avatar
    leichuan committed
    {
    
    	gptc_writel(timer, cycles, GPTC_RLD(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_reset_counter(struct gptc_timer *timer)
    {
    	gptc_reload_counter(timer, 0);
    }
    
    static inline void gptc_start_counter(struct gptc_timer *timer)
    {
    
    	gptc_writel(timer, RUN_EN, GPTC_RUN(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_reload_and_run(struct gptc_timer *timer)
    {
    
    	gptc_writel(timer, RUN_EN | RUN_RELOAD, GPTC_RUN(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_clc_enable(void __iomem *base)
    {
    
    	iowrite32(CLC_SUSPEND | CLC_RMC, base + GPTC_CLC);
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_irq_mask_all(void __iomem *base)
    {
    
    	iowrite32(0x00, base + GPTC_IRNEN);
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_irq_clear_all(void __iomem *base)
    {
    
    	iowrite32(0xff, base + GPTC_IRNCR);
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_irq_mask(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	unsigned long flags;
    	struct gptc *gptc = timer->gptc;
    
    leichuan's avatar
    leichuan committed
    
    
    	spin_lock_irqsave(&gptc->lock, flags);
    	reg = gptc_readl(timer, GPTC_IRNEN);
    
    leichuan's avatar
    leichuan committed
    	reg &= ~BIT(timer->tid * 2);
    
    	gptc_writel(timer, reg, GPTC_IRNEN);
    	spin_unlock_irqrestore(&gptc->lock, flags);
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_irq_unmask(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	unsigned long flags;
    	struct gptc *gptc = timer->gptc;
    
    leichuan's avatar
    leichuan committed
    
    
    	spin_lock_irqsave(&gptc->lock, flags);
    	reg = gptc_readl(timer, GPTC_IRNEN);
    
    leichuan's avatar
    leichuan committed
    	reg |= BIT(timer->tid * 2);
    
    	gptc_writel(timer, reg, GPTC_IRNEN);
    	spin_unlock_irqrestore(&gptc->lock, flags);
    
    leichuan's avatar
    leichuan committed
    }
    
    static inline void gptc_irq_ack(struct gptc_timer *timer)
    {
    
    	gptc_writel(timer, BIT(timer->tid * 2), GPTC_IRNCR);
    
    Ma, Hua's avatar
    Ma, Hua committed
    static inline int gptc_irq_read(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTC_IRNCR);
    	if (reg & (BIT(timer->tid * 2)))
    		return 1;
    	else
    		return 0;
    }
    
    
    leichuan's avatar
    leichuan committed
    static void gptc_enable_32bit_timer(struct gptc_timer *timer)
    {
    	u32 reg;
    
    
    	reg = gptc_readl(timer, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    	reg |= CON_EXT;
    
    	gptc_writel(timer, reg, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static void gptc_count_dir(struct gptc_timer *timer)
    {
    	u32 reg;
    
    
    	reg = gptc_readl(timer, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    	if (timer->dir == GPTC_COUNT_UP)
    		reg |= CON_COUNT_UP;
    	else
    		reg &= ~CON_COUNT_UP;
    
    	gptc_writel(timer, reg, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static void gptc_mode_setup(struct gptc_timer *timer, bool oneshot)
    {
    	u32 reg;
    
    
    	reg = gptc_readl(timer, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    	if (oneshot)
    		reg |= CON_ONESHOT;
    	else
    		reg &= ~CON_ONESHOT;
    
    	gptc_writel(timer, reg, GPTC_CON(timer->tid));
    
    leichuan's avatar
    leichuan committed
    }
    
    static irqreturn_t gptc_timer_interrupt(int irq, void *data)
    {
    	struct gptc_clockevent *gptce = data;
    
    	struct gptc_timer *timer = gptce->timer;
    
    leichuan's avatar
    leichuan committed
    
    
    leichuan's avatar
    leichuan committed
    	gptc_irq_ack(timer);
    
    	gptce->ce.event_handler(&gptce->ce);
    
    leichuan's avatar
    leichuan committed
    	return IRQ_HANDLED;
    }
    
    static int gptc_clkevt_next_event(unsigned long cycles,
    
    				  struct clock_event_device *evt)
    
    leichuan's avatar
    leichuan committed
    {
    	struct gptc_timer *timer = clkevt_to_gptc_timer(evt);
    
    	WARN_ON(cycles == 0);
    	gptc_stop_counter(timer);
    	gptc_mode_setup(timer, true);
    	gptc_reload_counter(timer, cycles);
    	gptc_reload_and_run(timer);
    	return 0;
    }
    
    static int gptc_clkevt_shutdown(struct clock_event_device *evt)
    {
    	struct gptc_timer *timer = clkevt_to_gptc_timer(evt);
    
    	gptc_stop_counter(timer);
    	gptc_reset_counter(timer);
    	return 0;
    }
    
    static int gptc_clkevt_periodic(struct clock_event_device *evt)
    {
    	struct gptc_clockevent *gptce = to_gptc_clockevent(evt);
    
    	struct gptc_timer *timer = gptce->timer;
    
    leichuan's avatar
    leichuan committed
    
    	gptc_stop_counter(timer);
    	gptc_mode_setup(timer, false);
    	gptc_reload_counter(timer, gptce->ticks_per_jiffy);
    	gptc_start_counter(timer);
    	return 0;
    }
    
    static int gptc_clkevt_resume(struct clock_event_device *evt)
    {
    	struct gptc_timer *timer = clkevt_to_gptc_timer(evt);
    
    	gptc_start_counter(timer);
    	return 0;
    }
    
    static cycle_t gptc_hpt_read(struct clocksource *cs)
    {
    	struct gptc_timer *timer = clksrc_to_gptc_timer(cs);
    
    	return (cycle_t)gptc_read_counter(timer);
    }
    
    
    static void gptc_global_init(struct gptc *gptc)
    
    leichuan's avatar
    leichuan committed
    {
    
    	gptc_clc_enable(gptc->base);
    	gptc_irq_mask_all(gptc->base);
    	gptc_irq_clear_all(gptc->base);
    
    leichuan's avatar
    leichuan committed
    }
    
    static void gptc_per_timer_init(struct gptc_timer *timer)
    {
    	gptc_count_dir(timer);
    	gptc_enable_32bit_timer(timer);
    
    	if (timer->type == TIMER_TYPE_HEARTBEAT)
    		gptc_reload_counter(timer, 1); /* TODO for interval */
    	else
    		gptc_reset_counter(timer);
    
    leichuan's avatar
    leichuan committed
    	if (timer->type == TIMER_TYPE_CLK_SRC ||
    	    timer->type == TIMER_TYPE_HEARTBEAT)
    
    leichuan's avatar
    leichuan committed
    		gptc_reload_and_run(timer);
    }
    
    
    static const char *const timer_type_to_str(u32 type)
    
    	switch (type) {
    	case TIMER_TYPE_CLK_SRC:
    
    leichuan's avatar
    leichuan committed
    		return "src";
    
    
    	case TIMER_TYPE_CLK_EVT:
    
    leichuan's avatar
    leichuan committed
    		return "event";
    
    
    	case TIMER_TYPE_WDT:
    
    leichuan's avatar
    leichuan committed
    		return "wdt";
    
    leichuan's avatar
    leichuan committed
    	case TIMER_TYPE_HEARTBEAT:
    		return "heartbeat";
    
    Ma, Hua's avatar
    Ma, Hua committed
    	case TIMER_TYPE_HT_YIELD:
    		return "ht_yield";
    
    
    	default:
    		return "none";
    	}
    
    static void gptc_of_config_print(struct gptc *gptc)
    
    leichuan's avatar
    leichuan committed
    {
    
    leichuan's avatar
    leichuan committed
    	struct gptc_timer *timer;
    
    
    	pr_debug("GPTC%d timer list info\n", gptc->id);
    	list_for_each_entry(timer, &gptc->parent, child) {
    		pr_debug("timer%d base %p gptcid %d freq %d tid %d cpuid %d irq %d clk %s\n",
    
    			 i, timer->base, timer->gptcid, timer->frequency,
    			 timer->tid, timer->cpuid, timer->irq,
    			 timer_type_to_str(timer->type));
    
    static int gptc_clock_init(struct gptc *gptc)
    
    leichuan's avatar
    leichuan committed
    {
    
    	struct device_node *np = gptc->np;
    
    leichuan's avatar
    leichuan committed
    
    
    	gptc->gateclk = of_clk_get_by_name(np, "gptc");
    	if (IS_ERR_OR_NULL(gptc->gateclk)) {
    		pr_err("Failed to get gptc gate clk: %ld\n",
    
    		       PTR_ERR(gptc->gateclk));
    		return gptc->gateclk ? PTR_ERR(gptc->gateclk) : -ENODEV;
    
    leichuan's avatar
    leichuan committed
    
    
    	gptc->freqclk = of_clk_get_by_name(np, "freq");
    	if (IS_ERR_OR_NULL(gptc->freqclk)) {
    
    		pr_err("Failed to get gptc frequency clk: %ld\n",
    
    		       PTR_ERR(gptc->freqclk));
    		return gptc->freqclk ? PTR_ERR(gptc->freqclk) : -ENODEV;
    
    	}
    	return 0;
    }
    
    static void gptc_clock_deinit(struct gptc *gptc)
    {
    	gptc->gateclk = NULL;
    
    	gptc->freqclk = NULL;
    
    }
    
    static int gptc_clock_enable(struct gptc *gptc)
    {
    	int ret;
    
    leichuan's avatar
    leichuan committed
    
    
    	if (IS_ERR_OR_NULL(gptc->gateclk) ||
    
    	    IS_ERR_OR_NULL(gptc->freqclk)) {
    
    		pr_err("%s clock(s) is/are not initialized\n", __func__);
    		ret = -EIO;
    		goto out;
    
    leichuan's avatar
    leichuan committed
    	}
    
    	ret = clk_prepare_enable(gptc->gateclk);
    
    leichuan's avatar
    leichuan committed
    	if (ret) {
    
    		pr_err("%s failed to enable gate clk: %d\n", __func__, ret);
    		goto out;
    
    	ret = clk_prepare_enable(gptc->freqclk);
    
    	if (ret) {
    		pr_err("%s failed to enable fpi clk: %d\n", __func__, ret);
    		goto err_gateclk_disable;
    
    leichuan's avatar
    leichuan committed
    	}
    
    	gptc->fpifreq = clk_get_rate(gptc->freqclk);
    
    leichuan's avatar
    leichuan committed
    
    
    	return 0;
    err_gateclk_disable:
    	clk_disable_unprepare(gptc->gateclk);
    out:
    	return ret;
    }
    
    leichuan's avatar
    leichuan committed
    
    
    static void gptc_clock_disable(struct gptc *gptc)
    {
    	if (!IS_ERR_OR_NULL(gptc->gateclk))
    		clk_disable_unprepare(gptc->gateclk);
    
    	if (!IS_ERR_OR_NULL(gptc->freqclk))
    		clk_disable_unprepare(gptc->freqclk);
    
    leichuan's avatar
    leichuan committed
    
    
    static void __gptc_release(struct kref *ref)
    {
    	struct gptc *gptc = container_of(ref, struct gptc, ref);
    
    	gptc_clock_disable(gptc);
    	list_del(&gptc->next);
    	kfree(gptc);
    }
    
    static int gptc_get(struct gptc *gptc)
    {
    	if (!gptc)
    		return 0;
    	kref_get(&gptc->ref);
    	return 1;
    }
    
    static void gptc_put(struct gptc *gptc)
    {
    	if (!gptc)
    		return;
    
    	kref_put(&gptc->ref, __gptc_release);
    }
    
    static int gptc_of_parse_timer(struct gptc *gptc)
    {
    	u32 type;
    	struct of_phandle_args clkspec;
    	int index, ret, nr_timers;
    	struct gptc_timer *timer;
    	u32 tid;
    	u32 cpuid;
    	struct device_node *np = gptc->np;
    
    
    	nr_timers = of_count_phandle_with_args(np, "intel,clk", "#gptc-cells");
    
    	if (nr_timers <= 0) {
    
    		pr_err("gptc%d: invalid value of phandler property at %s\n",
    
    		       gptc->id, np->full_name);
    
    		return -ENODEV;
    
    	pr_debug("%s nr_timers %d available\n", __func__, nr_timers);
    	for (index = 0; index < nr_timers; index++) {
    
    		ret = of_parse_phandle_with_args(np, "intel,clk", "#gptc-cells",
    						 index, &clkspec);
    
    		if (ret < 0)
    			return ret;
    		pr_debug("%s args_count %d arg[0] %d arg[1] %d arg[2] %d\n",
    
    			 __func__, clkspec.args_count, clkspec.args[0],
    			 clkspec.args[1], clkspec.args[2]);
    
    
    		if (clkspec.args_count != 3) {
    			pr_err("%s: invalid gptc clk property\n", __func__);
    			return -EINVAL;
    
    		type = clkspec.args[0];
    		tid = clkspec.args[1];
    		cpuid = clkspec.args[2];
    
    		/* Ignore CPU id check */
    		if (type > TIMER_TYPE_MAX || tid > (TIMER_PER_GPTC - 1)) {
    			pr_err("%s invalid clk type %d or timer id %d\n",
    
    			       __func__, type, tid);
    
    			return -EINVAL;
    
    		timer = kzalloc(sizeof(*timer), GFP_KERNEL);
    		if (!timer)
    			return -ENOMEM;
    
    		INIT_LIST_HEAD(&timer->child);
    		timer->gptc = gptc;
    		timer->base = gptc->base;
    		timer->phy_base = gptc->phy_base;
    		timer->gptcid = gptc->id;
    		timer->tid = tid;
    		timer->type = type;
    		timer->frequency = gptc->fpifreq;
    		timer->used = false;
    
    		timer->irq_registered = false;
    
    Ma, Hua's avatar
    Ma, Hua committed
    		timer->call_back = NULL;
    
    		list_add_tail(&timer->child, &gptc->parent);
    		switch (type) {
    		case TIMER_TYPE_CLK_SRC:
    			INIT_LIST_HEAD(&timer->clksrc);
    			timer->irq = 0;
    			timer->dir = GPTC_COUNT_UP;
    			timer->cpuid = 0;
    			list_add_tail(&timer->clksrc, &gptc_clksrc_list);
    			break;
    
    leichuan's avatar
    leichuan committed
    		case TIMER_TYPE_HEARTBEAT:
    			INIT_LIST_HEAD(&timer->heartbeat);
    			timer->irq = 0;
    
    			timer->dir = GPTC_COUNT_DOWN;
    
    leichuan's avatar
    leichuan committed
    			timer->cpuid = 0;
    			list_add_tail(&timer->heartbeat, &gptc_heartbeat_list);
    			break;
    
    		case TIMER_TYPE_CLK_EVT:
    		case TIMER_TYPE_WDT:
    
    leichuan's avatar
    leichuan committed
    			timer->irq = irq_of_parse_and_map(np, timer->tid);
    
    			WARN_ON(timer->irq <= 0);
    
    leichuan's avatar
    leichuan committed
    			timer->dir = GPTC_COUNT_DOWN;
    
    			timer->cpuid = cpuid;
    			if (type == TIMER_TYPE_CLK_EVT) {
    				INIT_LIST_HEAD(&timer->clkevt);
    				list_add_tail(&timer->clkevt,
    
    					      &gptc_clkevt_list);
    
    			} else {
    				INIT_LIST_HEAD(&timer->wdt);
    				list_add_tail(&timer->wdt, &gptc_wdt_list);
    			}
    			break;
    
    Ma, Hua's avatar
    Ma, Hua committed
    		case TIMER_TYPE_HT_YIELD:
    			timer->irq = irq_of_parse_and_map(np, timer->tid);
    			timer->dir = GPTC_COUNT_DOWN;
    			timer->cpuid = 0;
    			list_add_tail(&timer->ht_yield, &gptc_ht_yield_list);
    
    leichuan's avatar
    leichuan committed
    	}
    
    leichuan's avatar
    leichuan committed
    
    
    static int gptc_of_init(struct device_node *np)
    {
    	int ret;
    	u32 gptcid;
    	struct resource res;
    	void __iomem *base;
    	struct gptc *gptc;
    
    	/* Which GPTC is being handled */
    	gptcid = of_alias_get_id(np, "timer");
    	if (gptcid >= (GPTC_MAX - 1))
    		return -EINVAL;
    
    	ret = of_address_to_resource(np, 0, &res);
    	if (WARN_ON(ret))
    		return ret;
    
    	base = of_iomap(np, 0);
    	if (!base) {
    		pr_err("Can't map GPTC base address\n");
    		return -ENXIO;
    
    leichuan's avatar
    leichuan committed
    	}
    
    	gptc = kzalloc(sizeof(*gptc), GFP_KERNEL);
    	if (!gptc)
    		goto err_iomap;
    
    	INIT_LIST_HEAD(&gptc->parent);
    	INIT_LIST_HEAD(&gptc->next);
    	spin_lock_init(&gptc->lock);
    	kref_init(&gptc->ref);
    	gptc->np = np;
    	gptc->id = gptcid;
    	gptc->base = base;
    	gptc->phy_base = res.start;
    
    	ret = gptc_clock_init(gptc);
    	if (ret)
    		goto err_clk_init;
    
    	ret = gptc_clock_enable(gptc);
    	if (ret)
    		goto err_clk_en;
    
    	ret = gptc_of_parse_timer(gptc);
    	if (ret)
    		goto err_parse_fail;
    
    	list_add_tail(&gptc->next, &gptc_list);
    
    leichuan's avatar
    leichuan committed
    
    	/* GPTC level initialization */
    
    	gptc_global_init(gptc);
    	gptc_of_config_print(gptc);
    
    leichuan's avatar
    leichuan committed
    	return 0;
    
    err_parse_fail:
    	gptc_clock_disable(gptc);
    err_clk_en:
    	gptc_clock_deinit(gptc);
    err_clk_init:
    	kfree(gptc);
    err_iomap:
    
    leichuan's avatar
    leichuan committed
    	iounmap(base);
    	return ret;
    }
    
    static struct gptc_clocksource gptc_clksrc = {
    	.cs = {
    		.name = "gptc",
    		.read = gptc_hpt_read,
    		.mask = CLOCKSOURCE_MASK(32),
    		.flags = CLOCK_SOURCE_IS_CONTINUOUS
    			| CLOCK_SOURCE_SUSPEND_NONSTOP,
    
    leichuan's avatar
    leichuan committed
    cycle_t gptc_read_count(void)
    
    leichuan's avatar
    leichuan committed
    {
    	return gptc_hpt_read(&gptc_clksrc.cs);
    }
    
    
    leichuan's avatar
    leichuan committed
    #ifdef CONFIG_GPTC_SCHED_CLOCK
    
    leichuan's avatar
    leichuan committed
    static unsigned long sched_clock_mult __read_mostly;
    static cycle_t cycle_last, cycle_offset;
    static DEFINE_SPINLOCK(gptc_shed_lock);
    
    unsigned long long notrace sched_clock(void)
    {
    	cycle_t cycle;
    	unsigned long flags;
    
    	if (!gptc_clksrc_init)
    
    leichuan's avatar
    leichuan committed
    		return (jiffies_64 - INITIAL_JIFFIES) * (1000000000 / HZ);
    
    leichuan's avatar
    leichuan committed
    
    	spin_lock_irqsave(&gptc_shed_lock, flags);
    	cycle = gptc_read_count();
    	cycle &= gptc_clksrc.cs.mask;
    	/* Counter wrapped */
    	if (unlikely(cycle_last > cycle))
    		cycle_offset += BIT_ULL(32);
    
    	cycle_last = cycle;
    	cycle += cycle_offset;
    	spin_unlock_irqrestore(&gptc_shed_lock, flags);
    
    	return cycle * sched_clock_mult;
    }
    
    leichuan's avatar
    leichuan committed
    #endif /* CONFIG_GPTC_SCHED_CLOCK */
    
    #ifndef CONFIG_X86
    
    leichuan's avatar
    leichuan committed
    static u64 __maybe_unused notrace gptc_read_sched_clock(void)
    {
    	return (u64)gptc_read_count();
    }
    #endif /* CONFIG_X86 */
    
    static int gptc_clocksource_init(void)
    {
    
    leichuan's avatar
    leichuan committed
    	struct gptc_timer *timer;
    
    
    	list_for_each_entry(timer, &gptc_clksrc_list, clksrc) {
    		if (!timer->used) {
    			/* Only one clock source from GPTC allowed */
    
    leichuan's avatar
    leichuan committed
    			if (gptc_clksrc_init)
    				return -EEXIST;
    			/* Record for VDSO */
    			gptc_phy_base = timer->phy_base;
    
    			gptc_clksrc.timer = timer;
    
    leichuan's avatar
    leichuan committed
    			/*
    			 * Calculate a somewhat reasonable rating value
    			 * in 10MHz
    			 */
    			gptc_clksrc.cs.rating =
    
    				250 + timer->frequency / 10000000;
    
    leichuan's avatar
    leichuan committed
    			gptc_per_timer_init(timer);
    			ret = clocksource_register_hz(&gptc_clksrc.cs,
    
    						      timer->frequency);
    
    leichuan's avatar
    leichuan committed
    			if (ret < 0)
    				pr_warn("GPTC: Unable to register clocksource\n");
    
    leichuan's avatar
    leichuan committed
    		#ifndef CONFIG_X86
    
    leichuan's avatar
    leichuan committed
    			sched_clock_register(gptc_read_sched_clock,
    
    					     32, timer->frequency);
    
    leichuan's avatar
    leichuan committed
    		#endif /* CONFIG_X86 */
    
    		#ifdef CONFIG_GPTC_SCHED_CLOCK
    			sched_clock_mult = NSEC_PER_SEC / timer->frequency;
    		#endif /* CONFIG_GPTC_SCHED_CLOCK */
    
    leichuan's avatar
    leichuan committed
    			timer->used = true;
    			gptc_clksrc_init = true;
    
    			gptc_get(timer->gptc);
    
    leichuan's avatar
    leichuan committed
    			pr_debug("gptc %d timer %d clk src register @cpu%d\n",
    
    				 timer->gptcid, timer->tid, timer->cpuid);
    
    leichuan's avatar
    leichuan committed
    			return 0;
    		}
    	}
    	return -EINVAL;
    }
    
    static struct clock_event_device gptc_per_timer_clockevent = {
    	.name = "gptc_clockevent",
    	.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC,
    
    leichuan's avatar
    leichuan committed
    	.set_state_shutdown = gptc_clkevt_shutdown,
    	.set_state_periodic = gptc_clkevt_periodic,
    	.set_state_oneshot = gptc_clkevt_shutdown,
    	.set_next_event = gptc_clkevt_next_event,
    	.tick_resume = gptc_clkevt_resume,
    	.irq = 0,
    };
    
    static int gptc_clockevent_cpu_init(unsigned int cpu,
    
    				    struct gptc_clockevent *cd)
    
    leichuan's avatar
    leichuan committed
    {
    	struct gptc_timer *timer;
    
    	struct clock_event_device *ce;
    
    leichuan's avatar
    leichuan committed
    
    	if (!cd)
    		return -EINVAL;
    
    
    leichuan's avatar
    leichuan committed
    
    
    	list_for_each_entry(timer, &gptc_clkevt_list, clkevt) {
    		if (!timer->used && (timer->cpuid == cpu)) {
    
    			memcpy(ce, &gptc_per_timer_clockevent, sizeof(*ce));
    			ce->irq = timer->irq;
    			ce->cpumask = cpumask_of(cpu);
    			ce->name = cd->name;
    
    leichuan's avatar
    leichuan committed
    			gptc_per_timer_init(timer);
    
    			clockevents_config_and_register(ce, timer->frequency,
    
    							0x64, 0x7FFFFFFF);
    
    leichuan's avatar
    leichuan committed
    			gptc_irq_unmask(timer);
    
    			gptc_get(timer->gptc);
    
    leichuan's avatar
    leichuan committed
    			timer->used = true;
    			pr_debug("gptc %d timer %d clk evt register @cpu%d\n",
    
    				 timer->gptcid, timer->tid, timer->cpuid);
    
    leichuan's avatar
    leichuan committed
    			return 0;
    		}
    	}
    	return -EINVAL;
    }
    
    static int gptc_clockevent_cpu_exit(struct gptc_clockevent *cd)
    {
    	struct clock_event_device *levt;
    	struct gptc_timer *timer;
    
    	struct gptc *gptc;
    
    leichuan's avatar
    leichuan committed
    
    	if (!cd)
    		return -EINVAL;
    
    	levt = &cd->ce;
    
    	timer = cd->timer;
    
    leichuan's avatar
    leichuan committed
    
    	if (!timer)
    		return -EINVAL;
    
    	if (levt->irq) {
    		free_irq(levt->irq, cd);
    			levt->irq = 0;
    	}
    
    	gptc_irq_mask(timer);
    
    	list_del(&timer->clkevt);
    	gptc = timer->gptc;
    	kfree(timer);
    	gptc_put(gptc);
    
    leichuan's avatar
    leichuan committed
    	return 0;
    }
    
    
    static void gptc_clkevt_irq_init(void)
    {
    	int ret;
    	int cpu;
    	struct gptc_timer *timer;
    	struct gptc_clockevent *gptc_evt;
    
    	for_each_possible_cpu(cpu) {
    		gptc_evt = per_cpu_ptr(&gptc_event_device, cpu);
    		list_for_each_entry(timer, &gptc_clkevt_list, clkevt) {
    			if (!timer->irq_registered && (timer->cpuid == cpu)) {
    				gptc_evt->timer = timer;
    				gptc_evt->ticks_per_jiffy =
    					DIV_ROUND_UP(timer->frequency, HZ);
    				snprintf(gptc_evt->name, sizeof(gptc_evt->name),
    					 "gptc_event%d", cpu);
    				ret = request_irq(timer->irq,
    						  gptc_timer_interrupt,
    						  IRQF_TIMER | IRQF_NOBALANCING,
    						  gptc_evt->name, gptc_evt);
    				if (ret) {
    					pr_err("gptc irq %d register failed @cpu%d\n",
    					       timer->irq, cpu);
    					break;
    				}
    				irq_set_affinity(timer->irq, cpumask_of(cpu));
    				timer->irq_registered = true;
    				pr_debug("gptc %d timer %d irq %d register @cpu%d\n",
    					 timer->gptcid, timer->tid, timer->irq,
    					 timer->cpuid);
    			}
    		}
    	}
    }
    
    
    leichuan's avatar
    leichuan committed
    static int gptc_starting_cpu(unsigned int cpu)
    {
    
    	gptc_clockevent_cpu_init(cpu, this_cpu_ptr(&gptc_event_device));
    	return 0;
    
    leichuan's avatar
    leichuan committed
    }
    
    static int gptc_dying_cpu(unsigned int cpu)
    {
    
    	gptc_clockevent_cpu_exit(this_cpu_ptr(&gptc_event_device));
    	return 0;
    
    leichuan's avatar
    leichuan committed
    }
    
    static void gptc_clkevent_init(void)
    {
    
    leichuan's avatar
    leichuan committed
    	cpuhp_setup_state(CPUHP_AP_INTEL_GPTC_TIMER_STARTING,
    
    			  "AP_INTEL_GPTC_TIMER_STARTING", gptc_starting_cpu,
    			  gptc_dying_cpu);
    
    leichuan's avatar
    leichuan committed
    }
    
    static int __init gptc_timer_init(struct device_node *np)
    {
    
    	gptc_of_init(np);
    
    leichuan's avatar
    leichuan committed
    
    	gptc_clocksource_init();
    
    	/* Register immediately the clock event on BSP */
    	gptc_clkevent_init();
    #ifdef CONFIG_X86
    	global_clock_event = &gptc_per_timer_clockevent;
    #endif
    	return 0;
    }
    
    leichuan's avatar
    leichuan committed
    CLOCKSOURCE_OF_DECLARE(lantiq_gptc_timer, "lantiq,gptc", gptc_timer_init);
    CLOCKSOURCE_OF_DECLARE(intel_gptc_timer, "intel,gptc", gptc_timer_init);
    
    leichuan's avatar
    leichuan committed
    static int __init gptc_heartbeat_init(void)
    {
    	struct gptc_timer *timer;
    
    	list_for_each_entry(timer, &gptc_heartbeat_list, heartbeat) {
    		if (!timer->used) {
    			gptc_per_timer_init(timer);
    			timer->used = true;
    			gptc_get(timer->gptc);
    			pr_debug("gptc %d timer %d heartbeat register @cpu%d\n",
    				 timer->gptcid, timer->tid, timer->cpuid);
    			return 0;
    		}
    	}
    	return -EINVAL;
    }
    arch_initcall(gptc_heartbeat_init);
    
    
    Ma, Hua's avatar
    Ma, Hua committed
    #ifdef CONFIG_LTQ_VMB
    #define GPTC_TC_THREAD_STACK_SIZE 4096
    #define GPTC_TC_THREAD_STACK_RESERVED_SIZE (32 + sizeof(struct pt_regs))
    
    static u8 tc_thread_stack[GPTC_TC_THREAD_STACK_SIZE] __aligned(16);
    static u8 tc_thread_gp[GPTC_TC_THREAD_STACK_SIZE] __aligned(16);
    
    static void tc_thread(u32 arg0, u32 arg1)
    {
    	u32 mask, yqmask;
    	struct gptc_timer *timer = (struct gptc_timer *)arg0;
    
    	/* init the yq mask */
    	mask = (1 << timer->yield_pin);
    	yqmask = read_c0_yqmask();
    	yqmask |= mask;
    	write_c0_yqmask(yqmask);
    	ehb();
    
    	while (1) {
    		mips_mt_yield(mask);
    
    		if (gptc_irq_read(timer)) {
    			gptc_irq_ack(timer);
    			gic_clear_edge(timer->irq);
    
    			/* Do the call back stuff */
    			if (timer->call_back)
    				timer->call_back(timer->call_back_param);
    		}
    	}
    }
    
    static irqreturn_t tc_dummy_isr(int irq, void *dev_id)
    {
    	return IRQ_HANDLED;
    }
    
    int gptc_ht_yield_init(struct gptc_ht_yield *param, void *call_back,
    		       void *call_back_param)
    {
    	struct gptc_timer *timer;
    	int tc_num, cpu;
    	unsigned long cycles, interval;
    	struct TC_launch_t tc_launch;
    	int ret;
    
    	if (!param || !call_back ||
    	    param->interval == 0) {
    		pr_err("Bad parameter.\n");
    		return -EINVAL;
    	}
    
    	list_for_each_entry(timer, &gptc_ht_yield_list, ht_yield) {
    		if (!timer->used) {
    			cpu = smp_processor_id();
    			gptc_per_timer_init(timer);
    			timer->used = true;
    			gptc_get(timer->gptc);
    			timer->call_back = call_back;
    			timer->call_back_param = call_back_param;