kirkwood-cpufreq.c 5.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 *	kirkwood_freq.c: cpufreq driver for the Marvell kirkwood
 *
 *	Copyright (C) 2013 Andrew Lunn <andrew@lunn.ch>
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License
 *	as published by the Free Software Foundation; either version
 *	2 of the License, or (at your option) any later version.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
16
#include <linux/of_device.h>
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include <linux/platform_device.h>
#include <linux/io.h>
#include <asm/proc-fns.h>

#define CPU_SW_INT_BLK BIT(28)

static struct priv
{
	struct clk *cpu_clk;
	struct clk *ddr_clk;
	struct clk *powersave_clk;
	struct device *dev;
	void __iomem *base;
} priv;

#define STATE_CPU_FREQ 0x01
#define STATE_DDR_FREQ 0x02

/*
 * Kirkwood can swap the clock to the CPU between two clocks:
 *
 * - cpu clk
 * - ddr clk
 *
41
 * The frequencies are set at runtime before registering this table.
42 43
 */
static struct cpufreq_frequency_table kirkwood_freq_table[] = {
44 45 46
	{0, STATE_CPU_FREQ,	0}, /* CPU uses cpuclk */
	{0, STATE_DDR_FREQ,	0}, /* CPU uses ddrclk */
	{0, 0,			CPUFREQ_TABLE_END},
47 48 49 50
};

static unsigned int kirkwood_cpufreq_get_cpu_frequency(unsigned int cpu)
{
51
	return clk_get_rate(priv.powersave_clk) / 1000;
52 53
}

54 55
static int kirkwood_cpufreq_target(struct cpufreq_policy *policy,
			    unsigned int index)
56
{
57
	unsigned int state = kirkwood_freq_table[index].driver_data;
58 59
	unsigned long reg;

60
	local_irq_disable();
61

62 63 64 65
	/* Disable interrupts to the CPU */
	reg = readl_relaxed(priv.base);
	reg |= CPU_SW_INT_BLK;
	writel_relaxed(reg, priv.base);
66

67 68
	switch (state) {
	case STATE_CPU_FREQ:
69
		clk_set_parent(priv.powersave_clk, priv.cpu_clk);
70 71
		break;
	case STATE_DDR_FREQ:
72
		clk_set_parent(priv.powersave_clk, priv.ddr_clk);
73 74
		break;
	}
75

76 77
	/* Wait-for-Interrupt, while the hardware changes frequency */
	cpu_do_idle();
78

79 80 81 82
	/* Enable interrupts to the CPU */
	reg = readl_relaxed(priv.base);
	reg &= ~CPU_SW_INT_BLK;
	writel_relaxed(reg, priv.base);
83

84
	local_irq_enable();
85 86 87 88 89 90 91

	return 0;
}

/* Module init and exit code */
static int kirkwood_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
92
	return cpufreq_generic_init(policy, kirkwood_freq_table, 5000);
93 94 95
}

static struct cpufreq_driver kirkwood_cpufreq_driver = {
96
	.flags	= CPUFREQ_NEED_INITIAL_FREQ_CHECK,
97
	.get	= kirkwood_cpufreq_get_cpu_frequency,
98
	.verify	= cpufreq_generic_frequency_table_verify,
99
	.target_index = kirkwood_cpufreq_target,
100 101
	.init	= kirkwood_cpufreq_cpu_init,
	.name	= "kirkwood-cpufreq",
102
	.attr	= cpufreq_generic_attr,
103 104 105 106 107 108 109 110 111 112 113
};

static int kirkwood_cpufreq_probe(struct platform_device *pdev)
{
	struct device_node *np;
	struct resource *res;
	int err;

	priv.dev = &pdev->dev;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
114 115 116
	priv.base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(priv.base))
		return PTR_ERR(priv.base);
117

118 119 120
	np = of_cpu_device_node_get(0);
	if (!np) {
		dev_err(&pdev->dev, "failed to get cpu device node\n");
121
		return -ENODEV;
122
	}
123 124 125

	priv.cpu_clk = of_clk_get_by_name(np, "cpu_clk");
	if (IS_ERR(priv.cpu_clk)) {
126
		dev_err(priv.dev, "Unable to get cpuclk\n");
127 128 129
		return PTR_ERR(priv.cpu_clk);
	}

130 131 132 133 134 135
	err = clk_prepare_enable(priv.cpu_clk);
	if (err) {
		dev_err(priv.dev, "Unable to prepare cpuclk\n");
		return err;
	}

136 137 138 139
	kirkwood_freq_table[0].frequency = clk_get_rate(priv.cpu_clk) / 1000;

	priv.ddr_clk = of_clk_get_by_name(np, "ddrclk");
	if (IS_ERR(priv.ddr_clk)) {
140
		dev_err(priv.dev, "Unable to get ddrclk\n");
141 142 143 144
		err = PTR_ERR(priv.ddr_clk);
		goto out_cpu;
	}

145 146 147 148 149
	err = clk_prepare_enable(priv.ddr_clk);
	if (err) {
		dev_err(priv.dev, "Unable to prepare ddrclk\n");
		goto out_cpu;
	}
150 151 152 153
	kirkwood_freq_table[1].frequency = clk_get_rate(priv.ddr_clk) / 1000;

	priv.powersave_clk = of_clk_get_by_name(np, "powersave");
	if (IS_ERR(priv.powersave_clk)) {
154
		dev_err(priv.dev, "Unable to get powersave\n");
155 156 157
		err = PTR_ERR(priv.powersave_clk);
		goto out_ddr;
	}
158 159 160 161 162
	err = clk_prepare_enable(priv.powersave_clk);
	if (err) {
		dev_err(priv.dev, "Unable to prepare powersave clk\n");
		goto out_ddr;
	}
163 164 165 166 167 168 169 170

	of_node_put(np);
	np = NULL;

	err = cpufreq_register_driver(&kirkwood_cpufreq_driver);
	if (!err)
		return 0;

171
	dev_err(priv.dev, "Failed to register cpufreq driver\n");
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207

	clk_disable_unprepare(priv.powersave_clk);
out_ddr:
	clk_disable_unprepare(priv.ddr_clk);
out_cpu:
	clk_disable_unprepare(priv.cpu_clk);
	of_node_put(np);

	return err;
}

static int kirkwood_cpufreq_remove(struct platform_device *pdev)
{
	cpufreq_unregister_driver(&kirkwood_cpufreq_driver);

	clk_disable_unprepare(priv.powersave_clk);
	clk_disable_unprepare(priv.ddr_clk);
	clk_disable_unprepare(priv.cpu_clk);

	return 0;
}

static struct platform_driver kirkwood_cpufreq_platform_driver = {
	.probe = kirkwood_cpufreq_probe,
	.remove = kirkwood_cpufreq_remove,
	.driver = {
		.name = "kirkwood-cpufreq",
	},
};

module_platform_driver(kirkwood_cpufreq_platform_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch");
MODULE_DESCRIPTION("cpufreq driver for Marvell's kirkwood CPU");
MODULE_ALIAS("platform:kirkwood-cpufreq");