/* * Copyright 2017 NXP * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_CLUSTER_NUM 2 static struct delayed_work cpufreq_governor_daemon; static DEFINE_SPINLOCK(cpufreq_psci_lock); struct imx8_cpufreq { struct clk *cpu_clk; }; struct imx8_cpufreq cluster_freq[MAX_CLUSTER_NUM]; static struct cpufreq_frequency_table *freq_table[MAX_CLUSTER_NUM]; static unsigned int transition_latency[MAX_CLUSTER_NUM]; struct device *cpu_dev; static struct thermal_cooling_device *cdev[2]; static void cpufreq_governor_daemon_handler(struct work_struct *work) { int fd, i; unsigned char cluster_governor[MAX_CLUSTER_NUM][54] = { "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "", }; /* generate second cluster's cpufreq governor path */ sprintf(cluster_governor[MAX_CLUSTER_NUM - 1], "%s%d%s", "/sys/devices/system/cpu/cpu", num_online_cpus() - 1, "/cpufreq/scaling_governor"); for (i = 0; i < MAX_CLUSTER_NUM; i++) { fd = sys_open((const char __user __force *)cluster_governor[i], O_RDWR, 0700); if (fd >= 0) { sys_write(fd, "schedutil", strlen("schedutil")); sys_close(fd); pr_info("switch cluster %d cpu-freq governor to schedutil\n", i); } else { /* re-schedule if sys write is NOT ready */ schedule_delayed_work(&cpufreq_governor_daemon, msecs_to_jiffies(3000)); break; } } } static int imx8_set_target(struct cpufreq_policy *policy, unsigned int index) { struct arm_smccc_res res; unsigned int old_freq, new_freq; unsigned int cluster_id = topology_physical_package_id(policy->cpu); new_freq = freq_table[cluster_id][index].frequency; old_freq = policy->cur; dev_dbg(cpu_dev, "%u MHz --> %u MHz\n", old_freq / 1000, new_freq / 1000); spin_lock(&cpufreq_psci_lock); arm_smccc_smc(FSL_SIP_CPUFREQ, FSL_SIP_SET_CPUFREQ, cluster_id, new_freq * 1000, 0, 0, 0, 0, &res); spin_unlock(&cpufreq_psci_lock); /* * As we can only set CPU clock rate in ATF, clock * framework does NOT know CPU clock rate is changed, * so here do clk_get_rate once to update CPU clock * rate, otherwise cat /sys/kernel/debug/clk/xxx/clk_rate * will return incorrect rate as it does NOT do a * recalculation. */ clk_get_rate(cluster_freq[cluster_id].cpu_clk); return 0; } static int imx8_cpufreq_init(struct cpufreq_policy *policy) { int cluster_id = topology_physical_package_id(policy->cpu); int ret = 0; policy->clk = cluster_freq[cluster_id].cpu_clk; policy->cur = clk_get_rate(cluster_freq[cluster_id].cpu_clk) / 1000; /* * The driver only supports the SMP configuartion where all processors * share the clock and voltage and clock. */ cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); ret = cpufreq_table_validate_and_show(policy, freq_table[cluster_id]); if (ret) { pr_err("%s: invalid frequency table: %d\n", __func__, ret); return ret; } policy->cpuinfo.transition_latency = transition_latency[cluster_id]; policy->suspend_freq = policy->max; pr_info("%s: cluster %d running at freq %d MHz, suspend freq %d MHz\n", __func__, cluster_id, policy->cur / 1000, policy->suspend_freq / 1000); return ret; } static void imx8_cpufreq_ready(struct cpufreq_policy *policy) { struct device_node *np = of_get_cpu_node(policy->cpu, NULL); unsigned int cluster_id = topology_physical_package_id(policy->cpu); if (of_find_property(np, "#cooling-cells", NULL)) { cdev[cluster_id] = of_cpufreq_cooling_register(np, policy); if (IS_ERR(cdev[cluster_id]) && PTR_ERR(cdev[cluster_id]) != -ENOSYS) { pr_err("cpu%d is not running as cooling device: %ld\n", policy->cpu, PTR_ERR(cdev[cluster_id])); cdev[cluster_id] = NULL; } } of_node_put(np); } static struct cpufreq_driver imx8_cpufreq_driver = { .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK, .verify = cpufreq_generic_frequency_table_verify, .target_index = imx8_set_target, .get = cpufreq_generic_get, .init = imx8_cpufreq_init, .name = "imx8-cpufreq", .attr = cpufreq_generic_attr, .ready = imx8_cpufreq_ready, #ifdef CONFIG_PM .suspend = cpufreq_generic_suspend, #endif }; static int imx8_cpufreq_probe(struct platform_device *pdev) { struct device_node *np; int ret = 0; int i, cluster_id; struct device *first_cpu_dev = NULL; cpu_dev = get_cpu_device(0); if (!cpu_dev) { pr_err("failed to get cpu device 0\n"); return -ENODEV; } np = of_node_get(cpu_dev->of_node); if (!np) { pr_warn("failed to find cpu 0 node\n"); return -ENODEV; } ret = dev_pm_opp_of_add_table(cpu_dev); if (ret < 0) { dev_err(cpu_dev, "failed to init OPP table: %d\n", ret); goto put_node; } cluster_id = topology_physical_package_id(0); cluster_freq[cluster_id].cpu_clk = devm_clk_get(cpu_dev, NULL); if (IS_ERR(cluster_freq[cluster_id].cpu_clk)) { dev_err(cpu_dev, "failed to get cluster %d clock\n", cluster_id); ret = -ENOENT; goto put_node; } ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster_id]); if (ret) { dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); goto out_free_opp; } if (of_property_read_u32(np, "clock-latency", &transition_latency[cluster_id])) transition_latency[cluster_id] = CPUFREQ_ETERNAL; /* init next cluster if there is */ for (i = 1; i < num_online_cpus(); i++) { if (topology_physical_package_id(i) == topology_physical_package_id(0)) continue; INIT_DELAYED_WORK(&cpufreq_governor_daemon, cpufreq_governor_daemon_handler); schedule_delayed_work(&cpufreq_governor_daemon, msecs_to_jiffies(3000)); first_cpu_dev = cpu_dev; cpu_dev = get_cpu_device(i); if (!cpu_dev) { pr_err("failed to get cpu device %d\n", i); return -ENODEV; } np = of_node_get(cpu_dev->of_node); if (!np) { pr_warn("failed to find cpu %d node\n", i); ret = -ENODEV; goto put_node; } cluster_id = topology_physical_package_id(i); cluster_freq[cluster_id].cpu_clk = devm_clk_get(cpu_dev, NULL); if (IS_ERR(cluster_freq[cluster_id].cpu_clk)) { dev_err(cpu_dev, "failed to get cluster %d clock\n", cluster_id); ret = -ENOENT; goto put_node; } ret = dev_pm_opp_of_add_table(cpu_dev); if (ret < 0) { dev_err(cpu_dev, "failed to init OPP table: %d\n", ret); goto put_node; } ret = dev_pm_opp_init_cpufreq_table(cpu_dev, &freq_table[cluster_id]); if (ret) { dev_err(cpu_dev, "failed to init cpufreq table: %d\n", ret); goto out_free_opp; } if (of_property_read_u32(np, "clock-latency", &transition_latency[cluster_id])) transition_latency[cluster_id] = CPUFREQ_ETERNAL; break; } ret = cpufreq_register_driver(&imx8_cpufreq_driver); if (ret) { dev_err(cpu_dev, "failed register driver: %d\n", ret); if (cluster_id > 0 && first_cpu_dev != NULL) { dev_pm_opp_free_cpufreq_table(first_cpu_dev, &freq_table[0]); dev_pm_opp_of_remove_table(first_cpu_dev); } goto free_freq_table; } of_node_put(np); return 0; free_freq_table: dev_pm_opp_free_cpufreq_table(cpu_dev, &freq_table[cluster_id]); out_free_opp: dev_pm_opp_of_remove_table(cpu_dev); put_node: of_node_put(np); return ret; } static int imx8_cpufreq_remove(struct platform_device *pdev) { cpufreq_unregister_driver(&imx8_cpufreq_driver); return 0; } static struct platform_driver imx8_cpufreq_platdrv = { .driver = { .name = "imx8-cpufreq", }, .probe = imx8_cpufreq_probe, .remove = imx8_cpufreq_remove, }; module_platform_driver(imx8_cpufreq_platdrv); MODULE_AUTHOR("Anson Huang "); MODULE_DESCRIPTION("NXP i.MX8 cpufreq driver"); MODULE_LICENSE("GPL");