summaryrefslogtreecommitdiffstats
path: root/drivers/clocksource/clps711x-timer.c
blob: d83ec1f2fddc9d8e04c8bc3f29cb7e8c9e6f6c77 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 *  Cirrus Logic CLPS711X clocksource driver
 *
 *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>

enum {
	CLPS711X_CLKSRC_CLOCKSOURCE,
	CLPS711X_CLKSRC_CLOCKEVENT,
};

static void __iomem *tcd;

static u64 notrace clps711x_sched_clock_read(void)
{
	return ~readw(tcd);
}

static int __init _clps711x_clksrc_init(struct clk *clock, void __iomem *base)
{
	unsigned long rate;

	if (!base)
		return -ENOMEM;
	if (IS_ERR(clock))
		return PTR_ERR(clock);

	rate = clk_get_rate(clock);

	tcd = base;

	clocksource_mmio_init(tcd, "clps711x-clocksource", rate, 300, 16,
			      clocksource_mmio_readw_down);

	sched_clock_register(clps711x_sched_clock_read, 16, rate);

	return 0;
}

static irqreturn_t clps711x_timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *evt = dev_id;

	evt->event_handler(evt);

	return IRQ_HANDLED;
}

static void clps711x_clockevent_set_mode(enum clock_event_mode mode,
					 struct clock_event_device *evt)
{
}

static int __init _clps711x_clkevt_init(struct clk *clock, void __iomem *base,
					unsigned int irq)
{
	struct clock_event_device *clkevt;
	unsigned long rate;

	if (!irq)
		return -EINVAL;
	if (!base)
		return -ENOMEM;
	if (IS_ERR(clock))
		return PTR_ERR(clock);

	clkevt = kzalloc(sizeof(*clkevt), GFP_KERNEL);
	if (!clkevt)
		return -ENOMEM;

	rate = clk_get_rate(clock);

	/* Set Timer prescaler */
	writew(DIV_ROUND_CLOSEST(rate, HZ), base);

	clkevt->name = "clps711x-clockevent";
	clkevt->rating = 300;
	clkevt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_C3STOP;
	clkevt->set_mode = clps711x_clockevent_set_mode;
	clkevt->cpumask = cpumask_of(0);
	clockevents_config_and_register(clkevt, HZ, 0, 0);

	return request_irq(irq, clps711x_timer_interrupt, IRQF_TIMER,
			   "clps711x-timer", clkevt);
}

void __init clps711x_clksrc_init(void __iomem *tc1_base, void __iomem *tc2_base,
				 unsigned int irq)
{
	struct clk *tc1 = clk_get_sys("clps711x-timer.0", NULL);
	struct clk *tc2 = clk_get_sys("clps711x-timer.1", NULL);

	BUG_ON(_clps711x_clksrc_init(tc1, tc1_base));
	BUG_ON(_clps711x_clkevt_init(tc2, tc2_base, irq));
}

#ifdef CONFIG_CLKSRC_OF
static void __init clps711x_timer_init(struct device_node *np)
{
	unsigned int irq = irq_of_parse_and_map(np, 0);
	struct clk *clock = of_clk_get(np, 0);
	void __iomem *base = of_iomap(np, 0);

	switch (of_alias_get_id(np, "timer")) {
	case CLPS711X_CLKSRC_CLOCKSOURCE:
		BUG_ON(_clps711x_clksrc_init(clock, base));
		break;
	case CLPS711X_CLKSRC_CLOCKEVENT:
		BUG_ON(_clps711x_clkevt_init(clock, base, irq));
		break;
	default:
		break;
	}
}
CLOCKSOURCE_OF_DECLARE(clps711x, "cirrus,clps711x-timer", clps711x_timer_init);
#endif