linux/drivers/cpufreq/sti-cpufreq.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Match running platform with pre-defined OPP values for CPUFreq
   4 *
   5 * Author: Ajit Pal Singh <ajitpal.singh@st.com>
   6 *         Lee Jones <lee.jones@linaro.org>
   7 *
   8 * Copyright (C) 2015 STMicroelectronics (R&D) Limited
   9 */
  10
  11#include <linux/cpu.h>
  12#include <linux/io.h>
  13#include <linux/mfd/syscon.h>
  14#include <linux/module.h>
  15#include <linux/of.h>
  16#include <linux/of_platform.h>
  17#include <linux/pm_opp.h>
  18#include <linux/regmap.h>
  19
  20#define VERSION_ELEMENTS        3
  21#define MAX_PCODE_NAME_LEN      7
  22
  23#define VERSION_SHIFT           28
  24#define HW_INFO_INDEX           1
  25#define MAJOR_ID_INDEX          1
  26#define MINOR_ID_INDEX          2
  27
  28/*
  29 * Only match on "suitable for ALL versions" entries
  30 *
  31 * This will be used with the BIT() macro.  It sets the
  32 * top bit of a 32bit value and is equal to 0x80000000.
  33 */
  34#define DEFAULT_VERSION         31
  35
  36enum {
  37        PCODE = 0,
  38        SUBSTRATE,
  39        DVFS_MAX_REGFIELDS,
  40};
  41
  42/**
  43 * struct sti_cpufreq_ddata - ST CPUFreq Driver Data
  44 *
  45 * @cpu:                CPU's OF node
  46 * @syscfg_eng:         Engineering Syscon register map
  47 * @syscfg:             Syscon register map
  48 */
  49static struct sti_cpufreq_ddata {
  50        struct device *cpu;
  51        struct regmap *syscfg_eng;
  52        struct regmap *syscfg;
  53} ddata;
  54
  55static int sti_cpufreq_fetch_major(void) {
  56        struct device_node *np = ddata.cpu->of_node;
  57        struct device *dev = ddata.cpu;
  58        unsigned int major_offset;
  59        unsigned int socid;
  60        int ret;
  61
  62        ret = of_property_read_u32_index(np, "st,syscfg",
  63                                         MAJOR_ID_INDEX, &major_offset);
  64        if (ret) {
  65                dev_err(dev, "No major number offset provided in %pOF [%d]\n",
  66                        np, ret);
  67                return ret;
  68        }
  69
  70        ret = regmap_read(ddata.syscfg, major_offset, &socid);
  71        if (ret) {
  72                dev_err(dev, "Failed to read major number from syscon [%d]\n",
  73                        ret);
  74                return ret;
  75        }
  76
  77        return ((socid >> VERSION_SHIFT) & 0xf) + 1;
  78}
  79
  80static int sti_cpufreq_fetch_minor(void)
  81{
  82        struct device *dev = ddata.cpu;
  83        struct device_node *np = dev->of_node;
  84        unsigned int minor_offset;
  85        unsigned int minid;
  86        int ret;
  87
  88        ret = of_property_read_u32_index(np, "st,syscfg-eng",
  89                                         MINOR_ID_INDEX, &minor_offset);
  90        if (ret) {
  91                dev_err(dev,
  92                        "No minor number offset provided %pOF [%d]\n",
  93                        np, ret);
  94                return ret;
  95        }
  96
  97        ret = regmap_read(ddata.syscfg_eng, minor_offset, &minid);
  98        if (ret) {
  99                dev_err(dev,
 100                        "Failed to read the minor number from syscon [%d]\n",
 101                        ret);
 102                return ret;
 103        }
 104
 105        return minid & 0xf;
 106}
 107
 108static int sti_cpufreq_fetch_regmap_field(const struct reg_field *reg_fields,
 109                                          int hw_info_offset, int field)
 110{
 111        struct regmap_field *regmap_field;
 112        struct reg_field reg_field = reg_fields[field];
 113        struct device *dev = ddata.cpu;
 114        unsigned int value;
 115        int ret;
 116
 117        reg_field.reg = hw_info_offset;
 118        regmap_field = devm_regmap_field_alloc(dev,
 119                                               ddata.syscfg_eng,
 120                                               reg_field);
 121        if (IS_ERR(regmap_field)) {
 122                dev_err(dev, "Failed to allocate reg field\n");
 123                return PTR_ERR(regmap_field);
 124        }
 125
 126        ret = regmap_field_read(regmap_field, &value);
 127        if (ret) {
 128                dev_err(dev, "Failed to read %s code\n",
 129                        field ? "SUBSTRATE" : "PCODE");
 130                return ret;
 131        }
 132
 133        return value;
 134}
 135
 136static const struct reg_field sti_stih407_dvfs_regfields[DVFS_MAX_REGFIELDS] = {
 137        [PCODE]         = REG_FIELD(0, 16, 19),
 138        [SUBSTRATE]     = REG_FIELD(0, 0, 2),
 139};
 140
 141static const struct reg_field *sti_cpufreq_match(void)
 142{
 143        if (of_machine_is_compatible("st,stih407") ||
 144            of_machine_is_compatible("st,stih410") ||
 145            of_machine_is_compatible("st,stih418"))
 146                return sti_stih407_dvfs_regfields;
 147
 148        return NULL;
 149}
 150
 151static int sti_cpufreq_set_opp_info(void)
 152{
 153        struct device *dev = ddata.cpu;
 154        struct device_node *np = dev->of_node;
 155        const struct reg_field *reg_fields;
 156        unsigned int hw_info_offset;
 157        unsigned int version[VERSION_ELEMENTS];
 158        int pcode, substrate, major, minor;
 159        int ret;
 160        char name[MAX_PCODE_NAME_LEN];
 161        struct opp_table *opp_table;
 162
 163        reg_fields = sti_cpufreq_match();
 164        if (!reg_fields) {
 165                dev_err(dev, "This SoC doesn't support voltage scaling\n");
 166                return -ENODEV;
 167        }
 168
 169        ret = of_property_read_u32_index(np, "st,syscfg-eng",
 170                                         HW_INFO_INDEX, &hw_info_offset);
 171        if (ret) {
 172                dev_warn(dev, "Failed to read HW info offset from DT\n");
 173                substrate = DEFAULT_VERSION;
 174                pcode = 0;
 175                goto use_defaults;
 176        }
 177
 178        pcode = sti_cpufreq_fetch_regmap_field(reg_fields,
 179                                               hw_info_offset,
 180                                               PCODE);
 181        if (pcode < 0) {
 182                dev_warn(dev, "Failed to obtain process code\n");
 183                /* Use default pcode */
 184                pcode = 0;
 185        }
 186
 187        substrate = sti_cpufreq_fetch_regmap_field(reg_fields,
 188                                                   hw_info_offset,
 189                                                   SUBSTRATE);
 190        if (substrate) {
 191                dev_warn(dev, "Failed to obtain substrate code\n");
 192                /* Use default substrate */
 193                substrate = DEFAULT_VERSION;
 194        }
 195
 196use_defaults:
 197        major = sti_cpufreq_fetch_major();
 198        if (major < 0) {
 199                dev_err(dev, "Failed to obtain major version\n");
 200                /* Use default major number */
 201                major = DEFAULT_VERSION;
 202        }
 203
 204        minor = sti_cpufreq_fetch_minor();
 205        if (minor < 0) {
 206                dev_err(dev, "Failed to obtain minor version\n");
 207                /* Use default minor number */
 208                minor = DEFAULT_VERSION;
 209        }
 210
 211        snprintf(name, MAX_PCODE_NAME_LEN, "pcode%d", pcode);
 212
 213        opp_table = dev_pm_opp_set_prop_name(dev, name);
 214        if (IS_ERR(opp_table)) {
 215                dev_err(dev, "Failed to set prop name\n");
 216                return PTR_ERR(opp_table);
 217        }
 218
 219        version[0] = BIT(major);
 220        version[1] = BIT(minor);
 221        version[2] = BIT(substrate);
 222
 223        opp_table = dev_pm_opp_set_supported_hw(dev, version, VERSION_ELEMENTS);
 224        if (IS_ERR(opp_table)) {
 225                dev_err(dev, "Failed to set supported hardware\n");
 226                ret = PTR_ERR(opp_table);
 227                goto err_put_prop_name;
 228        }
 229
 230        dev_dbg(dev, "pcode: %d major: %d minor: %d substrate: %d\n",
 231                pcode, major, minor, substrate);
 232        dev_dbg(dev, "version[0]: %x version[1]: %x version[2]: %x\n",
 233                version[0], version[1], version[2]);
 234
 235        return 0;
 236
 237err_put_prop_name:
 238        dev_pm_opp_put_prop_name(opp_table);
 239        return ret;
 240}
 241
 242static int sti_cpufreq_fetch_syscon_registers(void)
 243{
 244        struct device *dev = ddata.cpu;
 245        struct device_node *np = dev->of_node;
 246
 247        ddata.syscfg = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
 248        if (IS_ERR(ddata.syscfg)) {
 249                dev_err(dev,  "\"st,syscfg\" not supplied\n");
 250                return PTR_ERR(ddata.syscfg);
 251        }
 252
 253        ddata.syscfg_eng = syscon_regmap_lookup_by_phandle(np, "st,syscfg-eng");
 254        if (IS_ERR(ddata.syscfg_eng)) {
 255                dev_err(dev, "\"st,syscfg-eng\" not supplied\n");
 256                return PTR_ERR(ddata.syscfg_eng);
 257        }
 258
 259        return 0;
 260}
 261
 262static int sti_cpufreq_init(void)
 263{
 264        int ret;
 265
 266        if ((!of_machine_is_compatible("st,stih407")) &&
 267                (!of_machine_is_compatible("st,stih410")) &&
 268                (!of_machine_is_compatible("st,stih418")))
 269                return -ENODEV;
 270
 271        ddata.cpu = get_cpu_device(0);
 272        if (!ddata.cpu) {
 273                dev_err(ddata.cpu, "Failed to get device for CPU0\n");
 274                goto skip_voltage_scaling;
 275        }
 276
 277        if (!of_get_property(ddata.cpu->of_node, "operating-points-v2", NULL)) {
 278                dev_err(ddata.cpu, "OPP-v2 not supported\n");
 279                goto skip_voltage_scaling;
 280        }
 281
 282        ret = sti_cpufreq_fetch_syscon_registers();
 283        if (ret)
 284                goto skip_voltage_scaling;
 285
 286        ret = sti_cpufreq_set_opp_info();
 287        if (!ret)
 288                goto register_cpufreq_dt;
 289
 290skip_voltage_scaling:
 291        dev_err(ddata.cpu, "Not doing voltage scaling\n");
 292
 293register_cpufreq_dt:
 294        platform_device_register_simple("cpufreq-dt", -1, NULL, 0);
 295
 296        return 0;
 297}
 298module_init(sti_cpufreq_init);
 299
 300static const struct of_device_id __maybe_unused sti_cpufreq_of_match[] = {
 301        { .compatible = "st,stih407" },
 302        { .compatible = "st,stih410" },
 303        { },
 304};
 305MODULE_DEVICE_TABLE(of, sti_cpufreq_of_match);
 306
 307MODULE_DESCRIPTION("STMicroelectronics CPUFreq/OPP driver");
 308MODULE_AUTHOR("Ajitpal Singh <ajitpal.singh@st.com>");
 309MODULE_AUTHOR("Lee Jones <lee.jones@linaro.org>");
 310MODULE_LICENSE("GPL v2");
 311