summaryrefslogtreecommitdiffstats
path: root/drivers/clk/rockchip/clk-cpu.c
blob: 71a64f71f6ff0add7a6f79f60fbcc8ff7925ff74 (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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014 MundoReader S.L.
 * Author: Heiko Stuebner <heiko@sntech.de>
 *
 * based on clk/samsung/clk-cpu.c
 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
 * Author: Thomas Abraham <thomas.ab@samsung.com>
 *
 * A CPU clock is defined as a clock supplied to a CPU or a group of CPUs.
 * The CPU clock is typically derived from a hierarchy of clock
 * blocks which includes mux and divider blocks. There are a number of other
 * auxiliary clocks supplied to the CPU domain such as the debug blocks and AXI
 * clock for CPU domain. The rates of these auxiliary clocks are related to the
 * CPU clock rate and this relation is usually specified in the hardware manual
 * of the SoC or supplied after the SoC characterization.
 *
 * The below implementation of the CPU clock allows the rate changes of the CPU
 * clock and the corresponding rate changes of the auxillary clocks of the CPU
 * domain. The platform clock driver provides a clock register configuration
 * for each configurable rate which is then used to program the clock hardware
 * registers to acheive a fast co-oridinated rate change for all the CPU domain
 * clocks.
 *
 * On a rate change request for the CPU clock, the rate change is propagated
 * upto the PLL supplying the clock to the CPU domain clock blocks. While the
 * CPU domain PLL is reconfigured, the CPU domain clocks are driven using an
 * alternate clock source. If required, the alternate clock source is divided
 * down in order to keep the output clock rate within the previous OPP limits.
 */

#include <common.h>
#include <of.h>
#include <malloc.h>
#include <io.h>
#include <xfuncs.h>
#include "clk.h"
#include <linux/barebox-wrapper.h>

/**
 * struct rockchip_cpuclk: information about clock supplied to a CPU core.
 * @hw:		handle between ccf and cpu clock.
 * @alt_parent:	alternate parent clock to use when switching the speed
 *		of the primary parent clock.
 * @reg_base:	base register for cpu-clock values.
 * @rate_count:	number of rates in the rate_table
 * @rate_table:	pll-rates and their associated dividers
 * @reg_data:	cpu-specific register settings
 */
struct rockchip_cpuclk {
	struct clk				hw;

	struct clk				*alt_parent;
	void __iomem				*reg_base;
	unsigned int				rate_count;
	struct rockchip_cpuclk_rate_table	*rate_table;
	const struct rockchip_cpuclk_reg_data	*reg_data;
};

#define to_rockchip_cpuclk_hw(hw) container_of(hw, struct rockchip_cpuclk, hw)

static unsigned long rockchip_cpuclk_recalc_rate(struct clk *hw,
					unsigned long parent_rate)
{
	struct rockchip_cpuclk *cpuclk = to_rockchip_cpuclk_hw(hw);
	const struct rockchip_cpuclk_reg_data *reg_data = cpuclk->reg_data;
	u32 clksel0 = readl(cpuclk->reg_base + reg_data->core_reg);

	clksel0 >>= reg_data->div_core_shift;
	clksel0 &= reg_data->div_core_mask;
	return parent_rate / (clksel0 + 1);
}

static const struct clk_ops rockchip_cpuclk_ops = {
	.recalc_rate = rockchip_cpuclk_recalc_rate,
};

struct clk *rockchip_clk_register_cpuclk(const char *name,
			const char **parent_names, u8 num_parents,
			const struct rockchip_cpuclk_reg_data *reg_data,
			const struct rockchip_cpuclk_rate_table *rates,
			int nrates, void __iomem *reg_base)
{
	struct rockchip_cpuclk *cpuclk;
	struct clk *clk;
	int ret;

	if (num_parents != 2) {
		pr_err("%s: needs two parent clocks\n", __func__);
		return ERR_PTR(-EINVAL);
	}

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

	cpuclk->hw.name = name;
	cpuclk->hw.parent_names = &parent_names[0];
	cpuclk->hw.num_parents = 1;
	cpuclk->hw.ops = &rockchip_cpuclk_ops;

	/* only allow rate changes when we have a rate table */
	cpuclk->hw.flags = (nrates > 0) ? CLK_SET_RATE_PARENT : 0;

	cpuclk->reg_base = reg_base;
	cpuclk->reg_data = reg_data;

	cpuclk->alt_parent = __clk_lookup(parent_names[1]);
	if (!cpuclk->alt_parent) {
		pr_err("%s: could not lookup alternate parent\n",
		       __func__);
		ret = -EINVAL;
		goto free_cpuclk;
	}

	ret = clk_enable(cpuclk->alt_parent);
	if (ret) {
		pr_err("%s: could not enable alternate parent\n",
		       __func__);
		goto free_cpuclk;
	}

	clk = __clk_lookup(parent_names[0]);
	if (!clk) {
		pr_err("%s: could not lookup parent clock %s\n",
		       __func__, parent_names[0]);
		ret = -EINVAL;
		goto free_cpuclk;
	}

	if (nrates > 0) {
		cpuclk->rate_count = nrates;
		cpuclk->rate_table = xmemdup(rates,
					     sizeof(*rates) * nrates
					     );
		if (!cpuclk->rate_table) {
			pr_err("%s: could not allocate memory for cpuclk rates\n",
			       __func__);
			ret = -ENOMEM;
			goto free_cpuclk;
		}
	}

	ret = clk_register(&cpuclk->hw);
	if (ret) {
		pr_err("%s: could not register cpuclk %s\n", __func__,	name);
		goto free_rate_table;
	}

	return &cpuclk->hw;

free_rate_table:
	kfree(cpuclk->rate_table);
free_cpuclk:
	kfree(cpuclk);
	return ERR_PTR(ret);
}