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