Path: blob/master/drivers/cpufreq/mediatek-cpufreq-hw.c
29266 views
// SPDX-License-Identifier: GPL-2.01/*2* Copyright (c) 2020 MediaTek Inc.3*/45#include <linux/bitfield.h>6#include <linux/cpufreq.h>7#include <linux/energy_model.h>8#include <linux/init.h>9#include <linux/iopoll.h>10#include <linux/kernel.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/of_platform.h>14#include <linux/platform_device.h>15#include <linux/regulator/consumer.h>16#include <linux/slab.h>1718#define LUT_MAX_ENTRIES 32U19#define LUT_FREQ GENMASK(11, 0)20#define LUT_ROW_SIZE 0x421#define CPUFREQ_HW_STATUS BIT(0)22#define SVS_HW_STATUS BIT(1)23#define POLL_USEC 100024#define TIMEOUT_USEC 3000002526#define FDVFS_FDIV_HZ (26 * 1000)2728enum {29REG_FREQ_LUT_TABLE,30REG_FREQ_ENABLE,31REG_FREQ_PERF_STATE,32REG_FREQ_HW_STATE,33REG_EM_POWER_TBL,34REG_FREQ_LATENCY,3536REG_ARRAY_SIZE,37};3839struct mtk_cpufreq_priv {40struct device *dev;41const struct mtk_cpufreq_variant *variant;42void __iomem *fdvfs;43};4445struct mtk_cpufreq_domain {46struct mtk_cpufreq_priv *parent;47struct cpufreq_frequency_table *table;48void __iomem *reg_bases[REG_ARRAY_SIZE];49struct resource *res;50void __iomem *base;51int nr_opp;52};5354struct mtk_cpufreq_variant {55int (*init)(struct mtk_cpufreq_priv *priv);56const u16 reg_offsets[REG_ARRAY_SIZE];57const bool is_hybrid_dvfs;58};5960static const struct mtk_cpufreq_variant cpufreq_mtk_base_variant = {61.reg_offsets = {62[REG_FREQ_LUT_TABLE] = 0x0,63[REG_FREQ_ENABLE] = 0x84,64[REG_FREQ_PERF_STATE] = 0x88,65[REG_FREQ_HW_STATE] = 0x8c,66[REG_EM_POWER_TBL] = 0x90,67[REG_FREQ_LATENCY] = 0x110,68},69};7071static int mtk_cpufreq_hw_mt8196_init(struct mtk_cpufreq_priv *priv)72{73priv->fdvfs = devm_of_iomap(priv->dev, priv->dev->of_node, 0, NULL);74if (IS_ERR(priv->fdvfs))75return dev_err_probe(priv->dev, PTR_ERR(priv->fdvfs),76"failed to get fdvfs iomem\n");7778return 0;79}8081static const struct mtk_cpufreq_variant cpufreq_mtk_mt8196_variant = {82.init = mtk_cpufreq_hw_mt8196_init,83.reg_offsets = {84[REG_FREQ_LUT_TABLE] = 0x0,85[REG_FREQ_ENABLE] = 0x84,86[REG_FREQ_PERF_STATE] = 0x88,87[REG_FREQ_HW_STATE] = 0x8c,88[REG_EM_POWER_TBL] = 0x90,89[REG_FREQ_LATENCY] = 0x114,90},91.is_hybrid_dvfs = true,92};9394static int __maybe_unused95mtk_cpufreq_get_cpu_power(struct device *cpu_dev, unsigned long *uW,96unsigned long *KHz)97{98struct mtk_cpufreq_domain *data;99struct cpufreq_policy *policy;100int i;101102policy = cpufreq_cpu_get_raw(cpu_dev->id);103if (!policy)104return -EINVAL;105106data = policy->driver_data;107108for (i = 0; i < data->nr_opp; i++) {109if (data->table[i].frequency < *KHz)110break;111}112i--;113114*KHz = data->table[i].frequency;115/* Provide micro-Watts value to the Energy Model */116*uW = readl_relaxed(data->reg_bases[REG_EM_POWER_TBL] +117i * LUT_ROW_SIZE);118119return 0;120}121122static void mtk_cpufreq_hw_fdvfs_switch(unsigned int target_freq,123struct cpufreq_policy *policy)124{125struct mtk_cpufreq_domain *data = policy->driver_data;126struct mtk_cpufreq_priv *priv = data->parent;127unsigned int cpu;128129target_freq = DIV_ROUND_UP(target_freq, FDVFS_FDIV_HZ);130for_each_cpu(cpu, policy->real_cpus) {131writel_relaxed(target_freq, priv->fdvfs + cpu * 4);132}133}134135static int mtk_cpufreq_hw_target_index(struct cpufreq_policy *policy,136unsigned int index)137{138struct mtk_cpufreq_domain *data = policy->driver_data;139unsigned int target_freq;140141if (data->parent->fdvfs) {142target_freq = policy->freq_table[index].frequency;143mtk_cpufreq_hw_fdvfs_switch(target_freq, policy);144} else {145writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]);146}147148return 0;149}150151static unsigned int mtk_cpufreq_hw_get(unsigned int cpu)152{153struct mtk_cpufreq_domain *data;154struct cpufreq_policy *policy;155unsigned int index;156157policy = cpufreq_cpu_get_raw(cpu);158if (!policy)159return 0;160161data = policy->driver_data;162163index = readl_relaxed(data->reg_bases[REG_FREQ_PERF_STATE]);164index = min(index, LUT_MAX_ENTRIES - 1);165166return data->table[index].frequency;167}168169static unsigned int mtk_cpufreq_hw_fast_switch(struct cpufreq_policy *policy,170unsigned int target_freq)171{172struct mtk_cpufreq_domain *data = policy->driver_data;173unsigned int index;174175index = cpufreq_table_find_index_dl(policy, target_freq, false);176177if (data->parent->fdvfs)178mtk_cpufreq_hw_fdvfs_switch(target_freq, policy);179else180writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]);181182return policy->freq_table[index].frequency;183}184185static int mtk_cpu_create_freq_table(struct platform_device *pdev,186struct mtk_cpufreq_domain *data)187{188struct device *dev = &pdev->dev;189u32 temp, i, freq, prev_freq = 0;190void __iomem *base_table;191192data->table = devm_kcalloc(dev, LUT_MAX_ENTRIES + 1,193sizeof(*data->table), GFP_KERNEL);194if (!data->table)195return -ENOMEM;196197base_table = data->reg_bases[REG_FREQ_LUT_TABLE];198199for (i = 0; i < LUT_MAX_ENTRIES; i++) {200temp = readl_relaxed(base_table + (i * LUT_ROW_SIZE));201freq = FIELD_GET(LUT_FREQ, temp) * 1000;202203if (freq == prev_freq)204break;205206data->table[i].frequency = freq;207208dev_dbg(dev, "index=%d freq=%d\n", i, data->table[i].frequency);209210prev_freq = freq;211}212213data->table[i].frequency = CPUFREQ_TABLE_END;214data->nr_opp = i;215216return 0;217}218219static int mtk_cpu_resources_init(struct platform_device *pdev,220struct cpufreq_policy *policy,221struct mtk_cpufreq_priv *priv)222{223struct mtk_cpufreq_domain *data;224struct device *dev = &pdev->dev;225struct resource *res;226struct of_phandle_args args;227void __iomem *base;228int ret, i;229int index;230231data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);232if (!data)233return -ENOMEM;234235ret = of_perf_domain_get_sharing_cpumask(policy->cpu, "performance-domains",236"#performance-domain-cells",237policy->cpus, &args);238if (ret < 0)239return ret;240241index = args.args[0];242of_node_put(args.np);243244/*245* In a cpufreq with hybrid DVFS, such as the MT8196, the first declared246* register range is for FDVFS, followed by the frequency domain MMIOs.247*/248if (priv->variant->is_hybrid_dvfs)249index++;250251data->parent = priv;252253res = platform_get_resource(pdev, IORESOURCE_MEM, index);254if (!res) {255dev_err(dev, "failed to get mem resource %d\n", index);256return -ENODEV;257}258259if (!request_mem_region(res->start, resource_size(res), res->name)) {260dev_err(dev, "failed to request resource %pR\n", res);261return -EBUSY;262}263264base = ioremap(res->start, resource_size(res));265if (!base) {266dev_err(dev, "failed to map resource %pR\n", res);267ret = -ENOMEM;268goto release_region;269}270271data->base = base;272data->res = res;273274for (i = REG_FREQ_LUT_TABLE; i < REG_ARRAY_SIZE; i++)275data->reg_bases[i] = base + priv->variant->reg_offsets[i];276277ret = mtk_cpu_create_freq_table(pdev, data);278if (ret) {279dev_info(dev, "Domain-%d failed to create freq table\n", index);280return ret;281}282283policy->freq_table = data->table;284policy->driver_data = data;285286return 0;287release_region:288release_mem_region(res->start, resource_size(res));289return ret;290}291292static int mtk_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)293{294struct platform_device *pdev = cpufreq_get_driver_data();295int sig, pwr_hw = CPUFREQ_HW_STATUS | SVS_HW_STATUS;296struct mtk_cpufreq_domain *data;297unsigned int latency;298int ret;299300/* Get the bases of cpufreq for domains */301ret = mtk_cpu_resources_init(pdev, policy, platform_get_drvdata(pdev));302if (ret) {303dev_info(&pdev->dev, "CPUFreq resource init failed\n");304return ret;305}306307data = policy->driver_data;308309latency = readl_relaxed(data->reg_bases[REG_FREQ_LATENCY]) * 1000;310if (!latency)311latency = CPUFREQ_ETERNAL;312313policy->cpuinfo.transition_latency = latency;314policy->fast_switch_possible = true;315316/* HW should be in enabled state to proceed now */317writel_relaxed(0x1, data->reg_bases[REG_FREQ_ENABLE]);318if (readl_poll_timeout(data->reg_bases[REG_FREQ_HW_STATE], sig,319(sig & pwr_hw) == pwr_hw, POLL_USEC,320TIMEOUT_USEC)) {321if (!(sig & CPUFREQ_HW_STATUS)) {322pr_info("cpufreq hardware of CPU%d is not enabled\n",323policy->cpu);324return -ENODEV;325}326327pr_info("SVS of CPU%d is not enabled\n", policy->cpu);328}329330return 0;331}332333static void mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)334{335struct mtk_cpufreq_domain *data = policy->driver_data;336struct resource *res = data->res;337void __iomem *base = data->base;338339/* HW should be in paused state now */340writel_relaxed(0x0, data->reg_bases[REG_FREQ_ENABLE]);341iounmap(base);342release_mem_region(res->start, resource_size(res));343}344345static void mtk_cpufreq_register_em(struct cpufreq_policy *policy)346{347struct em_data_callback em_cb = EM_DATA_CB(mtk_cpufreq_get_cpu_power);348struct mtk_cpufreq_domain *data = policy->driver_data;349350em_dev_register_perf_domain(get_cpu_device(policy->cpu), data->nr_opp,351&em_cb, policy->cpus, true);352}353354static struct cpufreq_driver cpufreq_mtk_hw_driver = {355.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |356CPUFREQ_HAVE_GOVERNOR_PER_POLICY |357CPUFREQ_IS_COOLING_DEV,358.verify = cpufreq_generic_frequency_table_verify,359.target_index = mtk_cpufreq_hw_target_index,360.get = mtk_cpufreq_hw_get,361.init = mtk_cpufreq_hw_cpu_init,362.exit = mtk_cpufreq_hw_cpu_exit,363.register_em = mtk_cpufreq_register_em,364.fast_switch = mtk_cpufreq_hw_fast_switch,365.name = "mtk-cpufreq-hw",366};367368static int mtk_cpufreq_hw_driver_probe(struct platform_device *pdev)369{370struct mtk_cpufreq_priv *priv;371const void *data;372int ret, cpu;373struct device *cpu_dev;374struct regulator *cpu_reg;375376/* Make sure that all CPU supplies are available before proceeding. */377for_each_present_cpu(cpu) {378cpu_dev = get_cpu_device(cpu);379if (!cpu_dev)380return dev_err_probe(&pdev->dev, -EPROBE_DEFER,381"Failed to get cpu%d device\n", cpu);382383cpu_reg = devm_regulator_get(cpu_dev, "cpu");384if (IS_ERR(cpu_reg))385return dev_err_probe(&pdev->dev, PTR_ERR(cpu_reg),386"CPU%d regulator get failed\n", cpu);387}388389390data = of_device_get_match_data(&pdev->dev);391if (!data)392return -EINVAL;393394priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);395if (!priv)396return -ENOMEM;397398priv->variant = data;399priv->dev = &pdev->dev;400401if (priv->variant->init) {402ret = priv->variant->init(priv);403if (ret)404return ret;405}406407platform_set_drvdata(pdev, priv);408cpufreq_mtk_hw_driver.driver_data = pdev;409410ret = cpufreq_register_driver(&cpufreq_mtk_hw_driver);411if (ret)412dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n");413414return ret;415}416417static void mtk_cpufreq_hw_driver_remove(struct platform_device *pdev)418{419cpufreq_unregister_driver(&cpufreq_mtk_hw_driver);420}421422static const struct of_device_id mtk_cpufreq_hw_match[] = {423{ .compatible = "mediatek,cpufreq-hw", .data = &cpufreq_mtk_base_variant },424{ .compatible = "mediatek,mt8196-cpufreq-hw", .data = &cpufreq_mtk_mt8196_variant },425{}426};427MODULE_DEVICE_TABLE(of, mtk_cpufreq_hw_match);428429static struct platform_driver mtk_cpufreq_hw_driver = {430.probe = mtk_cpufreq_hw_driver_probe,431.remove = mtk_cpufreq_hw_driver_remove,432.driver = {433.name = "mtk-cpufreq-hw",434.of_match_table = mtk_cpufreq_hw_match,435},436};437module_platform_driver(mtk_cpufreq_hw_driver);438439MODULE_AUTHOR("Hector Yuan <[email protected]>");440MODULE_DESCRIPTION("Mediatek cpufreq-hw driver");441MODULE_LICENSE("GPL v2");442443444