162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2019 NXP
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/clk.h>
762306a36Sopenharmony_ci#include <linux/cpu.h>
862306a36Sopenharmony_ci#include <linux/cpufreq.h>
962306a36Sopenharmony_ci#include <linux/err.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/nvmem-consumer.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/platform_device.h>
1662306a36Sopenharmony_ci#include <linux/pm_opp.h>
1762306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include "cpufreq-dt.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define OCOTP_CFG3_SPEED_GRADE_SHIFT	8
2362306a36Sopenharmony_ci#define OCOTP_CFG3_SPEED_GRADE_MASK	(0x3 << 8)
2462306a36Sopenharmony_ci#define IMX8MN_OCOTP_CFG3_SPEED_GRADE_MASK	(0xf << 8)
2562306a36Sopenharmony_ci#define OCOTP_CFG3_MKT_SEGMENT_SHIFT    6
2662306a36Sopenharmony_ci#define OCOTP_CFG3_MKT_SEGMENT_MASK     (0x3 << 6)
2762306a36Sopenharmony_ci#define IMX8MP_OCOTP_CFG3_MKT_SEGMENT_SHIFT    5
2862306a36Sopenharmony_ci#define IMX8MP_OCOTP_CFG3_MKT_SEGMENT_MASK     (0x3 << 5)
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define IMX7ULP_MAX_RUN_FREQ	528000
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/* cpufreq-dt device registered by imx-cpufreq-dt */
3362306a36Sopenharmony_cistatic struct platform_device *cpufreq_dt_pdev;
3462306a36Sopenharmony_cistatic struct device *cpu_dev;
3562306a36Sopenharmony_cistatic int cpufreq_opp_token;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cienum IMX7ULP_CPUFREQ_CLKS {
3862306a36Sopenharmony_ci	ARM,
3962306a36Sopenharmony_ci	CORE,
4062306a36Sopenharmony_ci	SCS_SEL,
4162306a36Sopenharmony_ci	HSRUN_CORE,
4262306a36Sopenharmony_ci	HSRUN_SCS_SEL,
4362306a36Sopenharmony_ci	FIRC,
4462306a36Sopenharmony_ci};
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic struct clk_bulk_data imx7ulp_clks[] = {
4762306a36Sopenharmony_ci	{ .id = "arm" },
4862306a36Sopenharmony_ci	{ .id = "core" },
4962306a36Sopenharmony_ci	{ .id = "scs_sel" },
5062306a36Sopenharmony_ci	{ .id = "hsrun_core" },
5162306a36Sopenharmony_ci	{ .id = "hsrun_scs_sel" },
5262306a36Sopenharmony_ci	{ .id = "firc" },
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic unsigned int imx7ulp_get_intermediate(struct cpufreq_policy *policy,
5662306a36Sopenharmony_ci					     unsigned int index)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	return clk_get_rate(imx7ulp_clks[FIRC].clk);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic int imx7ulp_target_intermediate(struct cpufreq_policy *policy,
6262306a36Sopenharmony_ci					unsigned int index)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	unsigned int newfreq = policy->freq_table[index].frequency;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	clk_set_parent(imx7ulp_clks[SCS_SEL].clk, imx7ulp_clks[FIRC].clk);
6762306a36Sopenharmony_ci	clk_set_parent(imx7ulp_clks[HSRUN_SCS_SEL].clk, imx7ulp_clks[FIRC].clk);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (newfreq > IMX7ULP_MAX_RUN_FREQ)
7062306a36Sopenharmony_ci		clk_set_parent(imx7ulp_clks[ARM].clk,
7162306a36Sopenharmony_ci			       imx7ulp_clks[HSRUN_CORE].clk);
7262306a36Sopenharmony_ci	else
7362306a36Sopenharmony_ci		clk_set_parent(imx7ulp_clks[ARM].clk, imx7ulp_clks[CORE].clk);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	return 0;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic struct cpufreq_dt_platform_data imx7ulp_data = {
7962306a36Sopenharmony_ci	.target_intermediate = imx7ulp_target_intermediate,
8062306a36Sopenharmony_ci	.get_intermediate = imx7ulp_get_intermediate,
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic int imx_cpufreq_dt_probe(struct platform_device *pdev)
8462306a36Sopenharmony_ci{
8562306a36Sopenharmony_ci	struct platform_device *dt_pdev;
8662306a36Sopenharmony_ci	u32 cell_value, supported_hw[2];
8762306a36Sopenharmony_ci	int speed_grade, mkt_segment;
8862306a36Sopenharmony_ci	int ret;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	cpu_dev = get_cpu_device(0);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (!of_property_present(cpu_dev->of_node, "cpu-supply"))
9362306a36Sopenharmony_ci		return -ENODEV;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	if (of_machine_is_compatible("fsl,imx7ulp")) {
9662306a36Sopenharmony_ci		ret = clk_bulk_get(cpu_dev, ARRAY_SIZE(imx7ulp_clks),
9762306a36Sopenharmony_ci				   imx7ulp_clks);
9862306a36Sopenharmony_ci		if (ret)
9962306a36Sopenharmony_ci			return ret;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		dt_pdev = platform_device_register_data(NULL, "cpufreq-dt",
10262306a36Sopenharmony_ci							-1, &imx7ulp_data,
10362306a36Sopenharmony_ci							sizeof(imx7ulp_data));
10462306a36Sopenharmony_ci		if (IS_ERR(dt_pdev)) {
10562306a36Sopenharmony_ci			clk_bulk_put(ARRAY_SIZE(imx7ulp_clks), imx7ulp_clks);
10662306a36Sopenharmony_ci			ret = PTR_ERR(dt_pdev);
10762306a36Sopenharmony_ci			dev_err(&pdev->dev, "Failed to register cpufreq-dt: %d\n", ret);
10862306a36Sopenharmony_ci			return ret;
10962306a36Sopenharmony_ci		}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci		cpufreq_dt_pdev = dt_pdev;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		return 0;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	ret = nvmem_cell_read_u32(cpu_dev, "speed_grade", &cell_value);
11762306a36Sopenharmony_ci	if (ret)
11862306a36Sopenharmony_ci		return ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (of_machine_is_compatible("fsl,imx8mn") ||
12162306a36Sopenharmony_ci	    of_machine_is_compatible("fsl,imx8mp"))
12262306a36Sopenharmony_ci		speed_grade = (cell_value & IMX8MN_OCOTP_CFG3_SPEED_GRADE_MASK)
12362306a36Sopenharmony_ci			      >> OCOTP_CFG3_SPEED_GRADE_SHIFT;
12462306a36Sopenharmony_ci	else
12562306a36Sopenharmony_ci		speed_grade = (cell_value & OCOTP_CFG3_SPEED_GRADE_MASK)
12662306a36Sopenharmony_ci			      >> OCOTP_CFG3_SPEED_GRADE_SHIFT;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	if (of_machine_is_compatible("fsl,imx8mp"))
12962306a36Sopenharmony_ci		mkt_segment = (cell_value & IMX8MP_OCOTP_CFG3_MKT_SEGMENT_MASK)
13062306a36Sopenharmony_ci			       >> IMX8MP_OCOTP_CFG3_MKT_SEGMENT_SHIFT;
13162306a36Sopenharmony_ci	else
13262306a36Sopenharmony_ci		mkt_segment = (cell_value & OCOTP_CFG3_MKT_SEGMENT_MASK)
13362306a36Sopenharmony_ci			       >> OCOTP_CFG3_MKT_SEGMENT_SHIFT;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/*
13662306a36Sopenharmony_ci	 * Early samples without fuses written report "0 0" which may NOT
13762306a36Sopenharmony_ci	 * match any OPP defined in DT. So clamp to minimum OPP defined in
13862306a36Sopenharmony_ci	 * DT to avoid warning for "no OPPs".
13962306a36Sopenharmony_ci	 *
14062306a36Sopenharmony_ci	 * Applies to i.MX8M series SoCs.
14162306a36Sopenharmony_ci	 */
14262306a36Sopenharmony_ci	if (mkt_segment == 0 && speed_grade == 0) {
14362306a36Sopenharmony_ci		if (of_machine_is_compatible("fsl,imx8mm") ||
14462306a36Sopenharmony_ci		    of_machine_is_compatible("fsl,imx8mq"))
14562306a36Sopenharmony_ci			speed_grade = 1;
14662306a36Sopenharmony_ci		if (of_machine_is_compatible("fsl,imx8mn") ||
14762306a36Sopenharmony_ci		    of_machine_is_compatible("fsl,imx8mp"))
14862306a36Sopenharmony_ci			speed_grade = 0xb;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	supported_hw[0] = BIT(speed_grade);
15262306a36Sopenharmony_ci	supported_hw[1] = BIT(mkt_segment);
15362306a36Sopenharmony_ci	dev_info(&pdev->dev, "cpu speed grade %d mkt segment %d supported-hw %#x %#x\n",
15462306a36Sopenharmony_ci			speed_grade, mkt_segment, supported_hw[0], supported_hw[1]);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	cpufreq_opp_token = dev_pm_opp_set_supported_hw(cpu_dev, supported_hw, 2);
15762306a36Sopenharmony_ci	if (cpufreq_opp_token < 0) {
15862306a36Sopenharmony_ci		ret = cpufreq_opp_token;
15962306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to set supported opp: %d\n", ret);
16062306a36Sopenharmony_ci		return ret;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	cpufreq_dt_pdev = platform_device_register_data(
16462306a36Sopenharmony_ci			&pdev->dev, "cpufreq-dt", -1, NULL, 0);
16562306a36Sopenharmony_ci	if (IS_ERR(cpufreq_dt_pdev)) {
16662306a36Sopenharmony_ci		dev_pm_opp_put_supported_hw(cpufreq_opp_token);
16762306a36Sopenharmony_ci		ret = PTR_ERR(cpufreq_dt_pdev);
16862306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register cpufreq-dt: %d\n", ret);
16962306a36Sopenharmony_ci		return ret;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	return 0;
17362306a36Sopenharmony_ci}
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cistatic void imx_cpufreq_dt_remove(struct platform_device *pdev)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	platform_device_unregister(cpufreq_dt_pdev);
17862306a36Sopenharmony_ci	if (!of_machine_is_compatible("fsl,imx7ulp"))
17962306a36Sopenharmony_ci		dev_pm_opp_put_supported_hw(cpufreq_opp_token);
18062306a36Sopenharmony_ci	else
18162306a36Sopenharmony_ci		clk_bulk_put(ARRAY_SIZE(imx7ulp_clks), imx7ulp_clks);
18262306a36Sopenharmony_ci}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic struct platform_driver imx_cpufreq_dt_driver = {
18562306a36Sopenharmony_ci	.probe = imx_cpufreq_dt_probe,
18662306a36Sopenharmony_ci	.remove_new = imx_cpufreq_dt_remove,
18762306a36Sopenharmony_ci	.driver = {
18862306a36Sopenharmony_ci		.name = "imx-cpufreq-dt",
18962306a36Sopenharmony_ci	},
19062306a36Sopenharmony_ci};
19162306a36Sopenharmony_cimodule_platform_driver(imx_cpufreq_dt_driver);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ciMODULE_ALIAS("platform:imx-cpufreq-dt");
19462306a36Sopenharmony_ciMODULE_DESCRIPTION("Freescale i.MX cpufreq speed grading driver");
19562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
196