xref: /openbmc/linux/drivers/cpufreq/imx6q-cpufreq.c (revision b97d6790d03b763eca08847a9a5869a4291b9f9a)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
21dd538f0SShawn Guo /*
31dd538f0SShawn Guo  * Copyright (C) 2013 Freescale Semiconductor, Inc.
41dd538f0SShawn Guo  */
51dd538f0SShawn Guo 
61dd538f0SShawn Guo #include <linux/clk.h>
7b494b48dSSudeep KarkadaNagesha #include <linux/cpu.h>
81dd538f0SShawn Guo #include <linux/cpufreq.h>
91dd538f0SShawn Guo #include <linux/err.h>
101dd538f0SShawn Guo #include <linux/module.h>
112733fb0dSAnson Huang #include <linux/nvmem-consumer.h>
121dd538f0SShawn Guo #include <linux/of.h>
132b3d58a3SFabio Estevam #include <linux/of_address.h>
14e4db1c74SNishanth Menon #include <linux/pm_opp.h>
151dd538f0SShawn Guo #include <linux/platform_device.h>
161dd538f0SShawn Guo #include <linux/regulator/consumer.h>
171dd538f0SShawn Guo 
181dd538f0SShawn Guo #define PU_SOC_VOLTAGE_NORMAL	1250000
191dd538f0SShawn Guo #define PU_SOC_VOLTAGE_HIGH	1275000
201dd538f0SShawn Guo #define FREQ_1P2_GHZ		1200000000
211dd538f0SShawn Guo 
221dd538f0SShawn Guo static struct regulator *arm_reg;
231dd538f0SShawn Guo static struct regulator *pu_reg;
241dd538f0SShawn Guo static struct regulator *soc_reg;
251dd538f0SShawn Guo 
262332bd04SDong Aisheng enum IMX6_CPUFREQ_CLKS {
272332bd04SDong Aisheng 	ARM,
282332bd04SDong Aisheng 	PLL1_SYS,
292332bd04SDong Aisheng 	STEP,
302332bd04SDong Aisheng 	PLL1_SW,
312332bd04SDong Aisheng 	PLL2_PFD2_396M,
322332bd04SDong Aisheng 	/* MX6UL requires two more clks */
332332bd04SDong Aisheng 	PLL2_BUS,
342332bd04SDong Aisheng 	SECONDARY_SEL,
352332bd04SDong Aisheng };
362332bd04SDong Aisheng #define IMX6Q_CPUFREQ_CLK_NUM		5
372332bd04SDong Aisheng #define IMX6UL_CPUFREQ_CLK_NUM		7
381dd538f0SShawn Guo 
392332bd04SDong Aisheng static int num_clks;
402332bd04SDong Aisheng static struct clk_bulk_data clks[] = {
412332bd04SDong Aisheng 	{ .id = "arm" },
422332bd04SDong Aisheng 	{ .id = "pll1_sys" },
432332bd04SDong Aisheng 	{ .id = "step" },
442332bd04SDong Aisheng 	{ .id = "pll1_sw" },
452332bd04SDong Aisheng 	{ .id = "pll2_pfd2_396m" },
462332bd04SDong Aisheng 	{ .id = "pll2_bus" },
472332bd04SDong Aisheng 	{ .id = "secondary_sel" },
482332bd04SDong Aisheng };
49a35fc5a3SBai Ping 
501dd538f0SShawn Guo static struct device *cpu_dev;
511dd538f0SShawn Guo static struct cpufreq_frequency_table *freq_table;
528d768cdcSViresh Kumar static unsigned int max_freq;
531dd538f0SShawn Guo static unsigned int transition_latency;
541dd538f0SShawn Guo 
55b4573d1dSAnson Huang static u32 *imx6_soc_volt;
56b4573d1dSAnson Huang static u32 soc_opp_count;
57b4573d1dSAnson Huang 
imx6q_set_target(struct cpufreq_policy * policy,unsigned int index)589c0ebcf7SViresh Kumar static int imx6q_set_target(struct cpufreq_policy *policy, unsigned int index)
591dd538f0SShawn Guo {
6047d43ba7SNishanth Menon 	struct dev_pm_opp *opp;
611dd538f0SShawn Guo 	unsigned long freq_hz, volt, volt_old;
62d4019f0aSViresh Kumar 	unsigned int old_freq, new_freq;
63fded5fc8SLeonard Crestez 	bool pll1_sys_temp_enabled = false;
641dd538f0SShawn Guo 	int ret;
651dd538f0SShawn Guo 
66d4019f0aSViresh Kumar 	new_freq = freq_table[index].frequency;
67d4019f0aSViresh Kumar 	freq_hz = new_freq * 1000;
682332bd04SDong Aisheng 	old_freq = clk_get_rate(clks[ARM].clk) / 1000;
691dd538f0SShawn Guo 
705d4879cdSNishanth Menon 	opp = dev_pm_opp_find_freq_ceil(cpu_dev, &freq_hz);
711dd538f0SShawn Guo 	if (IS_ERR(opp)) {
721dd538f0SShawn Guo 		dev_err(cpu_dev, "failed to find OPP for %ld\n", freq_hz);
731dd538f0SShawn Guo 		return PTR_ERR(opp);
741dd538f0SShawn Guo 	}
751dd538f0SShawn Guo 
765d4879cdSNishanth Menon 	volt = dev_pm_opp_get_voltage(opp);
778a31d9d9SViresh Kumar 	dev_pm_opp_put(opp);
788a31d9d9SViresh Kumar 
791dd538f0SShawn Guo 	volt_old = regulator_get_voltage(arm_reg);
801dd538f0SShawn Guo 
811dd538f0SShawn Guo 	dev_dbg(cpu_dev, "%u MHz, %ld mV --> %u MHz, %ld mV\n",
82d4019f0aSViresh Kumar 		old_freq / 1000, volt_old / 1000,
83d4019f0aSViresh Kumar 		new_freq / 1000, volt / 1000);
845a571c35SViresh Kumar 
851dd538f0SShawn Guo 	/* scaling up?  scale voltage before frequency */
86d4019f0aSViresh Kumar 	if (new_freq > old_freq) {
8722d0628aSAnson Huang 		if (!IS_ERR(pu_reg)) {
88b4573d1dSAnson Huang 			ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0);
89b4573d1dSAnson Huang 			if (ret) {
90b4573d1dSAnson Huang 				dev_err(cpu_dev, "failed to scale vddpu up: %d\n", ret);
91b4573d1dSAnson Huang 				return ret;
92b4573d1dSAnson Huang 			}
9322d0628aSAnson Huang 		}
94b4573d1dSAnson Huang 		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0);
95b4573d1dSAnson Huang 		if (ret) {
96b4573d1dSAnson Huang 			dev_err(cpu_dev, "failed to scale vddsoc up: %d\n", ret);
97b4573d1dSAnson Huang 			return ret;
98b4573d1dSAnson Huang 		}
991dd538f0SShawn Guo 		ret = regulator_set_voltage_tol(arm_reg, volt, 0);
1001dd538f0SShawn Guo 		if (ret) {
1011dd538f0SShawn Guo 			dev_err(cpu_dev,
1021dd538f0SShawn Guo 				"failed to scale vddarm up: %d\n", ret);
103d4019f0aSViresh Kumar 			return ret;
1041dd538f0SShawn Guo 		}
1051dd538f0SShawn Guo 	}
1061dd538f0SShawn Guo 
1071dd538f0SShawn Guo 	/*
1081dd538f0SShawn Guo 	 * The setpoints are selected per PLL/PDF frequencies, so we need to
1091dd538f0SShawn Guo 	 * reprogram PLL for frequency scaling.  The procedure of reprogramming
1101dd538f0SShawn Guo 	 * PLL1 is as below.
111a35fc5a3SBai Ping 	 * For i.MX6UL, it has a secondary clk mux, the cpu frequency change
112a35fc5a3SBai Ping 	 * flow is slightly different from other i.MX6 OSC.
113a35fc5a3SBai Ping 	 * The cpu frequeny change flow for i.MX6(except i.MX6UL) is as below:
1141dd538f0SShawn Guo 	 *  - Enable pll2_pfd2_396m_clk and reparent pll1_sw_clk to it
1151dd538f0SShawn Guo 	 *  - Reprogram pll1_sys_clk and reparent pll1_sw_clk back to it
1161dd538f0SShawn Guo 	 *  - Disable pll2_pfd2_396m_clk
1171dd538f0SShawn Guo 	 */
1183fafb4e7SOctavian Purdila 	if (of_machine_is_compatible("fsl,imx6ul") ||
1193fafb4e7SOctavian Purdila 	    of_machine_is_compatible("fsl,imx6ull")) {
120a35fc5a3SBai Ping 		/*
121a35fc5a3SBai Ping 		 * When changing pll1_sw_clk's parent to pll1_sys_clk,
122a35fc5a3SBai Ping 		 * CPU may run at higher than 528MHz, this will lead to
123a35fc5a3SBai Ping 		 * the system unstable if the voltage is lower than the
124a35fc5a3SBai Ping 		 * voltage of 528MHz, so lower the CPU frequency to one
125a35fc5a3SBai Ping 		 * half before changing CPU frequency.
126a35fc5a3SBai Ping 		 */
1272332bd04SDong Aisheng 		clk_set_rate(clks[ARM].clk, (old_freq >> 1) * 1000);
1282332bd04SDong Aisheng 		clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
1292332bd04SDong Aisheng 		if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk))
1302332bd04SDong Aisheng 			clk_set_parent(clks[SECONDARY_SEL].clk,
1312332bd04SDong Aisheng 				       clks[PLL2_BUS].clk);
132a35fc5a3SBai Ping 		else
1332332bd04SDong Aisheng 			clk_set_parent(clks[SECONDARY_SEL].clk,
1342332bd04SDong Aisheng 				       clks[PLL2_PFD2_396M].clk);
1352332bd04SDong Aisheng 		clk_set_parent(clks[STEP].clk, clks[SECONDARY_SEL].clk);
1362332bd04SDong Aisheng 		clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk);
1375028f5d2SAnson Huang 		if (freq_hz > clk_get_rate(clks[PLL2_BUS].clk)) {
1385028f5d2SAnson Huang 			clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000);
1395028f5d2SAnson Huang 			clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
1405028f5d2SAnson Huang 		}
141a35fc5a3SBai Ping 	} else {
1422332bd04SDong Aisheng 		clk_set_parent(clks[STEP].clk, clks[PLL2_PFD2_396M].clk);
1432332bd04SDong Aisheng 		clk_set_parent(clks[PLL1_SW].clk, clks[STEP].clk);
1442332bd04SDong Aisheng 		if (freq_hz > clk_get_rate(clks[PLL2_PFD2_396M].clk)) {
1452332bd04SDong Aisheng 			clk_set_rate(clks[PLL1_SYS].clk, new_freq * 1000);
1462332bd04SDong Aisheng 			clk_set_parent(clks[PLL1_SW].clk, clks[PLL1_SYS].clk);
147fded5fc8SLeonard Crestez 		} else {
148fded5fc8SLeonard Crestez 			/* pll1_sys needs to be enabled for divider rate change to work. */
149fded5fc8SLeonard Crestez 			pll1_sys_temp_enabled = true;
1502332bd04SDong Aisheng 			clk_prepare_enable(clks[PLL1_SYS].clk);
1511dd538f0SShawn Guo 		}
152a35fc5a3SBai Ping 	}
1531dd538f0SShawn Guo 
1541dd538f0SShawn Guo 	/* Ensure the arm clock divider is what we expect */
1552332bd04SDong Aisheng 	ret = clk_set_rate(clks[ARM].clk, new_freq * 1000);
1561dd538f0SShawn Guo 	if (ret) {
1576ef28a04SAnson Huang 		int ret1;
1586ef28a04SAnson Huang 
1591dd538f0SShawn Guo 		dev_err(cpu_dev, "failed to set clock rate: %d\n", ret);
1606ef28a04SAnson Huang 		ret1 = regulator_set_voltage_tol(arm_reg, volt_old, 0);
1616ef28a04SAnson Huang 		if (ret1)
1626ef28a04SAnson Huang 			dev_warn(cpu_dev,
1636ef28a04SAnson Huang 				 "failed to restore vddarm voltage: %d\n", ret1);
164d4019f0aSViresh Kumar 		return ret;
1651dd538f0SShawn Guo 	}
1661dd538f0SShawn Guo 
167fded5fc8SLeonard Crestez 	/* PLL1 is only needed until after ARM-PODF is set. */
168fded5fc8SLeonard Crestez 	if (pll1_sys_temp_enabled)
1692332bd04SDong Aisheng 		clk_disable_unprepare(clks[PLL1_SYS].clk);
170fded5fc8SLeonard Crestez 
1711dd538f0SShawn Guo 	/* scaling down?  scale voltage after frequency */
172d4019f0aSViresh Kumar 	if (new_freq < old_freq) {
1731dd538f0SShawn Guo 		ret = regulator_set_voltage_tol(arm_reg, volt, 0);
17458ad4e61SAnson Huang 		if (ret)
1751dd538f0SShawn Guo 			dev_warn(cpu_dev,
1761dd538f0SShawn Guo 				 "failed to scale vddarm down: %d\n", ret);
177b4573d1dSAnson Huang 		ret = regulator_set_voltage_tol(soc_reg, imx6_soc_volt[index], 0);
17858ad4e61SAnson Huang 		if (ret)
179b4573d1dSAnson Huang 			dev_warn(cpu_dev, "failed to scale vddsoc down: %d\n", ret);
18022d0628aSAnson Huang 		if (!IS_ERR(pu_reg)) {
181b4573d1dSAnson Huang 			ret = regulator_set_voltage_tol(pu_reg, imx6_soc_volt[index], 0);
18258ad4e61SAnson Huang 			if (ret)
183b4573d1dSAnson Huang 				dev_warn(cpu_dev, "failed to scale vddpu down: %d\n", ret);
1841dd538f0SShawn Guo 		}
18522d0628aSAnson Huang 	}
1861dd538f0SShawn Guo 
187d4019f0aSViresh Kumar 	return 0;
1881dd538f0SShawn Guo }
1891dd538f0SShawn Guo 
imx6q_cpufreq_init(struct cpufreq_policy * policy)1901dd538f0SShawn Guo static int imx6q_cpufreq_init(struct cpufreq_policy *policy)
1911dd538f0SShawn Guo {
1922332bd04SDong Aisheng 	policy->clk = clks[ARM].clk;
193c4dcc8a1SViresh Kumar 	cpufreq_generic_init(policy, freq_table, transition_latency);
1948d768cdcSViresh Kumar 	policy->suspend_freq = max_freq;
1955aa1599fSLeonard Crestez 
196c4dcc8a1SViresh Kumar 	return 0;
1971dd538f0SShawn Guo }
1981dd538f0SShawn Guo 
1991dd538f0SShawn Guo static struct cpufreq_driver imx6q_cpufreq_driver = {
2004b498869SAmit Kucheria 	.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |
2014b498869SAmit Kucheria 		 CPUFREQ_IS_COOLING_DEV,
2024f6ba385SViresh Kumar 	.verify = cpufreq_generic_frequency_table_verify,
2039c0ebcf7SViresh Kumar 	.target_index = imx6q_set_target,
204652ed95dSViresh Kumar 	.get = cpufreq_generic_get,
2051dd538f0SShawn Guo 	.init = imx6q_cpufreq_init,
206fcd300c6SViresh Kumar 	.register_em = cpufreq_register_em_with_opp,
2071dd538f0SShawn Guo 	.name = "imx6q-cpufreq",
2084f6ba385SViresh Kumar 	.attr = cpufreq_generic_attr,
2095aa1599fSLeonard Crestez 	.suspend = cpufreq_generic_suspend,
2101dd538f0SShawn Guo };
2111dd538f0SShawn Guo 
imx6x_disable_freq_in_opp(struct device * dev,unsigned long freq)21211a3b0acSChristoph Niedermaier static void imx6x_disable_freq_in_opp(struct device *dev, unsigned long freq)
21311a3b0acSChristoph Niedermaier {
21411a3b0acSChristoph Niedermaier 	int ret = dev_pm_opp_disable(dev, freq);
21511a3b0acSChristoph Niedermaier 
21611a3b0acSChristoph Niedermaier 	if (ret < 0 && ret != -ENODEV)
21711a3b0acSChristoph Niedermaier 		dev_warn(dev, "failed to disable %ldMHz OPP\n", freq / 1000000);
21811a3b0acSChristoph Niedermaier }
21911a3b0acSChristoph Niedermaier 
2202b3d58a3SFabio Estevam #define OCOTP_CFG3			0x440
2212b3d58a3SFabio Estevam #define OCOTP_CFG3_SPEED_SHIFT		16
2222b3d58a3SFabio Estevam #define OCOTP_CFG3_SPEED_1P2GHZ		0x3
2232b3d58a3SFabio Estevam #define OCOTP_CFG3_SPEED_996MHZ		0x2
2242b3d58a3SFabio Estevam #define OCOTP_CFG3_SPEED_852MHZ		0x1
2252b3d58a3SFabio Estevam 
imx6q_opp_check_speed_grading(struct device * dev)2264bd8459bSPeng Fan static int imx6q_opp_check_speed_grading(struct device *dev)
2272b3d58a3SFabio Estevam {
2282b3d58a3SFabio Estevam 	struct device_node *np;
2292b3d58a3SFabio Estevam 	void __iomem *base;
2302b3d58a3SFabio Estevam 	u32 val;
2314bd8459bSPeng Fan 	int ret;
2322b3d58a3SFabio Estevam 
233b8f3a396SRob Herring 	if (of_property_present(dev->of_node, "nvmem-cells")) {
2344bd8459bSPeng Fan 		ret = nvmem_cell_read_u32(dev, "speed_grade", &val);
2354bd8459bSPeng Fan 		if (ret)
2364bd8459bSPeng Fan 			return ret;
2374bd8459bSPeng Fan 	} else {
2382b3d58a3SFabio Estevam 		np = of_find_compatible_node(NULL, NULL, "fsl,imx6q-ocotp");
2392b3d58a3SFabio Estevam 		if (!np)
2404bd8459bSPeng Fan 			return -ENOENT;
2412b3d58a3SFabio Estevam 
2422b3d58a3SFabio Estevam 		base = of_iomap(np, 0);
2434bd8459bSPeng Fan 		of_node_put(np);
2442b3d58a3SFabio Estevam 		if (!base) {
2452b3d58a3SFabio Estevam 			dev_err(dev, "failed to map ocotp\n");
2464bd8459bSPeng Fan 			return -EFAULT;
2472b3d58a3SFabio Estevam 		}
2482b3d58a3SFabio Estevam 
2492b3d58a3SFabio Estevam 		/*
2502b3d58a3SFabio Estevam 		 * SPEED_GRADING[1:0] defines the max speed of ARM:
2512b3d58a3SFabio Estevam 		 * 2b'11: 1200000000Hz;
2522b3d58a3SFabio Estevam 		 * 2b'10: 996000000Hz;
2532b3d58a3SFabio Estevam 		 * 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz.
2542b3d58a3SFabio Estevam 		 * 2b'00: 792000000Hz;
2552b3d58a3SFabio Estevam 		 * We need to set the max speed of ARM according to fuse map.
2562b3d58a3SFabio Estevam 		 */
2572b3d58a3SFabio Estevam 		val = readl_relaxed(base + OCOTP_CFG3);
2584bd8459bSPeng Fan 		iounmap(base);
2594bd8459bSPeng Fan 	}
2604bd8459bSPeng Fan 
2612b3d58a3SFabio Estevam 	val >>= OCOTP_CFG3_SPEED_SHIFT;
2622b3d58a3SFabio Estevam 	val &= 0x3;
2632b3d58a3SFabio Estevam 
2642b3d58a3SFabio Estevam 	if (val < OCOTP_CFG3_SPEED_996MHZ)
26511a3b0acSChristoph Niedermaier 		imx6x_disable_freq_in_opp(dev, 996000000);
266ccc153a6SLucas Stach 
267ccc153a6SLucas Stach 	if (of_machine_is_compatible("fsl,imx6q") ||
268ccc153a6SLucas Stach 	    of_machine_is_compatible("fsl,imx6qp")) {
2692b3d58a3SFabio Estevam 		if (val != OCOTP_CFG3_SPEED_852MHZ)
27011a3b0acSChristoph Niedermaier 			imx6x_disable_freq_in_opp(dev, 852000000);
27111a3b0acSChristoph Niedermaier 
272ccc153a6SLucas Stach 		if (val != OCOTP_CFG3_SPEED_1P2GHZ)
27311a3b0acSChristoph Niedermaier 			imx6x_disable_freq_in_opp(dev, 1200000000);
2742b3d58a3SFabio Estevam 	}
2754bd8459bSPeng Fan 
2764bd8459bSPeng Fan 	return 0;
2772b3d58a3SFabio Estevam }
2782b3d58a3SFabio Estevam 
2795028f5d2SAnson Huang #define OCOTP_CFG3_6UL_SPEED_696MHZ	0x2
2800aa9abd4SSébastien Szymanski #define OCOTP_CFG3_6ULL_SPEED_792MHZ	0x2
2810aa9abd4SSébastien Szymanski #define OCOTP_CFG3_6ULL_SPEED_900MHZ	0x3
2825028f5d2SAnson Huang 
imx6ul_opp_check_speed_grading(struct device * dev)2832733fb0dSAnson Huang static int imx6ul_opp_check_speed_grading(struct device *dev)
2845028f5d2SAnson Huang {
2852733fb0dSAnson Huang 	u32 val;
2862733fb0dSAnson Huang 	int ret = 0;
2872733fb0dSAnson Huang 
288b8f3a396SRob Herring 	if (of_property_present(dev->of_node, "nvmem-cells")) {
2892733fb0dSAnson Huang 		ret = nvmem_cell_read_u32(dev, "speed_grade", &val);
2902733fb0dSAnson Huang 		if (ret)
2912733fb0dSAnson Huang 			return ret;
2922733fb0dSAnson Huang 	} else {
2935028f5d2SAnson Huang 		struct device_node *np;
2945028f5d2SAnson Huang 		void __iomem *base;
2955028f5d2SAnson Huang 
2965028f5d2SAnson Huang 		np = of_find_compatible_node(NULL, NULL, "fsl,imx6ul-ocotp");
2975028f5d2SAnson Huang 		if (!np)
29836eb7dc1SChristoph Niedermaier 			np = of_find_compatible_node(NULL, NULL,
29936eb7dc1SChristoph Niedermaier 						     "fsl,imx6ull-ocotp");
30036eb7dc1SChristoph Niedermaier 		if (!np)
3012733fb0dSAnson Huang 			return -ENOENT;
3025028f5d2SAnson Huang 
3035028f5d2SAnson Huang 		base = of_iomap(np, 0);
3042733fb0dSAnson Huang 		of_node_put(np);
3055028f5d2SAnson Huang 		if (!base) {
3065028f5d2SAnson Huang 			dev_err(dev, "failed to map ocotp\n");
3072733fb0dSAnson Huang 			return -EFAULT;
3082733fb0dSAnson Huang 		}
3092733fb0dSAnson Huang 
3102733fb0dSAnson Huang 		val = readl_relaxed(base + OCOTP_CFG3);
3112733fb0dSAnson Huang 		iounmap(base);
3125028f5d2SAnson Huang 	}
3135028f5d2SAnson Huang 
3145028f5d2SAnson Huang 	/*
3155028f5d2SAnson Huang 	 * Speed GRADING[1:0] defines the max speed of ARM:
3165028f5d2SAnson Huang 	 * 2b'00: Reserved;
3175028f5d2SAnson Huang 	 * 2b'01: 528000000Hz;
3180aa9abd4SSébastien Szymanski 	 * 2b'10: 696000000Hz on i.MX6UL, 792000000Hz on i.MX6ULL;
3190aa9abd4SSébastien Szymanski 	 * 2b'11: 900000000Hz on i.MX6ULL only;
3205028f5d2SAnson Huang 	 * We need to set the max speed of ARM according to fuse map.
3215028f5d2SAnson Huang 	 */
3225028f5d2SAnson Huang 	val >>= OCOTP_CFG3_SPEED_SHIFT;
3235028f5d2SAnson Huang 	val &= 0x3;
3240aa9abd4SSébastien Szymanski 
32511a3b0acSChristoph Niedermaier 	if (of_machine_is_compatible("fsl,imx6ul"))
3265028f5d2SAnson Huang 		if (val != OCOTP_CFG3_6UL_SPEED_696MHZ)
32711a3b0acSChristoph Niedermaier 			imx6x_disable_freq_in_opp(dev, 696000000);
3280aa9abd4SSébastien Szymanski 
3290aa9abd4SSébastien Szymanski 	if (of_machine_is_compatible("fsl,imx6ull")) {
330*59fe58d5SChristoph Niedermaier 		if (val < OCOTP_CFG3_6ULL_SPEED_792MHZ)
33111a3b0acSChristoph Niedermaier 			imx6x_disable_freq_in_opp(dev, 792000000);
3320aa9abd4SSébastien Szymanski 
3330aa9abd4SSébastien Szymanski 		if (val != OCOTP_CFG3_6ULL_SPEED_900MHZ)
33411a3b0acSChristoph Niedermaier 			imx6x_disable_freq_in_opp(dev, 900000000);
3350aa9abd4SSébastien Szymanski 	}
3360aa9abd4SSébastien Szymanski 
3372733fb0dSAnson Huang 	return ret;
3385028f5d2SAnson Huang }
3395028f5d2SAnson Huang 
imx6q_cpufreq_probe(struct platform_device * pdev)3401dd538f0SShawn Guo static int imx6q_cpufreq_probe(struct platform_device *pdev)
3411dd538f0SShawn Guo {
3421dd538f0SShawn Guo 	struct device_node *np;
34347d43ba7SNishanth Menon 	struct dev_pm_opp *opp;
3441dd538f0SShawn Guo 	unsigned long min_volt, max_volt;
3451dd538f0SShawn Guo 	int num, ret;
346b4573d1dSAnson Huang 	const struct property *prop;
347b4573d1dSAnson Huang 	const __be32 *val;
348b4573d1dSAnson Huang 	u32 nr, i, j;
3491dd538f0SShawn Guo 
350b494b48dSSudeep KarkadaNagesha 	cpu_dev = get_cpu_device(0);
351b494b48dSSudeep KarkadaNagesha 	if (!cpu_dev) {
352b494b48dSSudeep KarkadaNagesha 		pr_err("failed to get cpu0 device\n");
353b494b48dSSudeep KarkadaNagesha 		return -ENODEV;
354b494b48dSSudeep KarkadaNagesha 	}
3551dd538f0SShawn Guo 
356cdc58d60SSudeep KarkadaNagesha 	np = of_node_get(cpu_dev->of_node);
3571dd538f0SShawn Guo 	if (!np) {
3581dd538f0SShawn Guo 		dev_err(cpu_dev, "failed to find cpu0 node\n");
3591dd538f0SShawn Guo 		return -ENOENT;
3601dd538f0SShawn Guo 	}
3611dd538f0SShawn Guo 
3623fafb4e7SOctavian Purdila 	if (of_machine_is_compatible("fsl,imx6ul") ||
3632332bd04SDong Aisheng 	    of_machine_is_compatible("fsl,imx6ull"))
3642332bd04SDong Aisheng 		num_clks = IMX6UL_CPUFREQ_CLK_NUM;
3652332bd04SDong Aisheng 	else
3662332bd04SDong Aisheng 		num_clks = IMX6Q_CPUFREQ_CLK_NUM;
3672332bd04SDong Aisheng 
3682332bd04SDong Aisheng 	ret = clk_bulk_get(cpu_dev, num_clks, clks);
3692332bd04SDong Aisheng 	if (ret)
3702332bd04SDong Aisheng 		goto put_node;
371a35fc5a3SBai Ping 
372f8269c19SPhilipp Zabel 	arm_reg = regulator_get(cpu_dev, "arm");
37322d0628aSAnson Huang 	pu_reg = regulator_get_optional(cpu_dev, "pu");
374f8269c19SPhilipp Zabel 	soc_reg = regulator_get(cpu_dev, "soc");
37554cad2fcSIrina Tirdea 	if (PTR_ERR(arm_reg) == -EPROBE_DEFER ||
37654cad2fcSIrina Tirdea 			PTR_ERR(soc_reg) == -EPROBE_DEFER ||
37754cad2fcSIrina Tirdea 			PTR_ERR(pu_reg) == -EPROBE_DEFER) {
37854cad2fcSIrina Tirdea 		ret = -EPROBE_DEFER;
37954cad2fcSIrina Tirdea 		dev_dbg(cpu_dev, "regulators not ready, defer\n");
38054cad2fcSIrina Tirdea 		goto put_reg;
38154cad2fcSIrina Tirdea 	}
38222d0628aSAnson Huang 	if (IS_ERR(arm_reg) || IS_ERR(soc_reg)) {
3831dd538f0SShawn Guo 		dev_err(cpu_dev, "failed to get regulators\n");
3841dd538f0SShawn Guo 		ret = -ENOENT;
385f8269c19SPhilipp Zabel 		goto put_reg;
3861dd538f0SShawn Guo 	}
3871dd538f0SShawn Guo 
3888f8d37b2SViresh Kumar 	ret = dev_pm_opp_of_add_table(cpu_dev);
38920b7cbe2SJohn Tobias 	if (ret < 0) {
39020b7cbe2SJohn Tobias 		dev_err(cpu_dev, "failed to init OPP table: %d\n", ret);
391f8269c19SPhilipp Zabel 		goto put_reg;
39220b7cbe2SJohn Tobias 	}
39320b7cbe2SJohn Tobias 
3940aa9abd4SSébastien Szymanski 	if (of_machine_is_compatible("fsl,imx6ul") ||
3952733fb0dSAnson Huang 	    of_machine_is_compatible("fsl,imx6ull")) {
3962733fb0dSAnson Huang 		ret = imx6ul_opp_check_speed_grading(cpu_dev);
3974bd8459bSPeng Fan 	} else {
3984bd8459bSPeng Fan 		ret = imx6q_opp_check_speed_grading(cpu_dev);
3994bd8459bSPeng Fan 	}
400cccf6ae5SAnson Huang 	if (ret) {
401ab4fdc73SYang Yingliang 		dev_err_probe(cpu_dev, ret, "failed to read ocotp\n");
4023646f50aSPeng Fan 		goto out_free_opp;
4032733fb0dSAnson Huang 	}
4042b3d58a3SFabio Estevam 
4055d4879cdSNishanth Menon 	num = dev_pm_opp_get_opp_count(cpu_dev);
4061dd538f0SShawn Guo 	if (num < 0) {
4071dd538f0SShawn Guo 		ret = num;
4081dd538f0SShawn Guo 		dev_err(cpu_dev, "no OPP table is found: %d\n", ret);
409cc87b8a8SViresh Kumar 		goto out_free_opp;
4101dd538f0SShawn Guo 	}
4111dd538f0SShawn Guo 
4125d4879cdSNishanth Menon 	ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table);
4131dd538f0SShawn Guo 	if (ret) {
4141dd538f0SShawn Guo 		dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret);
415eafca851SChristophe Jaillet 		goto out_free_opp;
4161dd538f0SShawn Guo 	}
4171dd538f0SShawn Guo 
418b4573d1dSAnson Huang 	/* Make imx6_soc_volt array's size same as arm opp number */
419a86854d0SKees Cook 	imx6_soc_volt = devm_kcalloc(cpu_dev, num, sizeof(*imx6_soc_volt),
420a86854d0SKees Cook 				     GFP_KERNEL);
421b4573d1dSAnson Huang 	if (imx6_soc_volt == NULL) {
422b4573d1dSAnson Huang 		ret = -ENOMEM;
423b4573d1dSAnson Huang 		goto free_freq_table;
424b4573d1dSAnson Huang 	}
425b4573d1dSAnson Huang 
426b4573d1dSAnson Huang 	prop = of_find_property(np, "fsl,soc-operating-points", NULL);
427b4573d1dSAnson Huang 	if (!prop || !prop->value)
428b4573d1dSAnson Huang 		goto soc_opp_out;
429b4573d1dSAnson Huang 
430b4573d1dSAnson Huang 	/*
431b4573d1dSAnson Huang 	 * Each OPP is a set of tuples consisting of frequency and
432b4573d1dSAnson Huang 	 * voltage like <freq-kHz vol-uV>.
433b4573d1dSAnson Huang 	 */
434b4573d1dSAnson Huang 	nr = prop->length / sizeof(u32);
435b4573d1dSAnson Huang 	if (nr % 2 || (nr / 2) < num)
436b4573d1dSAnson Huang 		goto soc_opp_out;
437b4573d1dSAnson Huang 
438b4573d1dSAnson Huang 	for (j = 0; j < num; j++) {
439b4573d1dSAnson Huang 		val = prop->value;
440b4573d1dSAnson Huang 		for (i = 0; i < nr / 2; i++) {
441b4573d1dSAnson Huang 			unsigned long freq = be32_to_cpup(val++);
442b4573d1dSAnson Huang 			unsigned long volt = be32_to_cpup(val++);
443b4573d1dSAnson Huang 			if (freq_table[j].frequency == freq) {
444b4573d1dSAnson Huang 				imx6_soc_volt[soc_opp_count++] = volt;
445b4573d1dSAnson Huang 				break;
446b4573d1dSAnson Huang 			}
447b4573d1dSAnson Huang 		}
448b4573d1dSAnson Huang 	}
449b4573d1dSAnson Huang 
450b4573d1dSAnson Huang soc_opp_out:
451b4573d1dSAnson Huang 	/* use fixed soc opp volt if no valid soc opp info found in dtb */
452b4573d1dSAnson Huang 	if (soc_opp_count != num) {
453b4573d1dSAnson Huang 		dev_warn(cpu_dev, "can NOT find valid fsl,soc-operating-points property in dtb, use default value!\n");
454b4573d1dSAnson Huang 		for (j = 0; j < num; j++)
455b4573d1dSAnson Huang 			imx6_soc_volt[j] = PU_SOC_VOLTAGE_NORMAL;
456b4573d1dSAnson Huang 		if (freq_table[num - 1].frequency * 1000 == FREQ_1P2_GHZ)
457b4573d1dSAnson Huang 			imx6_soc_volt[num - 1] = PU_SOC_VOLTAGE_HIGH;
458b4573d1dSAnson Huang 	}
459b4573d1dSAnson Huang 
4601dd538f0SShawn Guo 	if (of_property_read_u32(np, "clock-latency", &transition_latency))
4611dd538f0SShawn Guo 		transition_latency = CPUFREQ_ETERNAL;
4621dd538f0SShawn Guo 
4631dd538f0SShawn Guo 	/*
464b4573d1dSAnson Huang 	 * Calculate the ramp time for max voltage change in the
465b4573d1dSAnson Huang 	 * VDDSOC and VDDPU regulators.
466b4573d1dSAnson Huang 	 */
467b4573d1dSAnson Huang 	ret = regulator_set_voltage_time(soc_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]);
468b4573d1dSAnson Huang 	if (ret > 0)
469b4573d1dSAnson Huang 		transition_latency += ret * 1000;
47022d0628aSAnson Huang 	if (!IS_ERR(pu_reg)) {
471b4573d1dSAnson Huang 		ret = regulator_set_voltage_time(pu_reg, imx6_soc_volt[0], imx6_soc_volt[num - 1]);
472b4573d1dSAnson Huang 		if (ret > 0)
473b4573d1dSAnson Huang 			transition_latency += ret * 1000;
47422d0628aSAnson Huang 	}
475b4573d1dSAnson Huang 
476b4573d1dSAnson Huang 	/*
4771dd538f0SShawn Guo 	 * OPP is maintained in order of increasing frequency, and
4781dd538f0SShawn Guo 	 * freq_table initialised from OPP is therefore sorted in the
4791dd538f0SShawn Guo 	 * same order.
4801dd538f0SShawn Guo 	 */
4818d768cdcSViresh Kumar 	max_freq = freq_table[--num].frequency;
4825d4879cdSNishanth Menon 	opp = dev_pm_opp_find_freq_exact(cpu_dev,
4831dd538f0SShawn Guo 				  freq_table[0].frequency * 1000, true);
4845d4879cdSNishanth Menon 	min_volt = dev_pm_opp_get_voltage(opp);
4858a31d9d9SViresh Kumar 	dev_pm_opp_put(opp);
4868d768cdcSViresh Kumar 	opp = dev_pm_opp_find_freq_exact(cpu_dev, max_freq * 1000, true);
4875d4879cdSNishanth Menon 	max_volt = dev_pm_opp_get_voltage(opp);
4888a31d9d9SViresh Kumar 	dev_pm_opp_put(opp);
4898a31d9d9SViresh Kumar 
4901dd538f0SShawn Guo 	ret = regulator_set_voltage_time(arm_reg, min_volt, max_volt);
4911dd538f0SShawn Guo 	if (ret > 0)
4921dd538f0SShawn Guo 		transition_latency += ret * 1000;
4931dd538f0SShawn Guo 
4941dd538f0SShawn Guo 	ret = cpufreq_register_driver(&imx6q_cpufreq_driver);
4951dd538f0SShawn Guo 	if (ret) {
4961dd538f0SShawn Guo 		dev_err(cpu_dev, "failed register driver: %d\n", ret);
4971dd538f0SShawn Guo 		goto free_freq_table;
4981dd538f0SShawn Guo 	}
4991dd538f0SShawn Guo 
5001dd538f0SShawn Guo 	of_node_put(np);
5011dd538f0SShawn Guo 	return 0;
5021dd538f0SShawn Guo 
5031dd538f0SShawn Guo free_freq_table:
5045d4879cdSNishanth Menon 	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
505cc87b8a8SViresh Kumar out_free_opp:
5068f8d37b2SViresh Kumar 	dev_pm_opp_of_remove_table(cpu_dev);
507f8269c19SPhilipp Zabel put_reg:
508f8269c19SPhilipp Zabel 	if (!IS_ERR(arm_reg))
509f8269c19SPhilipp Zabel 		regulator_put(arm_reg);
510f8269c19SPhilipp Zabel 	if (!IS_ERR(pu_reg))
511f8269c19SPhilipp Zabel 		regulator_put(pu_reg);
512f8269c19SPhilipp Zabel 	if (!IS_ERR(soc_reg))
513f8269c19SPhilipp Zabel 		regulator_put(soc_reg);
5142332bd04SDong Aisheng 
5152332bd04SDong Aisheng 	clk_bulk_put(num_clks, clks);
5162332bd04SDong Aisheng put_node:
5171dd538f0SShawn Guo 	of_node_put(np);
5182332bd04SDong Aisheng 
5191dd538f0SShawn Guo 	return ret;
5201dd538f0SShawn Guo }
5211dd538f0SShawn Guo 
imx6q_cpufreq_remove(struct platform_device * pdev)5221d61b32eSYangtao Li static void imx6q_cpufreq_remove(struct platform_device *pdev)
5231dd538f0SShawn Guo {
5241dd538f0SShawn Guo 	cpufreq_unregister_driver(&imx6q_cpufreq_driver);
5255d4879cdSNishanth Menon 	dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table);
5268f8d37b2SViresh Kumar 	dev_pm_opp_of_remove_table(cpu_dev);
527f8269c19SPhilipp Zabel 	regulator_put(arm_reg);
52822d0628aSAnson Huang 	if (!IS_ERR(pu_reg))
529f8269c19SPhilipp Zabel 		regulator_put(pu_reg);
530f8269c19SPhilipp Zabel 	regulator_put(soc_reg);
5312332bd04SDong Aisheng 
5322332bd04SDong Aisheng 	clk_bulk_put(num_clks, clks);
5331dd538f0SShawn Guo }
5341dd538f0SShawn Guo 
5351dd538f0SShawn Guo static struct platform_driver imx6q_cpufreq_platdrv = {
5361dd538f0SShawn Guo 	.driver = {
5371dd538f0SShawn Guo 		.name	= "imx6q-cpufreq",
5381dd538f0SShawn Guo 	},
5391dd538f0SShawn Guo 	.probe		= imx6q_cpufreq_probe,
5401d61b32eSYangtao Li 	.remove_new	= imx6q_cpufreq_remove,
5411dd538f0SShawn Guo };
5421dd538f0SShawn Guo module_platform_driver(imx6q_cpufreq_platdrv);
5431dd538f0SShawn Guo 
544d0404738SNicolas Chauvet MODULE_ALIAS("platform:imx6q-cpufreq");
5451dd538f0SShawn Guo MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
5461dd538f0SShawn Guo MODULE_DESCRIPTION("Freescale i.MX6Q cpufreq driver");
5471dd538f0SShawn Guo MODULE_LICENSE("GPL");
548