1f0b7fabeSAlexander Shiyan /*
2f0b7fabeSAlexander Shiyan  *  Cirrus Logic CLPS711X clocksource driver
3f0b7fabeSAlexander Shiyan  *
4f0b7fabeSAlexander Shiyan  *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
5f0b7fabeSAlexander Shiyan  *
6f0b7fabeSAlexander Shiyan  * This program is free software; you can redistribute it and/or modify
7f0b7fabeSAlexander Shiyan  * it under the terms of the GNU General Public License as published by
8f0b7fabeSAlexander Shiyan  * the Free Software Foundation; either version 2 of the License, or
9f0b7fabeSAlexander Shiyan  * (at your option) any later version.
10f0b7fabeSAlexander Shiyan  */
11f0b7fabeSAlexander Shiyan 
12f0b7fabeSAlexander Shiyan #include <linux/clk.h>
13f0b7fabeSAlexander Shiyan #include <linux/clockchips.h>
14f0b7fabeSAlexander Shiyan #include <linux/clocksource.h>
15f0b7fabeSAlexander Shiyan #include <linux/interrupt.h>
16f0b7fabeSAlexander Shiyan #include <linux/io.h>
17f0b7fabeSAlexander Shiyan #include <linux/of_address.h>
18f0b7fabeSAlexander Shiyan #include <linux/of_irq.h>
19f0b7fabeSAlexander Shiyan #include <linux/sched_clock.h>
20f0b7fabeSAlexander Shiyan #include <linux/slab.h>
21f0b7fabeSAlexander Shiyan 
22f0b7fabeSAlexander Shiyan enum {
23f0b7fabeSAlexander Shiyan 	CLPS711X_CLKSRC_CLOCKSOURCE,
24f0b7fabeSAlexander Shiyan 	CLPS711X_CLKSRC_CLOCKEVENT,
25f0b7fabeSAlexander Shiyan };
26f0b7fabeSAlexander Shiyan 
27f0b7fabeSAlexander Shiyan static void __iomem *tcd;
28f0b7fabeSAlexander Shiyan 
29f0b7fabeSAlexander Shiyan static u64 notrace clps711x_sched_clock_read(void)
30f0b7fabeSAlexander Shiyan {
31f0b7fabeSAlexander Shiyan 	return ~readw(tcd);
32f0b7fabeSAlexander Shiyan }
33f0b7fabeSAlexander Shiyan 
34f0b7fabeSAlexander Shiyan static int __init _clps711x_clksrc_init(struct clk *clock, void __iomem *base)
35f0b7fabeSAlexander Shiyan {
36f0b7fabeSAlexander Shiyan 	unsigned long rate;
37f0b7fabeSAlexander Shiyan 
38f0b7fabeSAlexander Shiyan 	if (!base)
39f0b7fabeSAlexander Shiyan 		return -ENOMEM;
40f0b7fabeSAlexander Shiyan 	if (IS_ERR(clock))
41f0b7fabeSAlexander Shiyan 		return PTR_ERR(clock);
42f0b7fabeSAlexander Shiyan 
43f0b7fabeSAlexander Shiyan 	rate = clk_get_rate(clock);
44f0b7fabeSAlexander Shiyan 
45f0b7fabeSAlexander Shiyan 	tcd = base;
46f0b7fabeSAlexander Shiyan 
47f0b7fabeSAlexander Shiyan 	clocksource_mmio_init(tcd, "clps711x-clocksource", rate, 300, 16,
48f0b7fabeSAlexander Shiyan 			      clocksource_mmio_readw_down);
49f0b7fabeSAlexander Shiyan 
50f0b7fabeSAlexander Shiyan 	sched_clock_register(clps711x_sched_clock_read, 16, rate);
51f0b7fabeSAlexander Shiyan 
52f0b7fabeSAlexander Shiyan 	return 0;
53f0b7fabeSAlexander Shiyan }
54f0b7fabeSAlexander Shiyan 
55f0b7fabeSAlexander Shiyan static irqreturn_t clps711x_timer_interrupt(int irq, void *dev_id)
56f0b7fabeSAlexander Shiyan {
57f0b7fabeSAlexander Shiyan 	struct clock_event_device *evt = dev_id;
58f0b7fabeSAlexander Shiyan 
59f0b7fabeSAlexander Shiyan 	evt->event_handler(evt);
60f0b7fabeSAlexander Shiyan 
61f0b7fabeSAlexander Shiyan 	return IRQ_HANDLED;
62f0b7fabeSAlexander Shiyan }
63f0b7fabeSAlexander Shiyan 
64f0b7fabeSAlexander Shiyan static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
65f0b7fabeSAlexander Shiyan 					 struct clock_event_device *evt)
66f0b7fabeSAlexander Shiyan {
67f0b7fabeSAlexander Shiyan }
68f0b7fabeSAlexander Shiyan 
69f0b7fabeSAlexander Shiyan static int __init _clps711x_clkevt_init(struct clk *clock, void __iomem *base,
70f0b7fabeSAlexander Shiyan 					unsigned int irq)
71f0b7fabeSAlexander Shiyan {
72f0b7fabeSAlexander Shiyan 	struct clock_event_device *clkevt;
73f0b7fabeSAlexander Shiyan 	unsigned long rate;
74f0b7fabeSAlexander Shiyan 
75f0b7fabeSAlexander Shiyan 	if (!irq)
76f0b7fabeSAlexander Shiyan 		return -EINVAL;
77f0b7fabeSAlexander Shiyan 	if (!base)
78f0b7fabeSAlexander Shiyan 		return -ENOMEM;
79f0b7fabeSAlexander Shiyan 	if (IS_ERR(clock))
80f0b7fabeSAlexander Shiyan 		return PTR_ERR(clock);
81f0b7fabeSAlexander Shiyan 
82f0b7fabeSAlexander Shiyan 	clkevt = kzalloc(sizeof(*clkevt), GFP_KERNEL);
83f0b7fabeSAlexander Shiyan 	if (!clkevt)
84f0b7fabeSAlexander Shiyan 		return -ENOMEM;
85f0b7fabeSAlexander Shiyan 
86f0b7fabeSAlexander Shiyan 	rate = clk_get_rate(clock);
87f0b7fabeSAlexander Shiyan 
88f0b7fabeSAlexander Shiyan 	/* Set Timer prescaler */
89f0b7fabeSAlexander Shiyan 	writew(DIV_ROUND_CLOSEST(rate, HZ), base);
90f0b7fabeSAlexander Shiyan 
91f0b7fabeSAlexander Shiyan 	clkevt->name = "clps711x-clockevent";
92f0b7fabeSAlexander Shiyan 	clkevt->rating = 300;
93f0b7fabeSAlexander Shiyan 	clkevt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_C3STOP;
94f0b7fabeSAlexander Shiyan 	clkevt->set_mode = clps711x_clockevent_set_mode;
95f0b7fabeSAlexander Shiyan 	clkevt->cpumask = cpumask_of(0);
96f0b7fabeSAlexander Shiyan 	clockevents_config_and_register(clkevt, HZ, 0, 0);
97f0b7fabeSAlexander Shiyan 
98f0b7fabeSAlexander Shiyan 	return request_irq(irq, clps711x_timer_interrupt, IRQF_TIMER,
99f0b7fabeSAlexander Shiyan 			   "clps711x-timer", clkevt);
100f0b7fabeSAlexander Shiyan }
101f0b7fabeSAlexander Shiyan 
102f0b7fabeSAlexander Shiyan void __init clps711x_clksrc_init(void __iomem *tc1_base, void __iomem *tc2_base,
103f0b7fabeSAlexander Shiyan 				 unsigned int irq)
104f0b7fabeSAlexander Shiyan {
105f0b7fabeSAlexander Shiyan 	struct clk *tc1 = clk_get_sys("clps711x-timer.0", NULL);
106f0b7fabeSAlexander Shiyan 	struct clk *tc2 = clk_get_sys("clps711x-timer.1", NULL);
107f0b7fabeSAlexander Shiyan 
108f0b7fabeSAlexander Shiyan 	BUG_ON(_clps711x_clksrc_init(tc1, tc1_base));
109f0b7fabeSAlexander Shiyan 	BUG_ON(_clps711x_clkevt_init(tc2, tc2_base, irq));
110f0b7fabeSAlexander Shiyan }
111f0b7fabeSAlexander Shiyan 
112f0b7fabeSAlexander Shiyan #ifdef CONFIG_CLKSRC_OF
113f0b7fabeSAlexander Shiyan static void __init clps711x_timer_init(struct device_node *np)
114f0b7fabeSAlexander Shiyan {
115f0b7fabeSAlexander Shiyan 	unsigned int irq = irq_of_parse_and_map(np, 0);
116f0b7fabeSAlexander Shiyan 	struct clk *clock = of_clk_get(np, 0);
117f0b7fabeSAlexander Shiyan 	void __iomem *base = of_iomap(np, 0);
118f0b7fabeSAlexander Shiyan 
119f0b7fabeSAlexander Shiyan 	switch (of_alias_get_id(np, "timer")) {
120f0b7fabeSAlexander Shiyan 	case CLPS711X_CLKSRC_CLOCKSOURCE:
121f0b7fabeSAlexander Shiyan 		BUG_ON(_clps711x_clksrc_init(clock, base));
122f0b7fabeSAlexander Shiyan 		break;
123f0b7fabeSAlexander Shiyan 	case CLPS711X_CLKSRC_CLOCKEVENT:
124f0b7fabeSAlexander Shiyan 		BUG_ON(_clps711x_clkevt_init(clock, base, irq));
125f0b7fabeSAlexander Shiyan 		break;
126f0b7fabeSAlexander Shiyan 	default:
127f0b7fabeSAlexander Shiyan 		break;
128f0b7fabeSAlexander Shiyan 	}
129f0b7fabeSAlexander Shiyan }
130f0b7fabeSAlexander Shiyan CLOCKSOURCE_OF_DECLARE(clps711x, "cirrus,clps711x-timer", clps711x_timer_init);
131f0b7fabeSAlexander Shiyan #endif
132