summaryrefslogtreecommitdiff
path: root/arch/arm/mach-omap2/cm_common.c
blob: b7ea609386d52dcf2e782006a581743f53814d53 (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// SPDX-License-Identifier: GPL-2.0-only
/*
 * OMAP2+ common Clock Management (CM) IP block functions
 *
 * Copyright (C) 2012 Texas Instruments, Inc.
 * Paul Walmsley
 *
 * XXX This code should eventually be moved to a CM driver.
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/bug.h>
#include <linux/of.h>
#include <linux/of_address.h>

#include "cm2xxx.h"
#include "cm3xxx.h"
#include "cm33xx.h"
#include "cm44xx.h"
#include "clock.h"

/*
 * cm_ll_data: function pointers to SoC-specific implementations of
 * common CM functions
 */
static struct cm_ll_data null_cm_ll_data;
static const struct cm_ll_data *cm_ll_data = &null_cm_ll_data;

/* cm_base: base virtual address of the CM IP block */
struct omap_domain_base cm_base;

/* cm2_base: base virtual address of the CM2 IP block (OMAP44xx only) */
struct omap_domain_base cm2_base;

#define CM_NO_CLOCKS		0x1
#define CM_SINGLE_INSTANCE	0x2

/**
 * omap2_set_globals_cm - set the CM/CM2 base addresses (for early use)
 * @cm: CM base virtual address
 * @cm2: CM2 base virtual address (if present on the booted SoC)
 *
 * XXX Will be replaced when the PRM/CM drivers are completed.
 */
void __init omap2_set_globals_cm(void __iomem *cm, void __iomem *cm2)
{
	cm_base.va = cm;
	cm2_base.va = cm2;
}

/**
 * cm_split_idlest_reg - split CM_IDLEST reg addr into its components
 * @idlest_reg: CM_IDLEST* virtual address
 * @prcm_inst: pointer to an s16 to return the PRCM instance offset
 * @idlest_reg_id: pointer to a u8 to return the CM_IDLESTx register ID
 *
 * Given an absolute CM_IDLEST register address @idlest_reg, passes
 * the PRCM instance offset and IDLEST register ID back to the caller
 * via the @prcm_inst and @idlest_reg_id.  Returns -EINVAL upon error,
 * or 0 upon success.  XXX This function is only needed until absolute
 * register addresses are removed from the OMAP struct clk records.
 */
int cm_split_idlest_reg(struct clk_omap_reg *idlest_reg, s16 *prcm_inst,
			u8 *idlest_reg_id)
{
	int ret;
	if (!cm_ll_data->split_idlest_reg) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return -EINVAL;
	}

	ret = cm_ll_data->split_idlest_reg(idlest_reg, prcm_inst,
					   idlest_reg_id);
	*prcm_inst -= cm_base.offset;
	return ret;
}

/**
 * omap_cm_wait_module_ready - wait for a module to leave idle or standby
 * @part: PRCM partition
 * @prcm_mod: PRCM module offset
 * @idlest_reg: CM_IDLESTx register
 * @idlest_shift: shift of the bit in the CM_IDLEST* register to check
 *
 * Wait for the PRCM to indicate that the module identified by
 * (@prcm_mod, @idlest_id, @idlest_shift) is clocked.  Return 0 upon
 * success, -EBUSY if the module doesn't enable in time, or -EINVAL if
 * no per-SoC wait_module_ready() function pointer has been registered
 * or if the idlest register is unknown on the SoC.
 */
int omap_cm_wait_module_ready(u8 part, s16 prcm_mod, u16 idlest_reg,
			      u8 idlest_shift)
{
	if (!cm_ll_data->wait_module_ready) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return -EINVAL;
	}

	return cm_ll_data->wait_module_ready(part, prcm_mod, idlest_reg,
					     idlest_shift);
}

/**
 * omap_cm_wait_module_idle - wait for a module to enter idle or standby
 * @part: PRCM partition
 * @prcm_mod: PRCM module offset
 * @idlest_reg: CM_IDLESTx register
 * @idlest_shift: shift of the bit in the CM_IDLEST* register to check
 *
 * Wait for the PRCM to indicate that the module identified by
 * (@prcm_mod, @idlest_id, @idlest_shift) is no longer clocked.  Return
 * 0 upon success, -EBUSY if the module doesn't enable in time, or
 * -EINVAL if no per-SoC wait_module_idle() function pointer has been
 * registered or if the idlest register is unknown on the SoC.
 */
