0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0012
0013 #include <linux/kernel.h>
0014 #include <linux/module.h>
0015 #include <linux/clk.h>
0016 #include <linux/cpu.h>
0017 #include <linux/err.h>
0018 #include <linux/of.h>
0019 #include <linux/pl320-ipc.h>
0020 #include <linux/platform_device.h>
0021
0022 #define HB_CPUFREQ_CHANGE_NOTE 0x80000001
0023 #define HB_CPUFREQ_IPC_LEN 7
0024 #define HB_CPUFREQ_VOLT_RETRIES 15
0025
0026 static int hb_voltage_change(unsigned int freq)
0027 {
0028 u32 msg[HB_CPUFREQ_IPC_LEN] = {HB_CPUFREQ_CHANGE_NOTE, freq / 1000000};
0029
0030 return pl320_ipc_transmit(msg);
0031 }
0032
0033 static int hb_cpufreq_clk_notify(struct notifier_block *nb,
0034 unsigned long action, void *hclk)
0035 {
0036 struct clk_notifier_data *clk_data = hclk;
0037 int i = 0;
0038
0039 if (action == PRE_RATE_CHANGE) {
0040 if (clk_data->new_rate > clk_data->old_rate)
0041 while (hb_voltage_change(clk_data->new_rate))
0042 if (i++ > HB_CPUFREQ_VOLT_RETRIES)
0043 return NOTIFY_BAD;
0044 } else if (action == POST_RATE_CHANGE) {
0045 if (clk_data->new_rate < clk_data->old_rate)
0046 while (hb_voltage_change(clk_data->new_rate))
0047 if (i++ > HB_CPUFREQ_VOLT_RETRIES)
0048 return NOTIFY_BAD;
0049 }
0050
0051 return NOTIFY_DONE;
0052 }
0053
0054 static struct notifier_block hb_cpufreq_clk_nb = {
0055 .notifier_call = hb_cpufreq_clk_notify,
0056 };
0057
0058 static int hb_cpufreq_driver_init(void)
0059 {
0060 struct platform_device_info devinfo = { .name = "cpufreq-dt", };
0061 struct device *cpu_dev;
0062 struct clk *cpu_clk;
0063 struct device_node *np;
0064 int ret;
0065
0066 if ((!of_machine_is_compatible("calxeda,highbank")) &&
0067 (!of_machine_is_compatible("calxeda,ecx-2000")))
0068 return -ENODEV;
0069
0070 cpu_dev = get_cpu_device(0);
0071 if (!cpu_dev) {
0072 pr_err("failed to get highbank cpufreq device\n");
0073 return -ENODEV;
0074 }
0075
0076 np = of_node_get(cpu_dev->of_node);
0077 if (!np) {
0078 pr_err("failed to find highbank cpufreq node\n");
0079 return -ENOENT;
0080 }
0081
0082 cpu_clk = clk_get(cpu_dev, NULL);
0083 if (IS_ERR(cpu_clk)) {
0084 ret = PTR_ERR(cpu_clk);
0085 pr_err("failed to get cpu0 clock: %d\n", ret);
0086 goto out_put_node;
0087 }
0088
0089 ret = clk_notifier_register(cpu_clk, &hb_cpufreq_clk_nb);
0090 if (ret) {
0091 pr_err("failed to register clk notifier: %d\n", ret);
0092 goto out_put_node;
0093 }
0094
0095
0096 platform_device_register_full(&devinfo);
0097
0098 out_put_node:
0099 of_node_put(np);
0100 return ret;
0101 }
0102 module_init(hb_cpufreq_driver_init);
0103
0104 static const struct of_device_id __maybe_unused hb_cpufreq_of_match[] = {
0105 { .compatible = "calxeda,highbank" },
0106 { .compatible = "calxeda,ecx-2000" },
0107 { },
0108 };
0109 MODULE_DEVICE_TABLE(of, hb_cpufreq_of_match);
0110
0111 MODULE_AUTHOR("Mark Langsdorf <mark.langsdorf@calxeda.com>");
0112 MODULE_DESCRIPTION("Calxeda Highbank cpufreq driver");
0113 MODULE_LICENSE("GPL");