Path: blob/master/drivers/cpufreq/mediatek-cpufreq-hw.c
26278 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 3000002526enum {27REG_FREQ_LUT_TABLE,28REG_FREQ_ENABLE,29REG_FREQ_PERF_STATE,30REG_FREQ_HW_STATE,31REG_EM_POWER_TBL,32REG_FREQ_LATENCY,3334REG_ARRAY_SIZE,35};3637struct mtk_cpufreq_data {38struct cpufreq_frequency_table *table;39void __iomem *reg_bases[REG_ARRAY_SIZE];40struct resource *res;41void __iomem *base;42int nr_opp;43};4445static const u16 cpufreq_mtk_offsets[REG_ARRAY_SIZE] = {46[REG_FREQ_LUT_TABLE] = 0x0,47[REG_FREQ_ENABLE] = 0x84,48[REG_FREQ_PERF_STATE] = 0x88,49[REG_FREQ_HW_STATE] = 0x8c,50[REG_EM_POWER_TBL] = 0x90,51[REG_FREQ_LATENCY] = 0x110,52};5354static int __maybe_unused55mtk_cpufreq_get_cpu_power(struct device *cpu_dev, unsigned long *uW,56unsigned long *KHz)57{58struct mtk_cpufreq_data *data;59struct cpufreq_policy *policy;60int i;6162policy = cpufreq_cpu_get_raw(cpu_dev->id);63if (!policy)64return -EINVAL;6566data = policy->driver_data;6768for (i = 0; i < data->nr_opp; i++) {69if (data->table[i].frequency < *KHz)70break;71}72i--;7374*KHz = data->table[i].frequency;75/* Provide micro-Watts value to the Energy Model */76*uW = readl_relaxed(data->reg_bases[REG_EM_POWER_TBL] +77i * LUT_ROW_SIZE);7879return 0;80}8182static int mtk_cpufreq_hw_target_index(struct cpufreq_policy *policy,83unsigned int index)84{85struct mtk_cpufreq_data *data = policy->driver_data;8687writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]);8889return 0;90}9192static unsigned int mtk_cpufreq_hw_get(unsigned int cpu)93{94struct mtk_cpufreq_data *data;95struct cpufreq_policy *policy;96unsigned int index;9798policy = cpufreq_cpu_get_raw(cpu);99if (!policy)100return 0;101102data = policy->driver_data;103104index = readl_relaxed(data->reg_bases[REG_FREQ_PERF_STATE]);105index = min(index, LUT_MAX_ENTRIES - 1);106107return data->table[index].frequency;108}109110static unsigned int mtk_cpufreq_hw_fast_switch(struct cpufreq_policy *policy,111unsigned int target_freq)112{113struct mtk_cpufreq_data *data = policy->driver_data;114unsigned int index;115116index = cpufreq_table_find_index_dl(policy, target_freq, false);117118writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]);119120return policy->freq_table[index].frequency;121}122123static int mtk_cpu_create_freq_table(struct platform_device *pdev,124struct mtk_cpufreq_data *data)125{126struct device *dev = &pdev->dev;127u32 temp, i, freq, prev_freq = 0;128void __iomem *base_table;129130data->table = devm_kcalloc(dev, LUT_MAX_ENTRIES + 1,131sizeof(*data->table), GFP_KERNEL);132if (!data->table)133return -ENOMEM;134135base_table = data->reg_bases[REG_FREQ_LUT_TABLE];136137for (i = 0; i < LUT_MAX_ENTRIES; i++) {138temp = readl_relaxed(base_table + (i * LUT_ROW_SIZE));139freq = FIELD_GET(LUT_FREQ, temp) * 1000;140141if (freq == prev_freq)142break;143144data->table[i].frequency = freq;145146dev_dbg(dev, "index=%d freq=%d\n", i, data->table[i].frequency);147148prev_freq = freq;149}150151data->table[i].frequency = CPUFREQ_TABLE_END;152data->nr_opp = i;153154return 0;155}156157static int mtk_cpu_resources_init(struct platform_device *pdev,158struct cpufreq_policy *policy,159const u16 *offsets)160{161struct mtk_cpufreq_data *data;162struct device *dev = &pdev->dev;163struct resource *res;164struct of_phandle_args args;165void __iomem *base;166int ret, i;167int index;168169data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);170if (!data)171return -ENOMEM;172173ret = of_perf_domain_get_sharing_cpumask(policy->cpu, "performance-domains",174"#performance-domain-cells",175policy->cpus, &args);176if (ret < 0)177return ret;178179index = args.args[0];180of_node_put(args.np);181182res = platform_get_resource(pdev, IORESOURCE_MEM, index);183if (!res) {184dev_err(dev, "failed to get mem resource %d\n", index);185return -ENODEV;186}187188if (!request_mem_region(res->start, resource_size(res), res->name)) {189dev_err(dev, "failed to request resource %pR\n", res);190return -EBUSY;191}192193base = ioremap(res->start, resource_size(res));194if (!base) {195dev_err(dev, "failed to map resource %pR\n", res);196ret = -ENOMEM;197goto release_region;198}199200data->base = base;201data->res = res;202203for (i = REG_FREQ_LUT_TABLE; i < REG_ARRAY_SIZE; i++)204data->reg_bases[i] = base + offsets[i];205206ret = mtk_cpu_create_freq_table(pdev, data);207if (ret) {208dev_info(dev, "Domain-%d failed to create freq table\n", index);209return ret;210}211212policy->freq_table = data->table;213policy->driver_data = data;214215return 0;216release_region:217release_mem_region(res->start, resource_size(res));218return ret;219}220221static int mtk_cpufreq_hw_cpu_init(struct cpufreq_policy *policy)222{223struct platform_device *pdev = cpufreq_get_driver_data();224int sig, pwr_hw = CPUFREQ_HW_STATUS | SVS_HW_STATUS;225struct mtk_cpufreq_data *data;226unsigned int latency;227int ret;228229/* Get the bases of cpufreq for domains */230ret = mtk_cpu_resources_init(pdev, policy, platform_get_drvdata(pdev));231if (ret) {232dev_info(&pdev->dev, "CPUFreq resource init failed\n");233return ret;234}235236data = policy->driver_data;237238latency = readl_relaxed(data->reg_bases[REG_FREQ_LATENCY]) * 1000;239if (!latency)240latency = CPUFREQ_ETERNAL;241242policy->cpuinfo.transition_latency = latency;243policy->fast_switch_possible = true;244245/* HW should be in enabled state to proceed now */246writel_relaxed(0x1, data->reg_bases[REG_FREQ_ENABLE]);247if (readl_poll_timeout(data->reg_bases[REG_FREQ_HW_STATE], sig,248(sig & pwr_hw) == pwr_hw, POLL_USEC,249TIMEOUT_USEC)) {250if (!(sig & CPUFREQ_HW_STATUS)) {251pr_info("cpufreq hardware of CPU%d is not enabled\n",252policy->cpu);253return -ENODEV;254}255256pr_info("SVS of CPU%d is not enabled\n", policy->cpu);257}258259return 0;260}261262static void mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy)263{264struct mtk_cpufreq_data *data = policy->driver_data;265struct resource *res = data->res;266void __iomem *base = data->base;267268/* HW should be in paused state now */269writel_relaxed(0x0, data->reg_bases[REG_FREQ_ENABLE]);270iounmap(base);271release_mem_region(res->start, resource_size(res));272}273274static void mtk_cpufreq_register_em(struct cpufreq_policy *policy)275{276struct em_data_callback em_cb = EM_DATA_CB(mtk_cpufreq_get_cpu_power);277struct mtk_cpufreq_data *data = policy->driver_data;278279em_dev_register_perf_domain(get_cpu_device(policy->cpu), data->nr_opp,280&em_cb, policy->cpus, true);281}282283static struct cpufreq_driver cpufreq_mtk_hw_driver = {284.flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK |285CPUFREQ_HAVE_GOVERNOR_PER_POLICY |286CPUFREQ_IS_COOLING_DEV,287.verify = cpufreq_generic_frequency_table_verify,288.target_index = mtk_cpufreq_hw_target_index,289.get = mtk_cpufreq_hw_get,290.init = mtk_cpufreq_hw_cpu_init,291.exit = mtk_cpufreq_hw_cpu_exit,292.register_em = mtk_cpufreq_register_em,293.fast_switch = mtk_cpufreq_hw_fast_switch,294.name = "mtk-cpufreq-hw",295};296297static int mtk_cpufreq_hw_driver_probe(struct platform_device *pdev)298{299const void *data;300int ret, cpu;301struct device *cpu_dev;302struct regulator *cpu_reg;303304/* Make sure that all CPU supplies are available before proceeding. */305for_each_present_cpu(cpu) {306cpu_dev = get_cpu_device(cpu);307if (!cpu_dev)308return dev_err_probe(&pdev->dev, -EPROBE_DEFER,309"Failed to get cpu%d device\n", cpu);310311cpu_reg = devm_regulator_get(cpu_dev, "cpu");312if (IS_ERR(cpu_reg))313return dev_err_probe(&pdev->dev, PTR_ERR(cpu_reg),314"CPU%d regulator get failed\n", cpu);315}316317318data = of_device_get_match_data(&pdev->dev);319if (!data)320return -EINVAL;321322platform_set_drvdata(pdev, (void *) data);323cpufreq_mtk_hw_driver.driver_data = pdev;324325ret = cpufreq_register_driver(&cpufreq_mtk_hw_driver);326if (ret)327dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n");328329return ret;330}331332static void mtk_cpufreq_hw_driver_remove(struct platform_device *pdev)333{334cpufreq_unregister_driver(&cpufreq_mtk_hw_driver);335}336337static const struct of_device_id mtk_cpufreq_hw_match[] = {338{ .compatible = "mediatek,cpufreq-hw", .data = &cpufreq_mtk_offsets },339{}340};341MODULE_DEVICE_TABLE(of, mtk_cpufreq_hw_match);342343static struct platform_driver mtk_cpufreq_hw_driver = {344.probe = mtk_cpufreq_hw_driver_probe,345.remove = mtk_cpufreq_hw_driver_remove,346.driver = {347.name = "mtk-cpufreq-hw",348.of_match_table = mtk_cpufreq_hw_match,349},350};351module_platform_driver(mtk_cpufreq_hw_driver);352353MODULE_AUTHOR("Hector Yuan <[email protected]>");354MODULE_DESCRIPTION("Mediatek cpufreq-hw driver");355MODULE_LICENSE("GPL v2");356357358