Blame view

drivers/cpufreq/imx6q-cpufreq.c 17.9 KB
d2912cb15   Thomas Gleixner   treewide: Replace...
1
  // SPDX-License-Identifier: GPL-2.0-only
1dd538f07   Shawn Guo   cpufreq: add imx6...
2
3
  /*
   * Copyright (C) 2013 Freescale Semiconductor, Inc.
1dd538f07   Shawn Guo   cpufreq: add imx6...
4
   */
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
5
  #include <linux/busfreq-imx.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
6
  #include <linux/clk.h>
b494b48da   Sudeep KarkadaNagesha   cpufreq: imx6q-cp...
7
  #include <linux/cpu.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
8
  #include <linux/cpufreq.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
9
10
  #include <linux/err.h>
  #include <linux/module.h>
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
11
  #include <linux/nvmem-consumer.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
12
  #include <linux/of.h>
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
13
  #include <linux/of_address.h>
e4db1c743   Nishanth Menon   PM / OPP: rename ...
14
  #include <linux/pm_opp.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
15
16
  #include <linux/platform_device.h>
  #include <linux/regulator/consumer.h>
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
17
  #include <linux/suspend.h>
1dd538f07   Shawn Guo   cpufreq: add imx6...
18
19
20
21
  
  #define PU_SOC_VOLTAGE_NORMAL	1250000
  #define PU_SOC_VOLTAGE_HIGH	1275000
  #define FREQ_1P2_GHZ		1200000000
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
22
  #define FREQ_396_MHZ		396000
1dd538f07   Shawn Guo   cpufreq: add imx6...
23

aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
24
25
26
27
28
29
30
31
32
  #define PU_SOC_VOLTAGE_NORMAL	1250000
  #define PU_SOC_VOLTAGE_HIGH	1275000
  #define DC_VOLTAGE_MIN		1300000
  #define DC_VOLTAGE_MAX		1400000
  #define FREQ_1P2_GHZ		1200000000
  #define FREQ_396_MHZ		396000
  #define FREQ_528_MHZ		528000
  #define FREQ_198_MHZ		198000
  #define FREQ_24_MHZ		24000
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
33
34
35
  struct regulator *arm_reg;
  struct regulator *pu_reg;
  struct regulator *soc_reg;
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
36
  struct regulator *dc_reg;
1dd538f07   Shawn Guo   cpufreq: add imx6...
37

2332bd041   Dong Aisheng   cpufreq: imx6q: s...
38
39
40
41
42
43
  enum IMX6_CPUFREQ_CLKS {
  	ARM,
  	PLL1_SYS,
  	STEP,
  	PLL1_SW,
  	PLL2_PFD2_396M,
8f94f2d75   Anson Huang   cpufreq: imx6q: k...
44
45
46
  	PLL1,
  	PLL1_BYPASS,
  	PLL1_BYPASS_SRC,
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
47
48
49
50
  	/* MX6UL requires two more clks */
  	PLL2_BUS,
  	SECONDARY_SEL,
  };
8f94f2d75   Anson Huang   cpufreq: imx6q: k...
51
52
  #define IMX6Q_CPUFREQ_CLK_NUM		8
  #define IMX6UL_CPUFREQ_CLK_NUM		10
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
53
54
55
56
57
58
59
60
  
  static int num_clks;
  static struct clk_bulk_data clks[] = {
  	{ .id = "arm" },
  	{ .id = "pll1_sys" },
  	{ .id = "step" },
  	{ .id = "pll1_sw" },
  	{ .id = "pll2_pfd2_396m" },
8f94f2d75   Anson Huang   cpufreq: imx6q: k...
61
62
63
  	{ .id = "pll1" },
  	{ .id = "pll1_bypass" },
  	{ .id = "pll1_bypass_src" },
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
64
65
66
  	{ .id = "pll2_bus" },
  	{ .id = "secondary_sel" },
  };
a35fc5a33   Bai Ping   cpufreq: imx: upd...
67

1dd538f07   Shawn Guo   cpufreq: add imx6...
68
69
  static struct device *cpu_dev;
  static struct cpufreq_frequency_table *freq_table;
8d768cdcf   Viresh Kumar   cpufreq: imx6q: F...
70
  static unsigned int max_freq;
1dd538f07   Shawn Guo   cpufreq: add imx6...
71
  static unsigned int transition_latency;
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
72
73
  static u32 *imx6_soc_volt;
  static u32 soc_opp_count;
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
74
75
  static bool ignore_dc_reg;
  static bool low_power_run_support;
9c0ebcf78   Viresh Kumar   cpufreq: Implemen...
76
  static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
