blob: f43e93738a992360024c5345765e43e9e6d9c6ec [file] [log] [blame]
Boris BREZILLON1a748d22013-10-11 10:48:26 +02001/*
2 * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 */
10
11#include <linux/clk-provider.h>
12#include <linux/clkdev.h>
13#include <linux/clk/at91_pmc.h>
14#include <linux/of.h>
15#include <linux/of_address.h>
16#include <linux/io.h>
Boris Brezillon1bdf0232014-09-07 08:14:29 +020017#include <linux/mfd/syscon.h>
18#include <linux/regmap.h>
Boris BREZILLON1a748d22013-10-11 10:48:26 +020019
20#include "pmc.h"
21
22#define to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
23
24struct clk_plldiv {
25 struct clk_hw hw;
Boris Brezillon1bdf0232014-09-07 08:14:29 +020026 struct regmap *regmap;
Boris BREZILLON1a748d22013-10-11 10:48:26 +020027};
28
29static unsigned long clk_plldiv_recalc_rate(struct clk_hw *hw,
30 unsigned long parent_rate)
31{
32 struct clk_plldiv *plldiv = to_clk_plldiv(hw);
Boris Brezillon1bdf0232014-09-07 08:14:29 +020033 unsigned int mckr;
Boris BREZILLON1a748d22013-10-11 10:48:26 +020034
Boris Brezillon1bdf0232014-09-07 08:14:29 +020035 regmap_read(plldiv->regmap, AT91_PMC_MCKR, &mckr);
36
37 if (mckr & AT91_PMC_PLLADIV2)
Boris BREZILLON1a748d22013-10-11 10:48:26 +020038 return parent_rate / 2;
39
40 return parent_rate;
41}
42
43static long clk_plldiv_round_rate(struct clk_hw *hw, unsigned long rate,
44 unsigned long *parent_rate)
45{
46 unsigned long div;
47
48 if (rate > *parent_rate)
49 return *parent_rate;
50 div = *parent_rate / 2;
51 if (rate < div)
52 return div;
53
54 if (rate - div < *parent_rate - rate)
55 return div;
56
57 return *parent_rate;
58}
59
60static int clk_plldiv_set_rate(struct clk_hw *hw, unsigned long rate,
61 unsigned long parent_rate)
62{
63 struct clk_plldiv *plldiv = to_clk_plldiv(hw);
Boris BREZILLON1a748d22013-10-11 10:48:26 +020064
Boris Brezillon1bdf0232014-09-07 08:14:29 +020065 if ((parent_rate != rate) && (parent_rate / 2 != rate))
Boris BREZILLON1a748d22013-10-11 10:48:26 +020066 return -EINVAL;
67
Boris Brezillon1bdf0232014-09-07 08:14:29 +020068 regmap_update_bits(plldiv->regmap, AT91_PMC_MCKR, AT91_PMC_PLLADIV2,
69 parent_rate != rate ? AT91_PMC_PLLADIV2 : 0);
Boris BREZILLON1a748d22013-10-11 10:48:26 +020070
71 return 0;
72}
73
74static const struct clk_ops plldiv_ops = {
75 .recalc_rate = clk_plldiv_recalc_rate,
76 .round_rate = clk_plldiv_round_rate,
77 .set_rate = clk_plldiv_set_rate,
78};
79
80static struct clk * __init
Boris Brezillon1bdf0232014-09-07 08:14:29 +020081at91_clk_register_plldiv(struct regmap *regmap, const char *name,
Boris BREZILLON1a748d22013-10-11 10:48:26 +020082 const char *parent_name)
83{
84 struct clk_plldiv *plldiv;
85 struct clk *clk = NULL;
86 struct clk_init_data init;
87
88 plldiv = kzalloc(sizeof(*plldiv), GFP_KERNEL);
89 if (!plldiv)
90 return ERR_PTR(-ENOMEM);
91
92 init.name = name;
93 init.ops = &plldiv_ops;
94 init.parent_names = parent_name ? &parent_name : NULL;
95 init.num_parents = parent_name ? 1 : 0;
96 init.flags = CLK_SET_RATE_GATE;
97
98 plldiv->hw.init = &init;
Boris Brezillon1bdf0232014-09-07 08:14:29 +020099 plldiv->regmap = regmap;
Boris BREZILLON1a748d22013-10-11 10:48:26 +0200100
101 clk = clk_register(NULL, &plldiv->hw);
102
103 if (IS_ERR(clk))
104 kfree(plldiv);
105
106 return clk;
107}
108
109static void __init
Boris Brezillon1bdf0232014-09-07 08:14:29 +0200110of_at91sam9x5_clk_plldiv_setup(struct device_node *np)
Boris BREZILLON1a748d22013-10-11 10:48:26 +0200111{
112 struct clk *clk;
113 const char *parent_name;
114 const char *name = np->name;
Boris Brezillon1bdf0232014-09-07 08:14:29 +0200115 struct regmap *regmap;
Boris BREZILLON1a748d22013-10-11 10:48:26 +0200116
117 parent_name = of_clk_get_parent_name(np, 0);
118
119 of_property_read_string(np, "clock-output-names", &name);
120
Boris Brezillon1bdf0232014-09-07 08:14:29 +0200121 regmap = syscon_node_to_regmap(of_get_parent(np));
122 if (IS_ERR(regmap))
123 return;
Boris BREZILLON1a748d22013-10-11 10:48:26 +0200124
Boris Brezillon1bdf0232014-09-07 08:14:29 +0200125 clk = at91_clk_register_plldiv(regmap, name, parent_name);
Boris BREZILLON1a748d22013-10-11 10:48:26 +0200126 if (IS_ERR(clk))
127 return;
128
129 of_clk_add_provider(np, of_clk_src_simple_get, clk);
130 return;
131}
Boris Brezillon1bdf0232014-09-07 08:14:29 +0200132CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv",
133 of_at91sam9x5_clk_plldiv_setup);