diff options
| author | Boris BREZILLON <b.brezillon@overkiz.com> | 2013-10-11 04:51:23 -0400 |
|---|---|---|
| committer | Nicolas Ferre <nicolas.ferre@atmel.com> | 2013-12-02 09:31:23 -0500 |
| commit | e442d234405ad75e2d3d2baf15b364ee2c3573c9 (patch) | |
| tree | 3aa5e2666bf5fccf0e4c61cf50030c839d7ef1b7 /drivers/clk/at91 | |
| parent | 1a748d2bc5061b72588013a720645661345c0e65 (diff) | |
clk: at91: add PMC master clock
This patch adds new at91 master clock implementation using common clk
framework.
The master clock layout describe the MCKR register layout.
There are 2 master clock layouts:
- at91rm9200
- at91sam9x5
Master clocks are given characteristics:
- min/max clock output rate
These characteristics are checked during rate change to avoid
over/underclocking.
These characteristics are described in atmel's SoC datasheet in
"Electrical Characteristics" paragraph.
Signed-off-by: Boris BREZILLON <b.brezillon@overkiz.com>
Acked-by: Mike Turquette <mturquette@linaro.org>
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Diffstat (limited to 'drivers/clk/at91')
| -rw-r--r-- | drivers/clk/at91/Makefile | 2 | ||||
| -rw-r--r-- | drivers/clk/at91/clk-master.c | 270 | ||||
| -rw-r--r-- | drivers/clk/at91/clk-pll.c | 10 | ||||
| -rw-r--r-- | drivers/clk/at91/pmc.c | 9 | ||||
| -rw-r--r-- | drivers/clk/at91/pmc.h | 5 |
5 files changed, 287 insertions, 9 deletions
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile index 902bbf1fe584..e28fb2bbb198 100644 --- a/drivers/clk/at91/Makefile +++ b/drivers/clk/at91/Makefile | |||
| @@ -3,4 +3,4 @@ | |||
| 3 | # | 3 | # |
| 4 | 4 | ||
| 5 | obj-y += pmc.o | 5 | obj-y += pmc.o |
| 6 | obj-y += clk-main.o clk-pll.o clk-plldiv.o | 6 | obj-y += clk-main.o clk-pll.o clk-plldiv.o clk-master.o |
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c new file mode 100644 index 000000000000..bd313f7816a8 --- /dev/null +++ b/drivers/clk/at91/clk-master.c | |||
| @@ -0,0 +1,270 @@ | |||
| 1 | /* | ||
| 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/of_irq.h> | ||
| 17 | #include <linux/io.h> | ||
| 18 | #include <linux/wait.h> | ||
| 19 | #include <linux/sched.h> | ||
| 20 | #include <linux/interrupt.h> | ||
| 21 | #include <linux/irq.h> | ||
| 22 | |||
| 23 | #include "pmc.h" | ||
| 24 | |||
| 25 | #define MASTER_SOURCE_MAX 4 | ||
| 26 | |||
| 27 | #define MASTER_PRES_MASK 0x7 | ||
| 28 | #define MASTER_PRES_MAX MASTER_PRES_MASK | ||
| 29 | #define MASTER_DIV_SHIFT 8 | ||
| 30 | #define MASTER_DIV_MASK 0x3 | ||
| 31 | |||
| 32 | struct clk_master_characteristics { | ||
| 33 | struct clk_range output; | ||
| 34 | u32 divisors[4]; | ||
| 35 | u8 have_div3_pres; | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct clk_master_layout { | ||
| 39 | u32 mask; | ||
| 40 | u8 pres_shift; | ||
| 41 | }; | ||
| 42 | |||
| 43 | #define to_clk_master(hw) container_of(hw, struct clk_master, hw) | ||
| 44 | |||
| 45 | struct clk_master { | ||
| 46 | struct clk_hw hw; | ||
| 47 | struct at91_pmc *pmc; | ||
| 48 | unsigned int irq; | ||
| 49 | wait_queue_head_t wait; | ||
| 50 | const struct clk_master_layout *layout; | ||
| 51 | const struct clk_master_characteristics *characteristics; | ||
| 52 | }; | ||
| 53 | |||
| 54 | static irqreturn_t clk_master_irq_handler(int irq, void *dev_id) | ||
| 55 | { | ||
| 56 | struct clk_master *master = (struct clk_master *)dev_id; | ||
| 57 | |||
| 58 | wake_up(&master->wait); | ||
| 59 | disable_irq_nosync(master->irq); | ||
| 60 | |||
| 61 | return IRQ_HANDLED; | ||
| 62 | } | ||
| 63 | static int clk_master_prepare(struct clk_hw *hw) | ||
| 64 | { | ||
| 65 | struct clk_master *master = to_clk_master(hw); | ||
| 66 | struct at91_pmc *pmc = master->pmc; | ||
| 67 | |||
| 68 | while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY)) { | ||
| 69 | enable_irq(master->irq); | ||
| 70 | wait_event(master->wait, | ||
| 71 | pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY); | ||
| 72 | } | ||
| 73 | |||
| 74 | return 0; | ||
| 75 | } | ||
| 76 | |||
| 77 | static int clk_master_is_prepared(struct clk_hw *hw) | ||
| 78 | { | ||
| 79 | struct clk_master *master = to_clk_master(hw); | ||
| 80 | |||
| 81 | return !!(pmc_read(master->pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY); | ||
| 82 | } | ||
| 83 | |||
| 84 | static unsigned long clk_master_recalc_rate(struct clk_hw *hw, | ||
| 85 | unsigned long parent_rate) | ||
| 86 | { | ||
| 87 | u8 pres; | ||
| 88 | u8 div; | ||
| 89 | unsigned long rate = parent_rate; | ||
| 90 | struct clk_master *master = to_clk_master(hw); | ||
| 91 | struct at91_pmc *pmc = master->pmc; | ||
| 92 | const struct clk_master_layout *layout = master->layout; | ||
| 93 | const struct clk_master_characteristics *characteristics = | ||
| 94 | master->characteristics; | ||
| 95 | u32 tmp; | ||
| 96 | |||
| 97 | pmc_lock(pmc); | ||
| 98 | tmp = pmc_read(pmc, AT91_PMC_MCKR) & layout->mask; | ||
| 99 | pmc_unlock(pmc); | ||
| 100 | |||
| 101 | pres = (tmp >> layout->pres_shift) & MASTER_PRES_MASK; | ||
| 102 | div = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; | ||
| 103 | |||
| 104 | if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX) | ||
| 105 | rate /= 3; | ||
| 106 | else | ||
| 107 | rate >>= pres; | ||
| 108 | |||
| 109 | rate /= characteristics->divisors[div]; | ||
| 110 | |||
| 111 | if (rate < characteristics->output.min) | ||
| 112 | pr_warn("master clk is underclocked"); | ||
| 113 | else if (rate > characteristics->output.max) | ||
| 114 | pr_warn("master clk is overclocked"); | ||
| 115 | |||
| 116 | return rate; | ||
| 117 | } | ||
| 118 | |||
| 119 | static u8 clk_master_get_parent(struct clk_hw *hw) | ||
| 120 | { | ||
| 121 | struct clk_master *master = to_clk_master(hw); | ||
| 122 | struct at91_pmc *pmc = master->pmc; | ||
| 123 | |||
| 124 | return pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_CSS; | ||
| 125 | } | ||
| 126 | |||
| 127 | static const struct clk_ops master_ops = { | ||
| 128 | .prepare = clk_master_prepare, | ||
| 129 | .is_prepared = clk_master_is_prepared, | ||
| 130 | .recalc_rate = clk_master_recalc_rate, | ||
| 131 | .get_parent = clk_master_get_parent, | ||
| 132 | }; | ||
| 133 | |||
| 134 | static struct clk * __init | ||
| 135 | at91_clk_register_master(struct at91_pmc *pmc, unsigned int irq, | ||
| 136 | const char *name, int num_parents, | ||
| 137 | const char **parent_names, | ||
| 138 | const struct clk_master_layout *layout, | ||
| 139 | const struct clk_master_characteristics *characteristics) | ||
| 140 | { | ||
| 141 | int ret; | ||
| 142 | struct clk_master *master; | ||
| 143 | struct clk *clk = NULL; | ||
| 144 | struct clk_init_data init; | ||
| 145 | |||
| 146 | if (!pmc || !irq || !name || !num_parents || !parent_names) | ||
| 147 | return ERR_PTR(-EINVAL); | ||
| 148 | |||
| 149 | master = kzalloc(sizeof(*master), GFP_KERNEL); | ||
| 150 | if (!master) | ||
| 151 | return ERR_PTR(-ENOMEM); | ||
| 152 | |||
| 153 | init.name = name; | ||
| 154 | init.ops = &master_ops; | ||
| 155 | init.parent_names = parent_names; | ||
| 156 | init.num_parents = num_parents; | ||
| 157 | init.flags = 0; | ||
| 158 | |||
| 159 | master->hw.init = &init; | ||
| 160 | master->layout = layout; | ||
| 161 | master->characteristics = characteristics; | ||
| 162 | master->pmc = pmc; | ||
| 163 | master->irq = irq; | ||
| 164 | init_waitqueue_head(&master->wait); | ||
| 165 | irq_set_status_flags(master->irq, IRQ_NOAUTOEN); | ||
| 166 | ret = request_irq(master->irq, clk_master_irq_handler, | ||
| 167 | IRQF_TRIGGER_HIGH, "clk-master", master); | ||
| 168 | if (ret) | ||
| 169 | return ERR_PTR(ret); | ||
| 170 | |||
| 171 | clk = clk_register(NULL, &master->hw); | ||
| 172 | if (IS_ERR(clk)) | ||
| 173 | kfree(master); | ||
| 174 | |||
| 175 | return clk; | ||
| 176 | } | ||
| 177 | |||
| 178 | |||
| 179 | static const struct clk_master_layout at91rm9200_master_layout = { | ||
| 180 | .mask = 0x31F, | ||
| 181 | .pres_shift = 2, | ||
| 182 | }; | ||
| 183 | |||
| 184 | static const struct clk_master_layout at91sam9x5_master_layout = { | ||
| 185 | .mask = 0x373, | ||
| 186 | .pres_shift = 4, | ||
| 187 | }; | ||
| 188 | |||
| 189 | |||
| 190 | static struct clk_master_characteristics * __init | ||
| 191 | of_at91_clk_master_get_characteristics(struct device_node *np) | ||
| 192 | { | ||
| 193 | struct clk_master_characteristics *characteristics; | ||
| 194 | |||
| 195 | characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL); | ||
| 196 | if (!characteristics) | ||
| 197 | return NULL; | ||
| 198 | |||
| 199 | if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output)) | ||
| 200 | goto out_free_characteristics; | ||
| 201 | |||
| 202 | of_property_read_u32_array(np, "atmel,clk-divisors", | ||
| 203 | characteristics->divisors, 4); | ||
| 204 | |||
| 205 | characteristics->have_div3_pres = | ||
| 206 | of_property_read_bool(np, "atmel,master-clk-have-div3-pres"); | ||
| 207 | |||
| 208 | return characteristics; | ||
| 209 | |||
| 210 | out_free_characteristics: | ||
| 211 | kfree(characteristics); | ||
| 212 | return NULL; | ||
| 213 | } | ||
| 214 | |||
| 215 | static void __init | ||
| 216 | of_at91_clk_master_setup(struct device_node *np, struct at91_pmc *pmc, | ||
| 217 | const struct clk_master_layout *layout) | ||
| 218 | { | ||
| 219 | struct clk *clk; | ||
| 220 | int num_parents; | ||
| 221 | int i; | ||
| 222 | unsigned int irq; | ||
| 223 | const char *parent_names[MASTER_SOURCE_MAX]; | ||
| 224 | const char *name = np->name; | ||
| 225 | struct clk_master_characteristics *characteristics; | ||
| 226 | |||
| 227 | num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells"); | ||
| 228 | if (num_parents <= 0 || num_parents > MASTER_SOURCE_MAX) | ||
| 229 | return; | ||
| 230 | |||
| 231 | for (i = 0; i < num_parents; ++i) { | ||
| 232 | parent_names[i] = of_clk_get_parent_name(np, i); | ||
| 233 | if (!parent_names[i]) | ||
| 234 | return; | ||
| 235 | } | ||
| 236 | |||
| 237 | of_property_read_string(np, "clock-output-names", &name); | ||
| 238 | |||
| 239 | characteristics = of_at91_clk_master_get_characteristics(np); | ||
| 240 | if (!characteristics) | ||
| 241 | return; | ||
| 242 | |||
| 243 | irq = irq_of_parse_and_map(np, 0); | ||
| 244 | if (!irq) | ||
| 245 | return; | ||
| 246 | |||
| 247 | clk = at91_clk_register_master(pmc, irq, name, num_parents, | ||
| 248 | parent_names, layout, | ||
| 249 | characteristics); | ||
| 250 | if (IS_ERR(clk)) | ||
| 251 | goto out_free_characteristics; | ||
| 252 | |||
| 253 | of_clk_add_provider(np, of_clk_src_simple_get, clk); | ||
| 254 | return; | ||
| 255 | |||
| 256 | out_free_characteristics: | ||
| 257 | kfree(characteristics); | ||
| 258 | } | ||
| 259 | |||
| 260 | void __init of_at91rm9200_clk_master_setup(struct device_node *np, | ||
| 261 | struct at91_pmc *pmc) | ||
| 262 | { | ||
| 263 | of_at91_clk_master_setup(np, pmc, &at91rm9200_master_layout); | ||
| 264 | } | ||
| 265 | |||
| 266 | void __init of_at91sam9x5_clk_master_setup(struct device_node *np, | ||
| 267 | struct at91_pmc *pmc) | ||
| 268 | { | ||
| 269 | of_at91_clk_master_setup(np, pmc, &at91sam9x5_master_layout); | ||
| 270 | } | ||
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c index 5e23ee4ce387..cf6ed023504c 100644 --- a/drivers/clk/at91/clk-pll.c +++ b/drivers/clk/at91/clk-pll.c | |||
| @@ -80,6 +80,8 @@ static int clk_pll_prepare(struct clk_hw *hw) | |||
| 80 | struct clk_pll *pll = to_clk_pll(hw); | 80 | struct clk_pll *pll = to_clk_pll(hw); |
| 81 | struct at91_pmc *pmc = pll->pmc; | 81 | struct at91_pmc *pmc = pll->pmc; |
| 82 | const struct clk_pll_layout *layout = pll->layout; | 82 | const struct clk_pll_layout *layout = pll->layout; |
| 83 | const struct clk_pll_characteristics *characteristics = | ||
| 84 | pll->characteristics; | ||
| 83 | u8 id = pll->id; | 85 | u8 id = pll->id; |
| 84 | u32 mask = PLL_STATUS_MASK(id); | 86 | u32 mask = PLL_STATUS_MASK(id); |
| 85 | int offset = PLL_REG(id); | 87 | int offset = PLL_REG(id); |
| @@ -269,18 +271,10 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, | |||
| 269 | unsigned long parent_rate) | 271 | unsigned long parent_rate) |
| 270 | { | 272 | { |
| 271 | struct clk_pll *pll = to_clk_pll(hw); | 273 | struct clk_pll *pll = to_clk_pll(hw); |
| 272 | struct at91_pmc *pmc = pll->pmc; | ||
| 273 | const struct clk_pll_layout *layout = pll->layout; | ||
| 274 | const struct clk_pll_characteristics *characteristics = | ||
| 275 | pll->characteristics; | ||
| 276 | u8 id = pll->id; | ||
| 277 | int offset = PLL_REG(id); | ||
| 278 | long ret; | 274 | long ret; |
| 279 | u32 div; | 275 | u32 div; |
| 280 | u32 mul; | 276 | u32 mul; |
| 281 | u32 index; | 277 | u32 index; |
| 282 | u32 tmp; | ||
| 283 | u8 out = 0; | ||
| 284 | 278 | ||
| 285 | ret = clk_pll_get_best_div_mul(pll, rate, parent_rate, | 279 | ret = clk_pll_get_best_div_mul(pll, rate, parent_rate, |
| 286 | &div, &mul, &index); | 280 | &div, &mul, &index); |
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c index 5af3f2fbf45e..a311cf3d5358 100644 --- a/drivers/clk/at91/pmc.c +++ b/drivers/clk/at91/pmc.c | |||
| @@ -255,6 +255,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = { | |||
| 255 | .compatible = "atmel,at91sam9x5-clk-plldiv", | 255 | .compatible = "atmel,at91sam9x5-clk-plldiv", |
| 256 | .data = of_at91sam9x5_clk_plldiv_setup, | 256 | .data = of_at91sam9x5_clk_plldiv_setup, |
| 257 | }, | 257 | }, |
| 258 | /* Master clock */ | ||
| 259 | { | ||
| 260 | .compatible = "atmel,at91rm9200-clk-master", | ||
| 261 | .data = of_at91rm9200_clk_master_setup, | ||
| 262 | }, | ||
| 263 | { | ||
| 264 | .compatible = "atmel,at91sam9x5-clk-master", | ||
| 265 | .data = of_at91sam9x5_clk_master_setup, | ||
| 266 | }, | ||
| 258 | { /*sentinel*/ } | 267 | { /*sentinel*/ } |
| 259 | }; | 268 | }; |
| 260 | 269 | ||
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h index 15aaf38e37c5..be5c964accad 100644 --- a/drivers/clk/at91/pmc.h +++ b/drivers/clk/at91/pmc.h | |||
| @@ -72,4 +72,9 @@ extern void __init of_sama5d3_clk_pll_setup(struct device_node *np, | |||
| 72 | extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np, | 72 | extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np, |
| 73 | struct at91_pmc *pmc); | 73 | struct at91_pmc *pmc); |
| 74 | 74 | ||
| 75 | extern void __init of_at91rm9200_clk_master_setup(struct device_node *np, | ||
| 76 | struct at91_pmc *pmc); | ||
| 77 | extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np, | ||
| 78 | struct at91_pmc *pmc); | ||
| 79 | |||
| 75 | #endif /* __PMC_H_ */ | 80 | #endif /* __PMC_H_ */ |