1dd538f07   Shawn Guo   cpufreq: add imx6...
77
  {
47d43ba73   Nishanth Menon   PM / OPP: rename ...
78
  	struct dev_pm_opp *opp;
1dd538f07   Shawn Guo   cpufreq: add imx6...
79
  	unsigned long freq_hz, volt, volt_old;
d4019f0a9   Viresh Kumar   cpufreq: move fre...
80
  	unsigned int old_freq, new_freq;
fded5fc84   Leonard Crestez   cpufreq: imx6q: F...
81
  	bool pll1_sys_temp_enabled = false;
1dd538f07   Shawn Guo   cpufreq: add imx6...
82
  	int ret;
d4019f0a9   Viresh Kumar   cpufreq: move fre...
83
84
  	new_freq = freq_table[index].frequency;
  	freq_hz = new_freq * 1000;
26daebdb5   Jacky Bai   cpufreq: imx6q: g...
85
  	old_freq = policy->cur;
1dd538f07   Shawn Guo   cpufreq: add imx6...
86

aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
87
88
89
90
91
92
93
94
  	/*
  	 * ON i.MX6ULL, the 24MHz setpoint is not seen by cpufreq
  	 * so we neet to prevent the cpufreq change frequency
  	 * from 24MHz to 198Mhz directly. busfreq will handle this
  	 * when exit from low bus mode.
  	 */
  	if (old_freq == FREQ_24_MHZ && new_freq == FREQ_198_MHZ)
  		return 0;
5d4879cda   Nishanth Menon   PM / OPP: rename ...
95
  	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
1dd538f07   Shawn Guo   cpufreq: add imx6...
96
  	if (IS_ERR(opp)) {
1dd538f07   Shawn Guo   cpufreq: add imx6...
97
98
99
100
  		dev_err(cpu_dev, "failed to find OPP for %ld
  ", freq_hz);
  		return PTR_ERR(opp);
  	}
5d4879cda   Nishanth Menon   PM / OPP: rename ...
101
  	volt = dev_pm_opp_get_voltage(opp);
8a31d9d94   Viresh Kumar   PM / OPP: Update ...
102
  	dev_pm_opp_put(opp);
1dd538f07   Shawn Guo   cpufreq: add imx6...
103
104
105
106
  	volt_old = regulator_get_voltage(arm_reg);
  
  	dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV
  ",
d4019f0a9   Viresh Kumar   cpufreq: move fre...
107
108
  		old_freq / 1000, volt_old / 1000,
  		new_freq / 1000, volt / 1000);
5a571c352   Viresh Kumar   cpufreq: imx6q: c...
109

aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
110
111
112
113
  	if (low_power_run_support) {
  		if (old_freq == freq_table[0].frequency)
  			request_bus_freq(BUS_FREQ_HIGH);
  	} else if (old_freq <= FREQ_396_MHZ && new_freq > FREQ_396_MHZ) {
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
114
  		request_bus_freq(BUS_FREQ_HIGH);
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
115
  	}
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
116

1dd538f07   Shawn Guo   cpufreq: add imx6...
117
  	/* scaling up?  scale voltage before frequency */
d4019f0a9   Viresh Kumar   cpufreq: move fre...
118
  	if (new_freq > old_freq) {
22d0628a2   Anson Huang   cpufreq: imx6: re...
119
120
121
122
123
124
125
  		if (!IS_ERR(pu_reg)) {
  			ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0);
  			if (ret) {
  				dev_err(cpu_dev, "failed to scale vddpu up: %d
  ", ret);
  				return ret;
  			}
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
126
127
128
129
130
131
132
  		}
  		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0);
  		if (ret) {
  			dev_err(cpu_dev, "failed to scale vddsoc up: %d
  ", ret);
  			return ret;
  		}
1dd538f07   Shawn Guo   cpufreq: add imx6...
133
134
135
136
137
  		ret = regulator_set_voltage_tol(arm_reg, volt, 0);
  		if (ret) {
  			dev_err(cpu_dev,
  				"failed to scale vddarm up: %d
  ", ret);
d4019f0a9   Viresh Kumar   cpufreq: move fre...
138
  			return ret;
1dd538f07   Shawn Guo   cpufreq: add imx6...
139
  		}
1dd538f07   Shawn Guo   cpufreq: add imx6...
140
141
142
143
144
145
  	}
  
  	/*
  	 * The setpoints are selected per PLL/PDF frequencies, so we need to
  	 * reprogram PLL for frequency scaling.  The procedure of reprogramming
  	 * PLL1 is as below.
a35fc5a33   Bai Ping   cpufreq: imx: upd...
146
147
148
  	 * For i.MX6UL, it has a secondary clk mux, the cpu frequency change
  	 * flow is slightly different from other i.MX6 OSC.
  	 * The cpu frequeny change flow for i.MX6(except i.MX6UL) is as below:
1dd538f07   Shawn Guo   cpufreq: add imx6...
149
150
151
152
  	 *  - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it
  	 *  - Reprogram pll1_sys_clk and reparent pll1_sw_clk back to it
  	 *  - Disable pll2_pfd2_396m_clk
  	 */
3fafb4e77   Octavian Purdila   cpufreq: imx6q: i...
153
154
  	if (of_machine_is_compatible("fsl,imx6ul") ||
  	    of_machine_is_compatible("fsl,imx6ull")) {
a35fc5a33   Bai Ping   cpufreq: imx: upd...
155
156
157
158
159
160
161
  		/*
  		 * When changing pll1_sw_clk's parent to pll1_sys_clk,
  		 * CPU may run at higher than 528MHz, this will lead to
  		 * the system unstable if the voltage is lower than the
  		 * voltage of 528MHz, so lower the CPU frequency to one
  		 * half before changing CPU frequency.
  		 */
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
162
163
164
165
166
  		clk_set_rate(clks[ARM].clk, (old_freq >> 1) * 1000);
  		clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
  		if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk))
  			clk_set_parent(clks[SECONDARY_SEL].clk,
  				       clks[PLL2_BUS].clk);
a35fc5a33   Bai Ping   cpufreq: imx: upd...
167
  		else
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
168
169
170
171
  			clk_set_parent(clks[SECONDARY_SEL].clk,
  				       clks[PLL2_PFD2_396M].clk);
  		clk_set_parent(clks[STEP].clk, clks[SECONDARY_SEL].clk);
  		clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk);
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
172
173
174
175
  		if (freq_hz > clk_get_rate(clks[PLL2_BUS].clk)) {
  			clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000);
  			clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
  		}