int omap_cm_wait_module_idle(u8 part, s16 prcm_mod, u16 idlest_reg,
			     u8 idlest_shift)
{
	if (!cm_ll_data->wait_module_idle) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return -EINVAL;
	}

	return cm_ll_data->wait_module_idle(part, prcm_mod, idlest_reg,
					    idlest_shift);
}

/**
 * omap_cm_module_enable - enable a module
 * @mode: target mode for the module
 * @part: PRCM partition
 * @inst: PRCM instance
 * @clkctrl_offs: CM_CLKCTRL register offset for the module
 *
 * Enables clocks for a module identified by (@part, @inst, @clkctrl_offs)
 * making its IO space accessible. Return 0 upon success, -EINVAL if no
 * per-SoC module_enable() function pointer has been registered.
 */
int omap_cm_module_enable(u8 mode, u8 part, u16 inst, u16 clkctrl_offs)
{
	if (!cm_ll_data->module_enable) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return -EINVAL;
	}

	cm_ll_data->module_enable(mode, part, inst, clkctrl_offs);
	return 0;
}

/**
 * omap_cm_module_disable - disable a module
 * @part: PRCM partition
 * @inst: PRCM instance
 * @clkctrl_offs: CM_CLKCTRL register offset for the module
 *
 * Disables clocks for a module identified by (@part, @inst, @clkctrl_offs)
 * makings its IO space inaccessible. Return 0 upon success, -EINVAL if
 * no per-SoC module_disable() function pointer has been registered.
 */
int omap_cm_module_disable(u8 part, u16 inst, u16 clkctrl_offs)
{
	if (!cm_ll_data->module_disable) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return -EINVAL;
	}

	cm_ll_data->module_disable(part, inst, clkctrl_offs);
	return 0;
}

u32 omap_cm_xlate_clkctrl(u8 part, u16 inst, u16 clkctrl_offs)
{
	if (!cm_ll_data->xlate_clkctrl) {
		WARN_ONCE(1, "cm: %s: no low-level function defined\n",
			  __func__);
		return 0;
	}
	return cm_ll_data->xlate_clkctrl(part, inst, clkctrl_offs);
}

/**
 * cm_register - register per-SoC low-level data with the CM
 * @cld: low-level per-SoC OMAP CM data & function pointers to register
 *
 * Register per-SoC low-level OMAP CM data and function pointers with
 * the OMAP CM common interface.  The caller must keep the data
 * pointed to by @cld valid until it calls cm_unregister() and
 * it returns successfully.  Returns 0 upon success, -EINVAL if @cld
 * is NULL, or -EEXIST if cm_register() has already been called
 * without an intervening cm_unregister().
 */
int cm_register(const struct cm_ll_data *cld)
{
	if (!cld)
		return -EINVAL;

	if (cm_ll_data != &null_cm_ll_data)
		return -EEXIST;

	cm_ll_data = cld;

	return 0;
}

/**
 * cm_unregister - unregister per-SoC low-level data & function pointers
 * @cld: low-level per-SoC OMAP CM data & function pointers to unregister
 *
 * Unregister per-SoC low-level OMAP CM data and function pointers
 * that were previously registered with cm_register().  The
 * caller may not destroy any of the data pointed to by @cld until
 * this function returns successfully.  Returns 0 upon success, or
 * -EINVAL if @cld is NULL or if @cld does not match the struct
 * cm_ll_data * previously registered by cm_register().
 */
int cm_unregister(const struct cm_ll_data *cld)
{
	if (!cld || cm_ll_data != cld)
		return -EINVAL;

	cm_ll_data = &null_cm_ll_data;

	return 0;
}

#if defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_SOC_OMAP5) || \
	defined(CONFIG_SOC_DRA7XX)
static struct omap_prcm_init_data cm_data __initdata = {
	.index = TI_CLKM_CM,
	.init = omap4_cm_init,
};

static struct omap_prcm_init_data cm2_data __initdata = {
	.index = TI_CLKM_CM2,
	.init = omap4_cm_init,
};
#endif

#ifdef CONFIG_ARCH_OMAP2
static struct omap_prcm_init_data omap2_prcm_data __initdata = {
	.index = TI_CLKM_CM,
	.init = omap2xxx_cm_init,
	.flags = CM_NO_CLOCKS | CM_SINGLE_INSTANCE,
};
#endif

#ifdef CONFIG_ARCH_OMAP3
static struct omap_prcm_init_data omap3_cm_data __initdata = {
	.index = TI_CLKM_CM,
	.init = omap3xxx_cm_init,
	.flags = CM_SINGLE_INSTANCE,

