summaryrefslogtreecommitdiff
path: root/drivers/clk/at91/clk-system.c
blob: 8c96307d736343e3cbc6924c09580408f32c2da7 (plain)
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/*
 *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
 *
 * 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-provider.h>
#include <linux/clkdev.h>
#include <linux/clk/at91_pmc.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>

#include "pmc.h"

#define SYSTEM_MAX_ID		31

#define SYSTEM_MAX_NAME_SZ	32

#define to_clk_system(hw) container_of(hw, struct clk_system, hw)
struct clk_system {
	struct clk_hw hw;
	struct at91_pmc *pmc;
	unsigned int irq;
	wait_queue_head_t wait;
	u8 id;
};

static inline int is_pck(int id)
{
	return (id >= 8) && (id <= 15);
}
static irqreturn_t clk_system_irq_handler(int irq, void *dev_id)
{
	struct clk_system *sys = (struct clk_system *)dev_id;

	wake_up(&sys->wait);
	disable_irq_nosync(sys->irq);

	return IRQ_HANDLED;
}

static int clk_system_prepare(struct clk_hw *hw)
{
	struct clk_system *sys = to_clk_system(hw);
	struct at91_pmc *pmc = sys->pmc;
	u32 mask = 1 << sys->id;

	pmc_write(pmc, AT91_PMC_SCER, mask);

	if (!is_pck(sys->id))
		return 0;

	while (!(pmc_read(pmc, AT91_PMC_SR) & mask)) {
		if (sys->irq) {
			enable_irq(sys->irq);
			wait_event(sys->wait,
				   pmc_read(pmc, AT91_PMC_SR) & mask);
		} else
			cpu_relax();
	}
	return 0;
}

static void clk_system_unprepare(struct clk_hw *hw)
{
	struct clk_system *sys = to_clk_system(hw);
	struct at91_pmc *pmc = sys->pmc;

	pmc_write(pmc, AT91_PMC_SCDR, 1 << sys->id);
}

static int clk_system_is_prepared(struct clk_hw *hw)
{
	struct clk_system *sys = to_clk_system(hw);
	struct at91_pmc *pmc = sys->pmc;

	if (!(pmc_read(pmc, AT91_PMC_SCSR) & (1 << sys->id)))
		return 0;

	if (!is_pck(sys->id))
		return 1;

	return !!(pmc_read(pmc, AT91_PMC_SR) & (1 << sys->id));
}

static const struct clk_ops system_ops = {
	.prepare = clk_system_prepare,
	.unprepare = clk_system_unprepare,
	.is_prepared = clk_system_is_prepared,
};

static struct clk * __init
at91_clk_register_system(struct at91_pmc *pmc, const char *name,
			 const char *parent_name, u8 id, int irq)
{
	struct clk_system *sys;
	struct clk *clk = NULL;
	struct clk_init_data init;
	int ret;

	if (!parent_name || id > SYSTEM_MAX_ID)
		return ERR_PTR(-EINVAL);

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

	init.name = name;
	init.ops = &system_ops;
	init.parent_names = &parent_name;
	init.num_parents = 1;
	/*
	 * CLK_IGNORE_UNUSED is used to avoid ddrck switch off.
	 * TODO : we should implement a driver supporting at91 ddr controller
	 * (see drivers/memory) which would request and enable the ddrck clock.
	 * When this is done we will be able to remove CLK_IGNORE_UNUSED flag.
	 */
	init.flags = CLK_SET_RATE_PARENT | CLK_IGNORE_UNUSED;

	sys->id = id;
	sys->hw.init = &init;
	sys->pmc = pmc;
	sys->irq = irq;
	if (irq) {
		init_waitqueue_head(&sys->wait);
		irq_set_status_flags(sys->irq, IRQ_NOAUTOEN);
		ret = request_irq(sys->irq, clk_system_irq_handler,
				IRQF_TRIGGER_HIGH, name, sys);
		if (ret)
			return ERR_PTR(ret);
	}

	clk = clk_register(NULL, &sys->hw);
	if (IS_ERR(clk))
		kfree(sys);

	return clk;
}

static void __init
of_at91_clk_sys_setup(struct device_node *np, struct at91_pmc *pmc)
{
	int num;
	int irq = 0;
	u32 id;
	struct clk *clk;
	const char *name;
	struct device_node *sysclknp;
	const char *parent_name;

	num = of_get_child_count(np);
	if (num > (SYSTEM_MAX_ID + 1))
		return;

	for_each_child_of_node(np, sysclknp) {
		if (of_property_read_u32(sysclknp, "reg", &id))
			continue;

		if (of_property_read_string(np, "clock-output-names", &name))
			name = sysclknp->name;

		if (is_pck(id))
			irq = irq_of_parse_and_map(sysclknp, 0);

		parent_name = of_clk_get_parent_name(sysclknp, 0);

		clk = at91_clk_register_system(pmc, name, parent_name, id, irq);
		if (IS_ERR(clk))
			continue;

		of_clk_add_provider(sysclknp, of_clk_src_simple_get, clk);
	}
}

void __init of_at91rm9200_clk_sys_setup(struct device_node *np,
					struct at91_pmc *pmc)
{
	of_at91_clk_sys_setup(np, pmc);
}