1d3df18a9SNicolas Saenz Julienne // SPDX-License-Identifier: GPL-2.0 2d3df18a9SNicolas Saenz Julienne /* 3d3df18a9SNicolas Saenz Julienne * Raspberry Pi cpufreq driver 4d3df18a9SNicolas Saenz Julienne * 5d3df18a9SNicolas Saenz Julienne * Copyright (C) 2019, Nicolas Saenz Julienne <nsaenzjulienne@suse.de> 6d3df18a9SNicolas Saenz Julienne */ 7d3df18a9SNicolas Saenz Julienne 8d3df18a9SNicolas Saenz Julienne #include <linux/clk.h> 9d3df18a9SNicolas Saenz Julienne #include <linux/cpu.h> 10d3df18a9SNicolas Saenz Julienne #include <linux/cpufreq.h> 11d3df18a9SNicolas Saenz Julienne #include <linux/module.h> 12d3df18a9SNicolas Saenz Julienne #include <linux/platform_device.h> 13d3df18a9SNicolas Saenz Julienne #include <linux/pm_opp.h> 14d3df18a9SNicolas Saenz Julienne 15d3df18a9SNicolas Saenz Julienne #define RASPBERRYPI_FREQ_INTERVAL 100000000 16d3df18a9SNicolas Saenz Julienne 17d3df18a9SNicolas Saenz Julienne static struct platform_device *cpufreq_dt; 18d3df18a9SNicolas Saenz Julienne 19d3df18a9SNicolas Saenz Julienne static int raspberrypi_cpufreq_probe(struct platform_device *pdev) 20d3df18a9SNicolas Saenz Julienne { 21d3df18a9SNicolas Saenz Julienne struct device *cpu_dev; 22d3df18a9SNicolas Saenz Julienne unsigned long min, max; 23d3df18a9SNicolas Saenz Julienne unsigned long rate; 24d3df18a9SNicolas Saenz Julienne struct clk *clk; 25d3df18a9SNicolas Saenz Julienne int ret; 26d3df18a9SNicolas Saenz Julienne 27d3df18a9SNicolas Saenz Julienne cpu_dev = get_cpu_device(0); 28d3df18a9SNicolas Saenz Julienne if (!cpu_dev) { 29d3df18a9SNicolas Saenz Julienne pr_err("Cannot get CPU for cpufreq driver\n"); 30d3df18a9SNicolas Saenz Julienne return -ENODEV; 31d3df18a9SNicolas Saenz Julienne } 32d3df18a9SNicolas Saenz Julienne 33d3df18a9SNicolas Saenz Julienne clk = clk_get(cpu_dev, NULL); 34d3df18a9SNicolas Saenz Julienne if (IS_ERR(clk)) { 35d3df18a9SNicolas Saenz Julienne dev_err(cpu_dev, "Cannot get clock for CPU0\n"); 36d3df18a9SNicolas Saenz Julienne return PTR_ERR(clk); 37d3df18a9SNicolas Saenz Julienne } 38d3df18a9SNicolas Saenz Julienne 39d3df18a9SNicolas Saenz Julienne /* 40d3df18a9SNicolas Saenz Julienne * The max and min frequencies are configurable in the Raspberry Pi 41d3df18a9SNicolas Saenz Julienne * firmware, so we query them at runtime. 42d3df18a9SNicolas Saenz Julienne */ 43d3df18a9SNicolas Saenz Julienne min = roundup(clk_round_rate(clk, 0), RASPBERRYPI_FREQ_INTERVAL); 44d3df18a9SNicolas Saenz Julienne max = roundup(clk_round_rate(clk, ULONG_MAX), RASPBERRYPI_FREQ_INTERVAL); 45d3df18a9SNicolas Saenz Julienne clk_put(clk); 46d3df18a9SNicolas Saenz Julienne 47d3df18a9SNicolas Saenz Julienne for (rate = min; rate <= max; rate += RASPBERRYPI_FREQ_INTERVAL) { 48d3df18a9SNicolas Saenz Julienne ret = dev_pm_opp_add(cpu_dev, rate, 0); 49d3df18a9SNicolas Saenz Julienne if (ret) 50d3df18a9SNicolas Saenz Julienne goto remove_opp; 51d3df18a9SNicolas Saenz Julienne } 52d3df18a9SNicolas Saenz Julienne 53d3df18a9SNicolas Saenz Julienne cpufreq_dt = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); 54d3df18a9SNicolas Saenz Julienne ret = PTR_ERR_OR_ZERO(cpufreq_dt); 55d3df18a9SNicolas Saenz Julienne if (ret) { 56d3df18a9SNicolas Saenz Julienne dev_err(cpu_dev, "Failed to create platform device, %d\n", ret); 57d3df18a9SNicolas Saenz Julienne goto remove_opp; 58d3df18a9SNicolas Saenz Julienne } 59d3df18a9SNicolas Saenz Julienne 60d3df18a9SNicolas Saenz Julienne return 0; 61d3df18a9SNicolas Saenz Julienne 62d3df18a9SNicolas Saenz Julienne remove_opp: 63d3df18a9SNicolas Saenz Julienne dev_pm_opp_remove_all_dynamic(cpu_dev); 64d3df18a9SNicolas Saenz Julienne 65d3df18a9SNicolas Saenz Julienne return ret; 66d3df18a9SNicolas Saenz Julienne } 67d3df18a9SNicolas Saenz Julienne 68d3df18a9SNicolas Saenz Julienne static int raspberrypi_cpufreq_remove(struct platform_device *pdev) 69d3df18a9SNicolas Saenz Julienne { 70d3df18a9SNicolas Saenz Julienne struct device *cpu_dev; 71d3df18a9SNicolas Saenz Julienne 72d3df18a9SNicolas Saenz Julienne cpu_dev = get_cpu_device(0); 73d3df18a9SNicolas Saenz Julienne if (cpu_dev) 74d3df18a9SNicolas Saenz Julienne dev_pm_opp_remove_all_dynamic(cpu_dev); 75d3df18a9SNicolas Saenz Julienne 76d3df18a9SNicolas Saenz Julienne platform_device_unregister(cpufreq_dt); 77d3df18a9SNicolas Saenz Julienne 78d3df18a9SNicolas Saenz Julienne return 0; 79d3df18a9SNicolas Saenz Julienne } 80d3df18a9SNicolas Saenz Julienne 81d3df18a9SNicolas Saenz Julienne /* 82d3df18a9SNicolas Saenz Julienne * Since the driver depends on clk-raspberrypi, which may return EPROBE_DEFER, 83d3df18a9SNicolas Saenz Julienne * all the activity is performed in the probe, which may be defered as well. 84d3df18a9SNicolas Saenz Julienne */ 85d3df18a9SNicolas Saenz Julienne static struct platform_driver raspberrypi_cpufreq_driver = { 86d3df18a9SNicolas Saenz Julienne .driver = { 87d3df18a9SNicolas Saenz Julienne .name = "raspberrypi-cpufreq", 88d3df18a9SNicolas Saenz Julienne }, 89d3df18a9SNicolas Saenz Julienne .probe = raspberrypi_cpufreq_probe, 90d3df18a9SNicolas Saenz Julienne .remove = raspberrypi_cpufreq_remove, 91d3df18a9SNicolas Saenz Julienne }; 92d3df18a9SNicolas Saenz Julienne module_platform_driver(raspberrypi_cpufreq_driver); 93d3df18a9SNicolas Saenz Julienne 94d3df18a9SNicolas Saenz Julienne MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@suse.de"); 95d3df18a9SNicolas Saenz Julienne MODULE_DESCRIPTION("Raspberry Pi cpufreq driver"); 96d3df18a9SNicolas Saenz Julienne MODULE_LICENSE("GPL"); 97d3df18a9SNicolas Saenz Julienne MODULE_ALIAS("platform:raspberrypi-cpufreq"); 98