a35fc5a33   Bai Ping   cpufreq: imx: upd...
176
  	} else {
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
177
178
179
  		clk_set_parent(clks[STEP].clk, clks[PLL2_PFD2_396M].clk);
  		clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk);
  		if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk)) {
8f94f2d75   Anson Huang   cpufreq: imx6q: k...
180
181
182
183
184
185
  			/* Ensure that pll1_bypass is set back to
  			 * pll1. We have to do this first so that the
  			 * change rate done to pll1_sys_clk done below
  			 * can propagate up to pll1.
  			 */
  			clk_set_parent(clks[PLL1_BYPASS].clk, clks[PLL1].clk);
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
186
187
  			clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000);
  			clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
fded5fc84   Leonard Crestez   cpufreq: imx6q: F...
188
189
190
  		} else {
  			/* pll1_sys needs to be enabled for divider rate change to work. */
  			pll1_sys_temp_enabled = true;
8f94f2d75   Anson Huang   cpufreq: imx6q: k...
191
  			clk_set_parent(clks[PLL1_BYPASS].clk, clks[PLL1_BYPASS_SRC].clk);
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
192
  			clk_prepare_enable(clks[PLL1_SYS].clk);
a35fc5a33   Bai Ping   cpufreq: imx: upd...
193
  		}
1dd538f07   Shawn Guo   cpufreq: add imx6...
194
195
196
  	}
  
  	/* Ensure the arm clock divider is what we expect */
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
197
  	ret = clk_set_rate(clks[ARM].clk, new_freq * 1000);