	/*
	 * IVA2 offset is a negative value, must offset the cm_base address
	 * by this to get it to positive side on the iomap
	 */
	.offset = -OMAP3430_IVA2_MOD,
};
#endif

#if defined(CONFIG_SOC_AM33XX) || defined(CONFIG_SOC_TI81XX)
static struct omap_prcm_init_data am3_prcm_data __initdata = {
	.index = TI_CLKM_CM,
	.flags = CM_NO_CLOCKS | CM_SINGLE_INSTANCE,
	.init = am33xx_cm_init,
};
#endif

#ifdef CONFIG_SOC_AM43XX
static struct omap_prcm_init_data am4_prcm_data __initdata = {
	.index = TI_CLKM_CM,
	.flags = CM_NO_CLOCKS | CM_SINGLE_INSTANCE,
	.init = omap4_cm_init,
};
#endif

static const struct of_device_id omap_cm_dt_match_table[] __initconst = {
#ifdef CONFIG_ARCH_OMAP2
	{ .compatible = "ti,omap2-prcm", .data = &omap2_prcm_data },
#endif
#ifdef CONFIG_ARCH_OMAP3
	{ .compatible = "ti,omap3-cm", .data = &omap3_cm_data },
#endif
#ifdef CONFIG_ARCH_OMAP4
	{ .compatible = "ti,omap4-cm1", .data = &cm_data },
	{ .compatible = "ti,omap4-cm2", .data = &cm2_data },
#endif
#ifdef CONFIG_SOC_OMAP5
	{ .compatible = "ti,omap5-cm-core-aon", .data = &cm_data },
	{ .compatible = "ti,omap5-cm-core", .data = &cm2_data },
#endif
#ifdef CONFIG_SOC_DRA7XX
	{ .compatible = "ti,dra7-cm-core-aon", .data = &cm_data },
	{ .compatible = "ti,dra7-cm-core", .data = &cm2_data },
#endif
#ifdef CONFIG_SOC_AM33XX
	{ .compatible = "ti,am3-prcm", .data = &am3_prcm_data },
#endif
#ifdef CONFIG_SOC_AM43XX
	{ .compatible = "ti,am4-prcm", .data = &am4_prcm_data },
#endif
#ifdef CONFIG_SOC_TI81XX
	{ .compatible = "ti,dm814-prcm", .data = &am3_prcm_data },
	{ .compatible = "ti,dm816-prcm", .data = &am3_prcm_data },
#endif
	{ }
};

/**
 * omap2_cm_base_init - initialize iomappings for the CM drivers
 *
 * Detects and initializes the iomappings for the CM driver, based
 * on the DT data. Returns 0 in success, negative error value
 * otherwise.
 */
int __init omap2_cm_base_init(void)
{
	struct device_node *np;
	const struct of_device_id *match;
	struct omap_prcm_init_data *data;
	struct resource res;
	int ret;
	struct omap_domain_base *mem = NULL;

	for_each_matching_node_and_match(np, omap_cm_dt_match_table, &match) {
		data = (struct omap_prcm_init_data *)match->data;

		ret = of_address_to_resource(np, 0, &res);
		if (ret)
			return ret;

		if (data->index == TI_CLKM_CM)
			mem = &cm_base;

		if (data->index == TI_CLKM_CM2)
			mem = &cm2_base;

		data->mem = ioremap(res.start, resource_size(&res));

		if (mem) {
			mem->pa = res.start + data->offset;
			mem->va = data->mem + data->offset;
			mem->offset = data->offset;
		}

		data->np = np;

		if (data->init && (data->flags & CM_SINGLE_INSTANCE ||
				   (cm_base.va && cm2_base.va)))
			data->init(data);
	}

	return 0;
}

/**
 * omap_cm_init - low level init for the CM drivers
 *
 * Initializes the low level clock infrastructure for CM drivers.
 * Returns 0 in success, negative error value in failure.
 */
int __init omap_cm_init(void)
{
	struct device_node *np;
	const struct of_device_id *match;
	const struct omap_prcm_init_data *data;
	int ret;

	for_each_matching_node_and_match(np, omap_cm_dt_match_table, &match) {
		data = match->data;

		if (data->flags & CM_NO_CLOCKS)
			continue;

		ret = omap2_clk_provider_init(np, data->index, NULL, data->mem);
		if (ret)
			return ret;
	}

	return 0;
}