linux/drivers/cpufreq/imx-cpufreq-dt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright 2019 NXP
   4 */
   5
   6#include <linux/clk.h>
   7#include <linux/cpu.h>
   8#include <linux/cpufreq.h>
   9#include <linux/err.h>
  10#include <linux/init.h>
  11#include <linux/kernel.h>
  12#include <linux/module.h>
  13#include <linux/nvmem-consumer.h>
  14#include <linux/of.h>
  15#include <linux/platform_device.h>
  16#include <linux/pm_opp.h>
  17#include <linux/regulator/consumer.h>
  18#include <linux/slab.h>
  19
  20#include "cpufreq-dt.h"
  21
  22#define OCOTP_CFG3_SPEED_GRADE_SHIFT    8
  23#define OCOTP_CFG3_SPEED_GRADE_MASK     (0x3 << 8)
  24#define IMX8MN_OCOTP_CFG3_SPEED_GRADE_MASK      (0xf << 8)
  25#define OCOTP_CFG3_MKT_SEGMENT_SHIFT    6
  26#define OCOTP_CFG3_MKT_SEGMENT_MASK     (0x3 << 6)
  27#define IMX8MP_OCOTP_CFG3_MKT_SEGMENT_SHIFT    5
  28#define IMX8MP_OCOTP_CFG3_MKT_SEGMENT_MASK     (0x3 << 5)
  29
  30#define IMX7ULP_MAX_RUN_FREQ    528000
  31
  32/* cpufreq-dt device registered by imx-cpufreq-dt */
  33static struct platform_device *cpufreq_dt_pdev;
  34static struct opp_table *cpufreq_opp_table;
  35static struct device *cpu_dev;
  36
  37enum IMX7ULP_CPUFREQ_CLKS {
  38        ARM,
  39        CORE,
  40        SCS_SEL,
  41        HSRUN_CORE,
  42        HSRUN_SCS_SEL,
  43        FIRC,
  44};
  45
  46static struct clk_bulk_data imx7ulp_clks[] = {
  47        { .id = "arm" },
  48        { .id = "core" },
  49        { .id = "scs_sel" },
  50        { .id = "hsrun_core" },
  51        { .id = "hsrun_scs_sel" },
  52        { .id = "firc" },
  53};
  54
  55static unsigned int imx7ulp_get_intermediate(struct cpufreq_policy *policy,
  56                                             unsigned int index)
  57{
  58        return clk_get_rate(imx7ulp_clks[FIRC].clk);
  59}
  60
  61static int imx7ulp_target_intermediate(struct cpufreq_policy *policy,
  62                                        unsigned int index)
  63{
  64        unsigned int newfreq = policy->freq_table[index].frequency;
  65
  66        clk_set_parent(imx7ulp_clks[SCS_SEL].clk, imx7ulp_clks[FIRC].clk);
  67        clk_set_parent(imx7ulp_clks[HSRUN_SCS_SEL].clk, imx7ulp_clks[FIRC].clk);
  68
  69        if (newfreq > IMX7ULP_MAX_RUN_FREQ)
  70                clk_set_parent(imx7ulp_clks[ARM].clk,
  71                               imx7ulp_clks[HSRUN_CORE].clk);
  72        else
  73                clk_set_parent(imx7ulp_clks[ARM].clk, imx7ulp_clks[CORE].clk);
  74
  75        return 0;
  76}
  77
  78static struct cpufreq_dt_platform_data imx7ulp_data = {
  79        .target_intermediate = imx7ulp_target_intermediate,
  80        .get_intermediate = imx7ulp_get_intermediate,
  81};
  82
  83static int imx_cpufreq_dt_probe(struct platform_device *pdev)
  84{
  85        struct platform_device *dt_pdev;
  86        u32 cell_value, supported_hw[2];
  87        int speed_grade, mkt_segment;
  88        int ret;
  89
  90        cpu_dev = get_cpu_device(0);
  91
  92        if (!of_find_property(cpu_dev->of_node, "cpu-supply", NULL))
  93                return -ENODEV;
  94
  95        if (of_machine_is_compatible("fsl,imx7ulp")) {
  96                ret = clk_bulk_get(cpu_dev, ARRAY_SIZE(imx7ulp_clks),
  97                                   imx7ulp_clks);
  98                if (ret)
  99                        return ret;
 100
 101                dt_pdev = platform_device_register_data(NULL, "cpufreq-dt",
 102                                                        -1, &imx7ulp_data,
 103                                                        sizeof(imx7ulp_data));
 104                if (IS_ERR(dt_pdev)) {
 105                        clk_bulk_put(ARRAY_SIZE(imx7ulp_clks), imx7ulp_clks);
 106                        ret = PTR_ERR(dt_pdev);
 107                        dev_err(&pdev->dev, "Failed to register cpufreq-dt: %d\n", ret);
 108                        return ret;
 109                }
 110
 111                cpufreq_dt_pdev = dt_pdev;
 112
 113                return 0;
 114        }
 115
 116        ret = nvmem_cell_read_u32(cpu_dev, "speed_grade", &cell_value);
 117        if (ret)
 118                return ret;
 119
 120        if (of_machine_is_compatible("fsl,imx8mn") ||
 121            of_machine_is_compatible("fsl,imx8mp"))
 122                speed_grade = (cell_value & IMX8MN_OCOTP_CFG3_SPEED_GRADE_MASK)
 123                              >> OCOTP_CFG3_SPEED_GRADE_SHIFT;
 124        else
 125                speed_grade = (cell_value & OCOTP_CFG3_SPEED_GRADE_MASK)
 126                              >> OCOTP_CFG3_SPEED_GRADE_SHIFT;
 127
 128        if (of_machine_is_compatible("fsl,imx8mp"))
 129                mkt_segment = (cell_value & IMX8MP_OCOTP_CFG3_MKT_SEGMENT_MASK)
 130                               >> IMX8MP_OCOTP_CFG3_MKT_SEGMENT_SHIFT;
 131        else
 132                mkt_segment = (cell_value & OCOTP_CFG3_MKT_SEGMENT_MASK)
 133                               >> OCOTP_CFG3_MKT_SEGMENT_SHIFT;
 134
 135        /*
 136         * Early samples without fuses written report "0 0" which may NOT
 137         * match any OPP defined in DT. So clamp to minimum OPP defined in
 138         * DT to avoid warning for "no OPPs".
 139         *
 140         * Applies to i.MX8M series SoCs.
 141         */
 142        if (mkt_segment == 0 && speed_grade == 0) {
 143                if (of_machine_is_compatible("fsl,imx8mm") ||
 144                    of_machine_is_compatible("fsl,imx8mq"))
 145                        speed_grade = 1;
 146                if (of_machine_is_compatible("fsl,imx8mn") ||
 147                    of_machine_is_compatible("fsl,imx8mp"))
 148                        speed_grade = 0xb;
 149        }
 150
 151        supported_hw[0] = BIT(speed_grade);
 152        supported_hw[1] = BIT(mkt_segment);
 153        dev_info(&pdev->dev, "cpu speed grade %d mkt segment %d supported-hw %#x %#x\n",
 154                        speed_grade, mkt_segment, supported_hw[0], supported_hw[1]);
 155
 156        cpufreq_opp_table = dev_pm_opp_set_supported_hw(cpu_dev, supported_hw, 2);
 157        if (IS_ERR(cpufreq_opp_table)) {
 158                ret = PTR_ERR(cpufreq_opp_table);
 159                dev_err(&pdev->dev, "Failed to set supported opp: %d\n", ret);
 160                return ret;
 161        }
 162
 163        cpufreq_dt_pdev = platform_device_register_data(
 164                        &pdev->dev, "cpufreq-dt", -1, NULL, 0);
 165        if (IS_ERR(cpufreq_dt_pdev)) {
 166                dev_pm_opp_put_supported_hw(cpufreq_opp_table);
 167                ret = PTR_ERR(cpufreq_dt_pdev);
 168                dev_err(&pdev->dev, "Failed to register cpufreq-dt: %d\n", ret);
 169                return ret;
 170        }
 171
 172        return 0;
 173}
 174
 175static int imx_cpufreq_dt_remove(struct platform_device *pdev)
 176{
 177        platform_device_unregister(cpufreq_dt_pdev);
 178        if (!of_machine_is_compatible("fsl,imx7ulp"))
 179                dev_pm_opp_put_supported_hw(cpufreq_opp_table);
 180        else
 181                clk_bulk_put(ARRAY_SIZE(imx7ulp_clks), imx7ulp_clks);
 182
 183        return 0;
 184}
 185
 186static struct platform_driver imx_cpufreq_dt_driver = {
 187        .probe = imx_cpufreq_dt_probe,
 188        .remove = imx_cpufreq_dt_remove,
 189        .driver = {
 190                .name = "imx-cpufreq-dt",
 191        },
 192};
 193module_platform_driver(imx_cpufreq_dt_driver);
 194
 195MODULE_ALIAS("platform:imx-cpufreq-dt");
 196MODULE_DESCRIPTION("Freescale i.MX cpufreq speed grading driver");
 197MODULE_LICENSE("GPL v2");
 198