Skip to content
Snippets Groups Projects
intel-gptc-timer.c 17.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • leichuan's avatar
    leichuan committed
    /*******************************************************************************
     *
     *  Copyright(c) 2016 Intel Corporation.
     *
     *  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.
     *
     *  You should have received a copy of the GNU General Public License along with
     *  this program; if not, write to the Free Software Foundation, Inc.,
     *  51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
     *
     *  The full GNU General Public License is included in this distribution in
     *  the file called "COPYING".
     *
     ******************************************************************************/
    #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 <asm/time.h>
    
    #define MS(_v, _f)	(((_v) & (_f)) >> _f##_S)
    #define SM(_v, _f)	(((_v) << _f##_S) & (_f))
    
    #define GPTU_CLC	0x00
    #define CLC_DIS		BIT(0)
    #define CLC_SUSPEND	BIT(4)
    #define CLC_RMC		BIT(8)
    #define CLC_SMC		BIT(16)
    
    #define GPTU_ID		0x08
    #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 GPTU_CON(X)	(0x10 + (X) * 0x20)
    #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 GPTU_RUN(X)	(0x18 + (X) * 0x20)
    #define RUN_EN		BIT(0)
    #define RUN_STOP	BIT(1)
    #define RUN_RELOAD	BIT(2)
    
    #define GPTU_RLD(X)	(0x20 + (X) * 0x20)
    #define GPTU_CNT(X)	(0x28 + (X) * 0x20)
    
    #define GPTU_IRNEN	0xf4
    #define GPTU_IRCR	0xf8
    #define GPTU_IRNCR	0xfc
    
    #define GPTU_MAX_TIMER	9 /* 3 GPTCs x 3 maximum */
    #define TIMER_PER_GPTC	3
    
    enum {
    	GPTC0 = 0,
    #define GPTC_MIN	GPTC0
    	GPTC1,
    	GPTC2,
    #define GPTC_MAX	GPTC2
    };
    
    enum gptc_cnt_dir {
    	GPTC_COUNT_DOWN = 0,
    	GPTC_COUNT_UP,
    };
    
    struct gptc_timer {
    	void __iomem *base;
    	unsigned long phy_base;
    	u32 gptcid;
    	u32 tid; /* 0, 1, 2 only */
    	u32 cpuid;
    	u32 irq;
    #define CLK_SRC_FLAG	0x01
    #define CLK_EVT_FLAG	0x02
    
    leichuan's avatar
    leichuan committed
    #define CLK_WDT_FLAG	0x04
    
    leichuan's avatar
    leichuan committed
    	u32 flags;
    	u32 frequency;
    	enum gptc_cnt_dir dir;
    	bool used;
    };
    
    struct gptc_clocksource {
    	struct gptc_timer  *gptc;
    	struct clocksource cs;
    };
    
    struct gptc_clockevent {
    	struct gptc_timer *gptc;
    	u32 ticks_per_jiffy;
    	struct clock_event_device ce;
    	char name[16];
    };
    
    unsigned long gptc_phy_base;
    static bool gptc_clksrc_init;
    static unsigned int gptc_timer_count;
    static struct gptc_timer gptc_timer_info[GPTU_MAX_TIMER];
    
    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->gptc;
    }
    
    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->gptc;
    }
    
    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)
    {
    	iowrite32(val, timer->base + offs);
    }
    
    static u32 gptc_read_counter(struct gptc_timer *timer)
    {
    	return gptc_readl(timer,  GPTU_CNT(timer->tid));
    }
    
    static inline void gptc_stop_counter(struct gptc_timer *timer)
    {
    	gptc_writel(timer, RUN_STOP, GPTU_RUN(timer->tid));
    }
    
    static inline void gptc_reload_counter(struct gptc_timer *timer,
    	unsigned long cycles)
    {
    	gptc_writel(timer, cycles, GPTU_RLD(timer->tid));
    }
    
    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, GPTU_RUN(timer->tid));
    }
    
    static inline void gptc_reload_and_run(struct gptc_timer *timer)
    {
    	gptc_writel(timer, RUN_EN | RUN_RELOAD, GPTU_RUN(timer->tid));
    }
    
    static inline void gptc_clc_enable(void __iomem *base)
    {
    	iowrite32(CLC_SUSPEND | CLC_RMC, base + GPTU_CLC);
    }
    
    static inline void gptc_irq_mask_all(void __iomem *base)
    {
    	iowrite32(0x00, base + GPTU_IRNEN);
    }
    
    static inline void gptc_irq_clear_all(void __iomem *base)
    {
    	iowrite32(0xff, base + GPTU_IRNCR);
    }
    
    static inline void gptc_irq_mask(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTU_IRNEN);
    	reg &= ~BIT(timer->tid * 2);
    	gptc_writel(timer, reg, GPTU_IRNEN);
    }
    
    static inline void gptc_irq_unmask(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTU_IRNEN);
    	reg |= BIT(timer->tid * 2);
    	gptc_writel(timer, reg, GPTU_IRNEN);
    }
    
    static inline void gptc_irq_ack(struct gptc_timer *timer)
    {
    	gptc_writel(timer, BIT(timer->tid * 2), GPTU_IRNCR);
    }
    
    static void gptc_enable_32bit_timer(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTU_CON(timer->tid));
    	reg |= CON_EXT;
    	gptc_writel(timer, reg, GPTU_CON(timer->tid));
    }
    
    static void gptc_count_dir(struct gptc_timer *timer)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTU_CON(timer->tid));
    	if (timer->dir == GPTC_COUNT_UP)
    		reg |= CON_COUNT_UP;
    	else
    		reg &= ~CON_COUNT_UP;
    	gptc_writel(timer, reg, GPTU_CON(timer->tid));
    }
    
    static void gptc_mode_setup(struct gptc_timer *timer, bool oneshot)
    {
    	u32 reg;
    
    	reg = gptc_readl(timer, GPTU_CON(timer->tid));
    	if (oneshot)
    		reg |= CON_ONESHOT;
    	else
    		reg &= ~CON_ONESHOT;
    	gptc_writel(timer, reg, GPTU_CON(timer->tid));
    }
    
    static irqreturn_t gptc_timer_interrupt(int irq, void *data)
    {
    	struct gptc_clockevent *gptce = data;
    	struct gptc_timer *timer = gptce->gptc;
    
    	gptc_irq_ack(timer);
    
    	gptce->ce.event_handler(&gptce->ce);
    	return IRQ_HANDLED;
    }
    
    static int gptc_clkevt_next_event(unsigned long cycles,
    					struct clock_event_device *evt)
    {
    	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->gptc;
    
    	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(void __iomem *base)
    {
    	gptc_clc_enable(base);
    	gptc_irq_mask_all(base);
    	gptc_irq_clear_all(base);
    }
    
    static void gptc_per_timer_init(struct gptc_timer *timer)
    {
    	gptc_count_dir(timer);
    	gptc_enable_32bit_timer(timer);
    	gptc_reset_counter(timer);
    	if ((timer->flags & CLK_SRC_FLAG))
    		gptc_reload_and_run(timer);
    }
    
    
    leichuan's avatar
    leichuan committed
    static const char *const gptc_flag_to_str(u32 flags)
    {
    	if (flags & CLK_SRC_FLAG)
    		return "src";
    
    	if (flags & CLK_EVT_FLAG)
    		return "event";
    
    	if (flags & CLK_WDT_FLAG)
    		return "wdt";
    	return "none";
    }
    
    
    leichuan's avatar
    leichuan committed
    static void gptc_print_config(void)
    {
    	int i;
    	struct gptc_timer *timer;
    
    	pr_debug("GPTC timer list info\n");
    	for (i = 0; i < gptc_timer_count; i++) {
    		timer = &gptc_timer_info[i];
    		pr_debug("timer%d base %p gptcid %d freq %d tid %d cpuid %d irq %d clk %s %s\n",
    			i, timer->base, timer->gptcid, timer->frequency,
    			timer->tid, timer->cpuid, timer->irq,
    
    leichuan's avatar
    leichuan committed
    			gptc_flag_to_str(timer->flags),
    
    leichuan's avatar
    leichuan committed
    			timer->used ? "used" : "unused");
    	}
    }
    
    static int gptc_timer_of_init(struct device_node *np)
    {
    	int i, ret;
    	u32 prop;
    
    leichuan's avatar
    leichuan committed
    	u32 gptcid;
    	u32 nr_evts = 0;
    	u32 nr_wdts = 0;
    	u32 nr_srcs = 0;
    
    leichuan's avatar
    leichuan committed
    	struct resource res;
    	void __iomem *base;
    	struct gptc_timer *timer;
    	u32 tid[TIMER_PER_GPTC];
    	u32 cpuid[TIMER_PER_GPTC];
    	struct clk *fpiclk, *gateclk;
    	u32 freq;
    
    	/* Which GPTC is being handled */
    	gptcid = of_alias_get_id(np, "timer");
    	if (gptcid < GPTC_MIN || gptcid > GPTC_MAX)
    		return -EINVAL;
    
    	ret = of_address_to_resource(np, 0, &res);
    	if (WARN_ON(ret))
    		return ret;
    
    	gateclk = of_clk_get_by_name(np, "gptc");
    	if (IS_ERR(gateclk)) {
    		pr_err("Failed to get gptc gate clock\n");
    		ret = PTR_ERR(gateclk);
    		goto err_gateclk;
    	}
    	ret = clk_prepare_enable(gateclk);
    	if (ret) {
    		pr_err("GPTC failed to enable clock\n");
    		goto err_clken;
    	}
    
    	fpiclk = of_clk_get_by_name(np, "fpi");
    	if (IS_ERR(fpiclk)) {
    		pr_err("Failed to get gptc frequency clock\n");
    		ret = PTR_ERR(fpiclk);
    		goto err_fpiclk;
    	}
    
    	clk_prepare(fpiclk);
    	freq = clk_get_rate(fpiclk);
    
    	if (!of_property_read_bool(np, "intel,clksrc") &&
    
    leichuan's avatar
    leichuan committed
    		!of_property_read_bool(np, "intel,clkevt") &&
    		!of_property_read_bool(np, "intel,clkwdt")) {
    
    leichuan's avatar
    leichuan committed
    		ret = -EEXIST;
    
    leichuan's avatar
    leichuan committed
    		goto err_fpipre;
    	}
    
    	base = of_iomap(np, 0);
    	if (!base) {
    		pr_err("Can't map GPTC base address\n");
    		goto err_fpipre;
    
    leichuan's avatar
    leichuan committed
    	}
    
    	if (!of_property_read_u32(np, "intel,clksrc", &prop)) {
    		if (gptc_clksrc_init)
    			pr_warn("Clock source initialized already!!!\n");
    		timer = &gptc_timer_info[gptc_timer_count];
    		timer->base = base;
    		timer->phy_base = res.start;
    		timer->gptcid = gptcid;
    		timer->tid = prop;
    		timer->flags |= CLK_SRC_FLAG;
    		timer->irq = 0;
    		timer->frequency = freq;
    		timer->dir = GPTC_COUNT_UP;
    		timer->used = false;
    		gptc_timer_count++;
    
    leichuan's avatar
    leichuan committed
    		nr_srcs++;
    
    leichuan's avatar
    leichuan committed
    	/* Parse clock event list */
    
    leichuan's avatar
    leichuan committed
    	nr_evts = of_property_count_u32_elems(np, "intel,clkevt");
    
    leichuan's avatar
    leichuan committed
    	if ((nr_evts > 0) && (nr_evts <= TIMER_PER_GPTC)) {
    		ret = of_property_read_u32_array(np, "intel,clkevt",
    			tid, nr_evts);
    		if (ret) {
    			pr_err("GPTC%d failed to get clock event timer list\n",
    				gptcid);
    			goto err;
    		}
    
    		ret = of_property_read_u32_array(np, "intel,cpuid",
    			cpuid, nr_evts);
    		if (ret) {
    			pr_err("GPTC%d failed to get cpu id list\n", gptcid);
    			goto err;
    		}
    
    		for (i = 0; i < nr_evts; i++) {
    			timer = &gptc_timer_info[gptc_timer_count];
    			timer->base = base;
    			timer->phy_base = res.start;
    			timer->gptcid = gptcid;
    			timer->tid = tid[i];
    			WARN_ON(timer->tid >= TIMER_PER_GPTC);
    			timer->cpuid = cpuid[i];
    			timer->flags |= CLK_EVT_FLAG;
    			/* Timer ID as index to get irq */
    			timer->irq = irq_of_parse_and_map(np, timer->tid);
    			WARN_ON(timer->irq == 0);
    			timer->frequency = freq;
    			timer->dir = GPTC_COUNT_DOWN;
    			timer->used = false;
    			gptc_timer_count++;
    		}
    
    leichuan's avatar
    leichuan committed
    	/* Parse watchdog timer list */
    	nr_wdts = of_property_count_u32_elems(np, "intel,clkwdt");
    	if ((nr_wdts > 0) && (nr_wdts <= TIMER_PER_GPTC)) {
    		ret = of_property_read_u32_array(np, "intel,clkwdt",
    			tid, nr_wdts);
    		if (ret) {
    			pr_err("GPTC%d failed to get wdt timer list\n", gptcid);
    			goto err;
    		}
    
    		ret = of_property_read_u32_array(np, "intel,wdtid",
    			cpuid, nr_wdts);
    		if (ret) {
    			pr_err("GPTC%d failed to get wdt cpu id list\n",
    				gptcid);
    			goto err;
    		}
    
    		for (i = 0; i < nr_wdts; i++) {
    			timer = &gptc_timer_info[gptc_timer_count];
    			timer->base = base;
    			timer->phy_base = res.start;
    			timer->gptcid = gptcid;
    			timer->tid = tid[i];
    			WARN_ON(timer->tid >= TIMER_PER_GPTC);
    			timer->cpuid = cpuid[i];
    			timer->flags |= CLK_WDT_FLAG;
    			/* Timer ID as index to get irq */
    			timer->irq = irq_of_parse_and_map(np, timer->tid);
    			WARN_ON(timer->irq == 0);
    			timer->frequency = freq;
    			timer->dir = GPTC_COUNT_DOWN;
    			timer->used = false;
    			gptc_timer_count++;
    		}
    
    leichuan's avatar
    leichuan committed
    	pr_debug("GPTC%d nr_srcs %d nr_evts %d nr_wdts %d\n",
    		gptcid, nr_srcs, nr_evts, nr_wdts);
    
    leichuan's avatar
    leichuan committed
    	/* GPTC level initialization */
    	gptc_global_init(base);
    	return 0;
    err:
    	iounmap(base);
    
    leichuan's avatar
    leichuan committed
    err_fpipre:
    
    leichuan's avatar
    leichuan committed
    	clk_unprepare(fpiclk);
    	clk_put(fpiclk);
    err_fpiclk:
    	clk_disable_unprepare(gateclk);
    err_clken:
    	clk_put(gateclk);
    
    err_gateclk:
    	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,
    		.rating = 200,
    	},
    };
    
    static cycle_t gptc_read_count(void)
    {
    	return gptc_hpt_read(&gptc_clksrc.cs);
    }
    
    #ifdef CONFIG_X86
    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)
    		return 0;
    
    	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;
    }
    #else
    static u64 __maybe_unused notrace gptc_read_sched_clock(void)
    {
    	return (u64)gptc_read_count();
    }
    #endif /* CONFIG_X86 */
    
    static int gptc_clocksource_init(void)
    {
    	int i, ret;
    	struct gptc_timer *timer;
    
    	for (i = 0; i < gptc_timer_count; i++) {
    		timer = &gptc_timer_info[i];
    		if (timer->flags & CLK_SRC_FLAG) {
    			/* Only one clock source from GPTC */
    			if (gptc_clksrc_init)
    				return -EEXIST;
    			/* Record for VDSO */
    			gptc_phy_base = timer->phy_base;
    			gptc_clksrc.gptc = timer;
    			/*
    			 * Calculate a somewhat reasonable rating value
    			 * in 10MHz
    			 */
    			gptc_clksrc.cs.rating =
    				200 + timer->frequency / 10000000;
    			gptc_per_timer_init(timer);
    			ret = clocksource_register_hz(&gptc_clksrc.cs,
    				timer->frequency);
    			if (ret < 0)
    				pr_warn("GPTC: Unable to register clocksource\n");
    		#ifdef CONFIG_X86
    			sched_clock_mult = NSEC_PER_SEC / timer->frequency;
    		#else
    			sched_clock_register(gptc_read_sched_clock,
    				32, timer->frequency);
    		#endif /* CONFIG_MIPS */
    			timer->used = true;
    			gptc_clksrc_init = true;
    			pr_debug("gptc %d timer %d clk src register @cpu%d\n",
    				timer->gptcid, timer->tid, timer->cpuid);
    			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,
    	.rating = 200,
    	.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)
    {
    	int i;
    	int ret;
    	struct gptc_timer *timer;
    	struct gptc_clockevent *gptc_clkevt = cd;
    	struct clock_event_device *levt = &cd->ce;
    
    	if (!cd)
    		return -EINVAL;
    
    	gptc_clkevt = cd;
    	levt = &cd->ce;
    
    	for (i = 0; i < gptc_timer_count; i++) {
    		timer = &gptc_timer_info[i];
    		if ((!timer->used) && (timer->cpuid == cpu)
    			&& (timer->flags & CLK_EVT_FLAG)) {
    			gptc_clkevt->gptc = timer;
    			gptc_clkevt->ticks_per_jiffy =
    				DIV_ROUND_UP(timer->frequency, HZ);
    			memcpy(levt, &gptc_per_timer_clockevent, sizeof(*levt));
    			levt->irq = timer->irq;
    			levt->cpumask = cpumask_of(cpu);
    			snprintf(gptc_clkevt->name, sizeof(gptc_clkevt->name),
    				"gptc_event%d", cpu);
    			levt->name = gptc_clkevt->name;
    			gptc_per_timer_init(timer);
    
    			ret = request_irq(levt->irq, gptc_timer_interrupt,
    				IRQF_TIMER, gptc_clkevt->name, gptc_clkevt);
    			if (ret) {
    				pr_err("gptc clkevt register failed @cpu%d\n",
    					cpu);
    				return ret;
    			}
    			irq_set_affinity(levt->irq, cpumask_of(cpu));
    			clockevents_config_and_register(levt,
    				timer->frequency, 0x100, 0x7FFFFFFF);
    			gptc_irq_unmask(timer);
    			timer->used = true;
    			pr_debug("gptc %d timer %d clk evt register @cpu%d\n",
    				timer->gptcid, timer->tid, timer->cpuid);
    			return 0;
    		}
    	}
    	return -EINVAL;
    }
    
    static int gptc_clockevent_cpu_exit(struct gptc_clockevent *cd)
    {
    	struct clock_event_device *levt;
    	struct gptc_timer *timer;
    
    	if (!cd)
    		return -EINVAL;
    
    	levt = &cd->ce;
    	timer = cd->gptc;
    
    	if (!timer)
    		return -EINVAL;
    
    	if (levt->irq) {
    		free_irq(levt->irq, cd);
    			levt->irq = 0;
    	}
    
    	gptc_irq_mask(timer);
    	return 0;
    }
    
    static int gptc_starting_cpu(unsigned int cpu)
    {
    	return gptc_clockevent_cpu_init(cpu, this_cpu_ptr(&gptc_event_device));
    }
    
    static int gptc_dying_cpu(unsigned int cpu)
    {
    	return gptc_clockevent_cpu_exit(this_cpu_ptr(&gptc_event_device));
    }
    
    static void gptc_clkevent_init(void)
    {
    
    leichuan's avatar
    leichuan committed
    	cpuhp_setup_state(CPUHP_AP_INTEL_GPTC_TIMER_STARTING,
    
    leichuan's avatar
    leichuan committed
    		  "AP_INTEL_GPTC_TIMER_STARTING", gptc_starting_cpu,
    		  gptc_dying_cpu);
    }
    
    static int __init gptc_timer_init(struct device_node *np)
    {
    	gptc_timer_of_init(np);
    
    	gptc_clocksource_init();
    
    	/* Register immediately the clock event on BSP */
    	gptc_clkevent_init();
    #ifdef CONFIG_X86
    	global_clock_event = &gptc_per_timer_clockevent;
    #endif
    	gptc_print_config();
    	return 0;
    }
    CLOCKSOURCE_OF_DECLARE(lantiq_gptc_timer, "lantiq,gptc", gptc_timer_init);
    CLOCKSOURCE_OF_DECLARE(intel_gptc_timer, "intel,gptc", gptc_timer_init);