1dd538f07   Shawn Guo   cpufreq: add imx6...
198
  	if (ret) {
6ef28a04d   Anson Huang   cpufreq: imx6q: a...
199
  		int ret1;
1dd538f07   Shawn Guo   cpufreq: add imx6...
200
201
  		dev_err(cpu_dev, "failed to set clock rate: %d
  ", ret);
6ef28a04d   Anson Huang   cpufreq: imx6q: a...
202
203
204
205
206
  		ret1 = regulator_set_voltage_tol(arm_reg, volt_old, 0);
  		if (ret1)
  			dev_warn(cpu_dev,
  				 "failed to restore vddarm voltage: %d
  ", ret1);
d4019f0a9   Viresh Kumar   cpufreq: move fre...
207
  		return ret;
1dd538f07   Shawn Guo   cpufreq: add imx6...
208
  	}
fded5fc84   Leonard Crestez   cpufreq: imx6q: F...
209
210
  	/* PLL1 is only needed until after ARM-PODF is set. */
  	if (pll1_sys_temp_enabled)
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
211
  		clk_disable_unprepare(clks[PLL1_SYS].clk);
fded5fc84   Leonard Crestez   cpufreq: imx6q: F...
212

1dd538f07   Shawn Guo   cpufreq: add imx6...
213
  	/* scaling down?  scale voltage after frequency */
d4019f0a9   Viresh Kumar   cpufreq: move fre...
214
  	if (new_freq < old_freq) {
1dd538f07   Shawn Guo   cpufreq: add imx6...
215
  		ret = regulator_set_voltage_tol(arm_reg, volt, 0);
58ad4e619   Anson Huang   cpufreq: imx6q: r...
216
  		if (ret)
1dd538f07   Shawn Guo   cpufreq: add imx6...
217
218
219
  			dev_warn(cpu_dev,
  				 "failed to scale vddarm down: %d
  ", ret);
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
220
  		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0);
58ad4e619   Anson Huang   cpufreq: imx6q: r...
221
  		if (ret)
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
222
223
  			dev_warn(cpu_dev, "failed to scale vddsoc down: %d
  ", ret);
22d0628a2   Anson Huang   cpufreq: imx6: re...
224
225
  		if (!IS_ERR(pu_reg)) {
  			ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0);
58ad4e619   Anson Huang   cpufreq: imx6q: r...
226
  			if (ret)
22d0628a2   Anson Huang   cpufreq: imx6: re...
227
228
  				dev_warn(cpu_dev, "failed to scale vddpu down: %d
  ", ret);
1dd538f07   Shawn Guo   cpufreq: add imx6...
229
230
  		}
  	}
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
231
232
233
234
235
236
237
238
  	/*
  	 * If CPU is dropped to the lowest level, release the need
  	 * for a high bus frequency.
  	 */
  	if (low_power_run_support) {
  		if (new_freq == freq_table[0].frequency)
  			release_bus_freq(BUS_FREQ_HIGH);
  	} else if (old_freq > FREQ_396_MHZ && new_freq <= FREQ_396_MHZ) {
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
239
  		release_bus_freq(BUS_FREQ_HIGH);
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
240
  	}
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
241

d4019f0a9   Viresh Kumar   cpufreq: move fre...
242
  	return 0;
1dd538f07   Shawn Guo   cpufreq: add imx6...
243
244
245
246
  }
  
  static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
  {
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
247
  	policy->clk = clks[ARM].clk;
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
248
  	policy->cur = clk_get_rate(policy->clk) / 1000;
c4dcc8a16   Viresh Kumar   cpufreq: Make cpu...
249
  	cpufreq_generic_init(policy, freq_table, transition_latency);
8d768cdcf   Viresh Kumar   cpufreq: imx6q: F...
250
  	policy->suspend_freq = max_freq;
0e0ffa855   Lukasz Luba   OPP: refactor dev...
251
  	dev_pm_opp_of_register_em(cpu_dev, policy->cpus);
5aa1599ff   Leonard Crestez   cpufreq: imx6q: S...
252

aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
253
254
255
  	if (low_power_run_support && policy->cur > freq_table[0].frequency) {
  		request_bus_freq(BUS_FREQ_HIGH);
  	} else if (policy->cur > FREQ_396_MHZ) {
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
256
  		request_bus_freq(BUS_FREQ_HIGH);
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
257
  	}
65ec7b54d   Anson Huang   cpufreq: imx6q: a...
258

c4dcc8a16   Viresh Kumar   cpufreq: Make cpu...
259
  	return 0;
1dd538f07   Shawn Guo   cpufreq: add imx6...
260
  }
1dd538f07   Shawn Guo   cpufreq: add imx6...
261
  static struct cpufreq_driver imx6q_cpufreq_driver = {
4b4988692   Amit Kucheria   cpufreq: imx6q: U...
262
263
  	.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
  		 CPUFREQ_IS_COOLING_DEV,
4f6ba385e   Viresh Kumar   cpufreq: imx6q: U...
264
  	.verify = cpufreq_generic_frequency_table_verify,
9c0ebcf78   Viresh Kumar   cpufreq: Implemen...
265
  	.target_index = imx6q_set_target,
652ed95d5   Viresh Kumar   cpufreq: introduc...
266
  	.get = cpufreq_generic_get,
1dd538f07   Shawn Guo   cpufreq: add imx6...
267
  	.init = imx6q_cpufreq_init,
1dd538f07   Shawn Guo   cpufreq: add imx6...
268
  	.name = "imx6q-cpufreq",
4f6ba385e   Viresh Kumar   cpufreq: imx6q: U...
269
  	.attr = cpufreq_generic_attr,
5aa1599ff   Leonard Crestez   cpufreq: imx6q: S...
270
  	.suspend = cpufreq_generic_suspend,
1dd538f07   Shawn Guo   cpufreq: add imx6...
271
  };
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
272
273
274
275
276
  #define OCOTP_CFG3			0x440
  #define OCOTP_CFG3_SPEED_SHIFT		16
  #define OCOTP_CFG3_SPEED_1P2GHZ		0x3
  #define OCOTP_CFG3_SPEED_996MHZ		0x2
  #define OCOTP_CFG3_SPEED_852MHZ		0x1
4bd8459b6   Peng Fan   cpufreq: imx6q: r...
277
  static int imx6q_opp_check_speed_grading(struct device *dev)
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
278
279
280
281
  {
  	struct device_node *np;
  	void __iomem *base;
  	u32 val;
4bd8459b6   Peng Fan   cpufreq: imx6q: r...
282
  	int ret;
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
283

4bd8459b6   Peng Fan   cpufreq: imx6q: r...
284
285
286
287
288
289
290
291
  	if (of_find_property(dev->of_node, "nvmem-cells", NULL)) {
  		ret = nvmem_cell_read_u32(dev, "speed_grade", &val);
  		if (ret)
  			return ret;
  	} else {
  		np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
  		if (!np)
  			return -ENOENT;
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
292

4bd8459b6   Peng Fan   cpufreq: imx6q: r...
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
  		base = of_iomap(np, 0);
  		of_node_put(np);
  		if (!base) {
  			dev_err(dev, "failed to map ocotp
  ");
  			return -EFAULT;
  		}
  
  		/*
  		 * SPEED_GRADING[1:0] defines the max speed of ARM:
  		 * 2b'11: 1200000000Hz;
  		 * 2b'10: 996000000Hz;
  		 * 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz.
  		 * 2b'00: 792000000Hz;
  		 * We need to set the max speed of ARM according to fuse map.
  		 */
  		val = readl_relaxed(base + OCOTP_CFG3);
  		iounmap(base);
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
311
  	}
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
312
313
  	val >>= OCOTP_CFG3_SPEED_SHIFT;
  	val &= 0x3;
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
314
315
316
317
  	if (val < OCOTP_CFG3_SPEED_996MHZ)
  		if (dev_pm_opp_disable(dev, 996000000))
  			dev_warn(dev, "failed to disable 996MHz OPP
  ");
ccc153a6d   Lucas Stach   cpufreq: imx6q: f...
318
319
320
  
  	if (of_machine_is_compatible("fsl,imx6q") ||
  	    of_machine_is_compatible("fsl,imx6qp")) {
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
321
322
323
324
  		if (val != OCOTP_CFG3_SPEED_852MHZ)
  			if (dev_pm_opp_disable(dev, 852000000))
  				dev_warn(dev, "failed to disable 852MHz OPP
  ");
ccc153a6d   Lucas Stach   cpufreq: imx6q: f...
325
326
327
328
  		if (val != OCOTP_CFG3_SPEED_1P2GHZ)
  			if (dev_pm_opp_disable(dev, 1200000000))
  				dev_warn(dev, "failed to disable 1.2GHz OPP
  ");
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
329
  	}
4bd8459b6   Peng Fan   cpufreq: imx6q: r...
330
331
  
  	return 0;
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
332
  }
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
333
  #define OCOTP_CFG3_6UL_SPEED_696MHZ	0x2
0aa9abd4c   Sébastien Szymanski   cpufreq: imx6q: c...
334
335
  #define OCOTP_CFG3_6ULL_SPEED_792MHZ	0x2
  #define OCOTP_CFG3_6ULL_SPEED_900MHZ	0x3
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
336

2733fb0d0   Anson Huang   cpufreq: imx6q: r...
337
  static int imx6ul_opp_check_speed_grading(struct device *dev)
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
338
  {
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
339
  	u32 val;
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
340
  	int ret = 0;
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
341

2733fb0d0   Anson Huang   cpufreq: imx6q: r...
342
343
344
345
346
347
348
349
350
351
  	if (of_find_property(dev->of_node, "nvmem-cells", NULL)) {
  		ret = nvmem_cell_read_u32(dev, "speed_grade", &val);
  		if (ret)
  			return ret;
  	} else {
  		struct device_node *np;
  		void __iomem *base;
  
  		np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-ocotp");
  		if (!np)
36eb7dc1b   Christoph Niedermaier   cpufreq: imx6q: F...
352
353
354
  			np = of_find_compatible_node(NULL, NULL,
  						     "fsl,imx6ull-ocotp");
  		if (!np)
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
355
356
357
358
359
360
361
362
363
  			return -ENOENT;
  
  		base = of_iomap(np, 0);
  		of_node_put(np);
  		if (!base) {
  			dev_err(dev, "failed to map ocotp
  ");
  			return -EFAULT;
  		}
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
364

2733fb0d0   Anson Huang   cpufreq: imx6q: r...
365
366
  		val = readl_relaxed(base + OCOTP_CFG3);
  		iounmap(base);
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
367
368
369
370
371
372
  	}
  
  	/*
  	 * Speed GRADING[1:0] defines the max speed of ARM:
  	 * 2b'00: Reserved;
  	 * 2b'01: 528000000Hz;
0aa9abd4c   Sébastien Szymanski   cpufreq: imx6q: c...
373
374
  	 * 2b'10: 696000000Hz on i.MX6UL, 792000000Hz on i.MX6ULL;
  	 * 2b'11: 900000000Hz on i.MX6ULL only;
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
375
376
  	 * We need to set the max speed of ARM according to fuse map.
  	 */
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
377
378
  	val >>= OCOTP_CFG3_SPEED_SHIFT;
  	val &= 0x3;
0aa9abd4c   Sébastien Szymanski   cpufreq: imx6q: c...
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
  
  	if (of_machine_is_compatible("fsl,imx6ul")) {
  		if (val != OCOTP_CFG3_6UL_SPEED_696MHZ)
  			if (dev_pm_opp_disable(dev, 696000000))
  				dev_warn(dev, "failed to disable 696MHz OPP
  ");
  	}
  
  	if (of_machine_is_compatible("fsl,imx6ull")) {
  		if (val != OCOTP_CFG3_6ULL_SPEED_792MHZ)
  			if (dev_pm_opp_disable(dev, 792000000))
  				dev_warn(dev, "failed to disable 792MHz OPP
  ");
  
  		if (val != OCOTP_CFG3_6ULL_SPEED_900MHZ)
  			if (dev_pm_opp_disable(dev, 900000000))
  				dev_warn(dev, "failed to disable 900MHz OPP
  ");
  	}
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
398
  	return ret;
5028f5d2b   Anson Huang   cpufreq: imx6q: a...
399
  }
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
  static int imx6_cpufreq_pm_notify(struct notifier_block *nb,
  	unsigned long event, void *dummy)
  {
  	int ret;
  
  	switch (event) {
  	case PM_SUSPEND_PREPARE:
  		if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
  			ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MAX, 0);
  			if (ret) {
  				dev_err(cpu_dev,
  					"failed to scale dc_reg to max: %d
  ", ret);
  				return ret;
  			}
  		}
  		break;
  	case PM_POST_SUSPEND:
  		if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
  			ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MIN, 0);
  			if (ret) {
  				dev_err(cpu_dev,
  					"failed to scale dc_reg to min: %d
  ", ret);
  				return ret;
  			}
  		}
  		break;
  	default:
  		break;
  	}
  
  	return NOTIFY_OK;
  }
  
  static struct notifier_block imx6_cpufreq_pm_notifier = {
  	.notifier_call = imx6_cpufreq_pm_notify,
  };
1dd538f07   Shawn Guo   cpufreq: add imx6...
438
439
440
  static int imx6q_cpufreq_probe(struct platform_device *pdev)
  {
  	struct device_node *np;
47d43ba73   Nishanth Menon   PM / OPP: rename ...
441
  	struct dev_pm_opp *opp;
1dd538f07   Shawn Guo   cpufreq: add imx6...
442
443
  	unsigned long min_volt, max_volt;
  	int num, ret;
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
444
445
446
  	const struct property *prop;
  	const __be32 *val;
  	u32 nr, i, j;
1dd538f07   Shawn Guo   cpufreq: add imx6...
447

b494b48da   Sudeep KarkadaNagesha   cpufreq: imx6q-cp...
448
449
450
451
452
453
  	cpu_dev = get_cpu_device(0);
  	if (!cpu_dev) {
  		pr_err("failed to get cpu0 device
  ");
  		return -ENODEV;
  	}
1dd538f07   Shawn Guo   cpufreq: add imx6...
454

cdc58d602   Sudeep KarkadaNagesha   cpufreq: imx6q-cp...
455
  	np = of_node_get(cpu_dev->of_node);
1dd538f07   Shawn Guo   cpufreq: add imx6...
456
457
458
459
460
  	if (!np) {
  		dev_err(cpu_dev, "failed to find cpu0 node
  ");
  		return -ENOENT;
  	}
3fafb4e77   Octavian Purdila   cpufreq: imx6q: i...
461
  	if (of_machine_is_compatible("fsl,imx6ul") ||
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
462
463
464
465
466
467
468
469
  	    of_machine_is_compatible("fsl,imx6ull"))
  		num_clks = IMX6UL_CPUFREQ_CLK_NUM;
  	else
  		num_clks = IMX6Q_CPUFREQ_CLK_NUM;
  
  	ret = clk_bulk_get(cpu_dev, num_clks, clks);
  	if (ret)
  		goto put_node;
a35fc5a33   Bai Ping   cpufreq: imx: upd...
470

f8269c192   Philipp Zabel   cpufreq: imx6q: D...
471
  	arm_reg = regulator_get(cpu_dev, "arm");
22d0628a2   Anson Huang   cpufreq: imx6: re...
472
  	pu_reg = regulator_get_optional(cpu_dev, "pu");
f8269c192   Philipp Zabel   cpufreq: imx6q: D...
473
  	soc_reg = regulator_get(cpu_dev, "soc");
54cad2fce   Irina Tirdea   cpufreq: imx6q: F...
474
475
476
477
478
479
480
481
  	if (PTR_ERR(arm_reg) == -EPROBE_DEFER ||
  			PTR_ERR(soc_reg) == -EPROBE_DEFER ||
  			PTR_ERR(pu_reg) == -EPROBE_DEFER) {
  		ret = -EPROBE_DEFER;
  		dev_dbg(cpu_dev, "regulators not ready, defer
  ");
  		goto put_reg;
  	}
22d0628a2   Anson Huang   cpufreq: imx6: re...
482
  	if (IS_ERR(arm_reg) || IS_ERR(soc_reg)) {
1dd538f07   Shawn Guo   cpufreq: add imx6...
483
484
485
  		dev_err(cpu_dev, "failed to get regulators
  ");
  		ret = -ENOENT;
f8269c192   Philipp Zabel   cpufreq: imx6q: D...
486
  		goto put_reg;
1dd538f07   Shawn Guo   cpufreq: add imx6...
487
  	}
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
488
489
490
491
  	dc_reg = devm_regulator_get_optional(cpu_dev, "dc");
  
  	/* On i.MX6ULL, check the 24MHz low power run mode support */
  	low_power_run_support = of_property_read_bool(np, "fsl,low-power-run");
2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
492
493
494
495
496
497
  	ret = dev_pm_opp_of_add_table(cpu_dev);
  	if (ret < 0) {
  		dev_err(cpu_dev, "failed to init OPP table: %d
  ", ret);
  		goto put_reg;
  	}
20b7cbe29   John Tobias   cpufreq: imx6q: a...
498

0aa9abd4c   Sébastien Szymanski   cpufreq: imx6q: c...
499
  	if (of_machine_is_compatible("fsl,imx6ul") ||
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
500
501
  	    of_machine_is_compatible("fsl,imx6ull")) {
  		ret = imx6ul_opp_check_speed_grading(cpu_dev);
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
502
  	} else {
4bd8459b6   Peng Fan   cpufreq: imx6q: r...
503
504
505
  		ret = imx6q_opp_check_speed_grading(cpu_dev);
  	}
  	if (ret) {
74a189ef0   Anson Huang   cpufreq: imx6q-cp...
506
507
508
509
  		if (ret != -EPROBE_DEFER)
  			dev_err(cpu_dev, "failed to read ocotp: %d
  ",
  				ret);
4bd8459b6   Peng Fan   cpufreq: imx6q: r...
510
  		goto out_free_opp;
2733fb0d0   Anson Huang   cpufreq: imx6q: r...
511
  	}
cc87b8a8e   Viresh Kumar   imx6q: free OPP t...
512

2b3d58a3a   Fabio Estevam   cpufreq: imx6q: M...
513
514
515
516
517
518
  	num = dev_pm_opp_get_opp_count(cpu_dev);
  	if (num < 0) {
  		ret = num;
  		dev_err(cpu_dev, "no OPP table is found: %d
  ", ret);
  		goto out_free_opp;
1dd538f07   Shawn Guo   cpufreq: add imx6...
519
  	}
5d4879cda   Nishanth Menon   PM / OPP: rename ...
520
  	ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
1dd538f07   Shawn Guo   cpufreq: add imx6...
521
522
523
  	if (ret) {
  		dev_err(cpu_dev, "failed to init cpufreq table: %d
  ", ret);
eafca8516   Christophe Jaillet   cpufreq: imx6q: F...
524
  		goto out_free_opp;
1dd538f07   Shawn Guo   cpufreq: add imx6...
525
  	}
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
  	/*
  	 * On i.MX6UL/ULL EVK board, if the SOC is run in overide frequency,
  	 * the dc_regulator voltage should not be touched.
  	 */
  	if (freq_table[num - 1].frequency > FREQ_528_MHZ)
  		ignore_dc_reg = true;
  	if (!IS_ERR(dc_reg) && !ignore_dc_reg) {
  		ret = regulator_set_voltage_tol(dc_reg, DC_VOLTAGE_MIN, 0);
  		if (ret) {
  			dev_err(cpu_dev,
  				"failed to scale dc_reg to min: %d
  ", ret);
  			return ret;
  		}
  	}
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
541
  	/* Make imx6_soc_volt array's size same as arm opp number */
a86854d0c   Kees Cook   treewide: devm_kz...
542
543
  	imx6_soc_volt = devm_kcalloc(cpu_dev, num, sizeof(*imx6_soc_volt),
  				     GFP_KERNEL);
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
  	if (imx6_soc_volt == NULL) {
  		ret = -ENOMEM;
  		goto free_freq_table;
  	}
  
  	prop = of_find_property(np, "fsl,soc-operating-points", NULL);
  	if (!prop || !prop->value)
  		goto soc_opp_out;
  
  	/*
  	 * Each OPP is a set of tuples consisting of frequency and
  	 * voltage like <freq-kHz vol-uV>.
  	 */
  	nr = prop->length / sizeof(u32);
  	if (nr % 2 || (nr / 2) < num)
  		goto soc_opp_out;
  
  	for (j = 0; j < num; j++) {
  		val = prop->value;
  		for (i = 0; i < nr / 2; i++) {
  			unsigned long freq = be32_to_cpup(val++);
  			unsigned long volt = be32_to_cpup(val++);
  			if (freq_table[j].frequency == freq) {
  				imx6_soc_volt[soc_opp_count++] = volt;
  				break;
  			}
  		}
  	}
  
  soc_opp_out:
  	/* use fixed soc opp volt if no valid soc opp info found in dtb */
  	if (soc_opp_count != num) {
  		dev_warn(cpu_dev, "can NOT find valid fsl,soc-operating-points property in dtb, use default value!
  ");
  		for (j = 0; j < num; j++)
  			imx6_soc_volt[j] = PU_SOC_VOLTAGE_NORMAL;
  		if (freq_table[num - 1].frequency * 1000 == FREQ_1P2_GHZ)
  			imx6_soc_volt[num - 1] = PU_SOC_VOLTAGE_HIGH;
  	}
1dd538f07   Shawn Guo   cpufreq: add imx6...
583
584
585
586
  	if (of_property_read_u32(np, "clock-latency", &transition_latency))
  		transition_latency = CPUFREQ_ETERNAL;
  
  	/*
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
587
588
589
590
591
592
  	 * Calculate the ramp time for max voltage change in the
  	 * VDDSOC and VDDPU regulators.
  	 */
  	ret = regulator_set_voltage_time(soc_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]);
  	if (ret > 0)
  		transition_latency += ret * 1000;
22d0628a2   Anson Huang   cpufreq: imx6: re...
593
594
595
596
597
  	if (!IS_ERR(pu_reg)) {
  		ret = regulator_set_voltage_time(pu_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]);
  		if (ret > 0)
  			transition_latency += ret * 1000;
  	}
b4573d1d6   Anson Huang   cpufreq: imx6q: c...
598
599
  
  	/*
1dd538f07   Shawn Guo   cpufreq: add imx6...
600
601
602
603
  	 * OPP is maintained in order of increasing frequency, and
  	 * freq_table initialised from OPP is therefore sorted in the
  	 * same order.
  	 */
8d768cdcf   Viresh Kumar   cpufreq: imx6q: F...
604
  	max_freq = freq_table[--num].frequency;
5d4879cda   Nishanth Menon   PM / OPP: rename ...
605
  	opp = dev_pm_opp_find_freq_exact(cpu_dev,
1dd538f07   Shawn Guo   cpufreq: add imx6...
606
  				  freq_table[0].frequency * 1000, true);
5d4879cda   Nishanth Menon   PM / OPP: rename ...
607
  	min_volt = dev_pm_opp_get_voltage(opp);
8a31d9d94   Viresh Kumar   PM / OPP: Update ...
608
  	dev_pm_opp_put(opp);
8d768cdcf   Viresh Kumar   cpufreq: imx6q: F...
609
  	opp = dev_pm_opp_find_freq_exact(cpu_dev, max_freq * 1000, true);
5d4879cda   Nishanth Menon   PM / OPP: rename ...
610
  	max_volt = dev_pm_opp_get_voltage(opp);
8a31d9d94   Viresh Kumar   PM / OPP: Update ...
611
  	dev_pm_opp_put(opp);
1dd538f07   Shawn Guo   cpufreq: add imx6...
612
613
614
  	ret = regulator_set_voltage_time(arm_reg, min_volt, max_volt);
  	if (ret > 0)
  		transition_latency += ret * 1000;
1dd538f07   Shawn Guo   cpufreq: add imx6...
615
616
617
618
619
620
  	ret = cpufreq_register_driver(&imx6q_cpufreq_driver);
  	if (ret) {
  		dev_err(cpu_dev, "failed register driver: %d
  ", ret);
  		goto free_freq_table;
  	}
aaa5d8130   Jacky Bai   cpufreq: imx6: Ad...
621
  	register_pm_notifier(&imx6_cpufreq_pm_notifier);
1dd538f07   Shawn Guo   cpufreq: add imx6...
622
623
624
625
  	of_node_put(np);
  	return 0;
  
  free_freq_table:
5d4879cda   Nishanth Menon   PM / OPP: rename ...
626
  	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
cc87b8a8e   Viresh Kumar   imx6q: free OPP t...
627
  out_free_opp:
ded10c47f   Viresh Kumar   cpufreq: imx6q: U...
628
  	dev_pm_opp_of_remove_table(cpu_dev);
f8269c192   Philipp Zabel   cpufreq: imx6q: D...
629
630
631
632
633
634
635
  put_reg:
  	if (!IS_ERR(arm_reg))
  		regulator_put(arm_reg);
  	if (!IS_ERR(pu_reg))
  		regulator_put(pu_reg);
  	if (!IS_ERR(soc_reg))
  		regulator_put(soc_reg);
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
636
637
638
  
  	clk_bulk_put(num_clks, clks);
  put_node:
1dd538f07   Shawn Guo   cpufreq: add imx6...
639
  	of_node_put(np);
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
640

1dd538f07   Shawn Guo   cpufreq: add imx6...
641
642
643
644
645
646
  	return ret;
  }
  
  static int imx6q_cpufreq_remove(struct platform_device *pdev)
  {
  	cpufreq_unregister_driver(&imx6q_cpufreq_driver);
5d4879cda   Nishanth Menon   PM / OPP: rename ...
647
  	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
ded10c47f   Viresh Kumar   cpufreq: imx6q: U...
648
  	dev_pm_opp_of_remove_table(cpu_dev);
f8269c192   Philipp Zabel   cpufreq: imx6q: D...
649
  	regulator_put(arm_reg);
22d0628a2   Anson Huang   cpufreq: imx6: re...
650
651
  	if (!IS_ERR(pu_reg))
  		regulator_put(pu_reg);
f8269c192   Philipp Zabel   cpufreq: imx6q: D...
652
  	regulator_put(soc_reg);
2332bd041   Dong Aisheng   cpufreq: imx6q: s...
653
654
  
  	clk_bulk_put(num_clks, clks);
1dd538f07   Shawn Guo   cpufreq: add imx6...
655
656
657
658
659
660
661
  
  	return 0;
  }
  
  static struct platform_driver imx6q_cpufreq_platdrv = {
  	.driver = {
  		.name	= "imx6q-cpufreq",
1dd538f07   Shawn Guo   cpufreq: add imx6...
662
663
664
665
666
  	},
  	.probe		= imx6q_cpufreq_probe,
  	.remove		= imx6q_cpufreq_remove,
  };
  module_platform_driver(imx6q_cpufreq_platdrv);
d0404738c   Nicolas Chauvet   arm: imx: Add MOD...
667
  MODULE_ALIAS("platform:imx6q-cpufreq");
1dd538f07   Shawn Guo   cpufreq: add imx6...
668
669
670
  MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
  MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver");
  MODULE_LICENSE("GPL");