diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-01-24 03:36:55 +0100 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-01-24 03:36:55 +0100 |
commit | f2c73464d7b399cf4e0c601c1c7d7b079080fa52 (patch) | |
tree | 902decd0c280757075bce5068fee679e0ccc261e /drivers/clk/at91/clk-master.c | |
parent | Merge tag 'fixes-nc-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/g... (diff) | |
parent | Merge tag 'at91-cleanup2' of git://github.com/at91linux/linux-at91 into next/... (diff) | |
download | linux-f2c73464d7b399cf4e0c601c1c7d7b079080fa52.tar.xz linux-f2c73464d7b399cf4e0c601c1c7d7b079080fa52.zip |
Merge tag 'cleanup-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC cleanups from Olof Johansson:
"This is the branch where we usually queue up cleanup efforts, moving
drivers out of the architecture directory, header file restructuring,
etc. Sometimes they tangle with new development so it's hard to keep
it strictly to cleanups.
Some of the things included in this branch are:
* Atmel SAMA5 conversion to common clock
* Reset framework conversion for tegra platforms
- Some of this depends on tegra clock driver reworks that are shared
with Mike Turquette's clk tree.
* Tegra DMA refactoring, which are shared branches with the DMA tree.
* Removal of some header files on exynos to prepare for
multiplatform"
* tag 'cleanup-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (169 commits)
ARM: mvebu: move Armada 370/XP specific definitions to armada-370-xp.h
ARM: mvebu: remove prototypes of non-existing functions from common.h
ARM: mvebu: move ARMADA_XP_MAX_CPUS to armada-370-xp.h
serial: sh-sci: Rework baud rate calculation
serial: sh-sci: Compute overrun_bit without using baud rate algo
serial: sh-sci: Remove unused GPIO request code
serial: sh-sci: Move overrun_bit and error_mask fields out of pdata
serial: sh-sci: Support resources passed through platform resources
serial: sh-sci: Don't check IRQ in verify port operation
serial: sh-sci: Set the UPF_FIXED_PORT flag
serial: sh-sci: Remove duplicate interrupt check in verify port op
serial: sh-sci: Simplify baud rate calculation algorithms
serial: sh-sci: Remove baud rate calculation algorithm 5
serial: sh-sci: Sort headers alphabetically
ARM: EXYNOS: Kill exynos_pm_late_initcall()
ARM: EXYNOS: Consolidate selection of PM_GENERIC_DOMAINS for Exynos4
ARM: at91: switch Calao QIL-A9260 board to DT
clk: at91: fix pmc_clk_ids data type attriubte
PM / devfreq: use inclusion <mach/map.h> instead of <plat/map-s5p.h>
ARM: EXYNOS: remove <mach/regs-clock.h> for exynos
...
Diffstat (limited to 'drivers/clk/at91/clk-master.c')
-rw-r--r-- | drivers/clk/at91/clk-master.c | 270 |
1 files changed, 270 insertions, 0 deletions
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 @@ +/* + * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com> + * + * 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/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/clk/at91_pmc.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/io.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/irq.h> + +#include "pmc.h" + +#define MASTER_SOURCE_MAX 4 + +#define MASTER_PRES_MASK 0x7 +#define MASTER_PRES_MAX MASTER_PRES_MASK +#define MASTER_DIV_SHIFT 8 +#define MASTER_DIV_MASK 0x3 + +struct clk_master_characteristics { + struct clk_range output; + u32 divisors[4]; + u8 have_div3_pres; +}; + +struct clk_master_layout { + u32 mask; + u8 pres_shift; +}; + +#define to_clk_master(hw) container_of(hw, struct clk_master, hw) + +struct clk_master { + struct clk_hw hw; + struct at91_pmc *pmc; + unsigned int irq; + wait_queue_head_t wait; + const struct clk_master_layout *layout; + const struct clk_master_characteristics *characteristics; +}; + +static irqreturn_t clk_master_irq_handler(int irq, void *dev_id) +{ + struct clk_master *master = (struct clk_master *)dev_id; + + wake_up(&master->wait); + disable_irq_nosync(master->irq); + + return IRQ_HANDLED; +} +static int clk_master_prepare(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + struct at91_pmc *pmc = master->pmc; + + while (!(pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY)) { + enable_irq(master->irq); + wait_event(master->wait, + pmc_read(pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY); + } + + return 0; +} + +static int clk_master_is_prepared(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + + return !!(pmc_read(master->pmc, AT91_PMC_SR) & AT91_PMC_MCKRDY); +} + +static unsigned long clk_master_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + u8 pres; + u8 div; + unsigned long rate = parent_rate; + struct clk_master *master = to_clk_master(hw); + struct at91_pmc *pmc = master->pmc; + const struct clk_master_layout *layout = master->layout; + const struct clk_master_characteristics *characteristics = + master->characteristics; + u32 tmp; + + pmc_lock(pmc); + tmp = pmc_read(pmc, AT91_PMC_MCKR) & layout->mask; + pmc_unlock(pmc); + + pres = (tmp >> layout->pres_shift) & MASTER_PRES_MASK; + div = (tmp >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; + + if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX) + rate /= 3; + else + rate >>= pres; + + rate /= characteristics->divisors[div]; + + if (rate < characteristics->output.min) + pr_warn("master clk is underclocked"); + else if (rate > characteristics->output.max) + pr_warn("master clk is overclocked"); + + return rate; +} + +static u8 clk_master_get_parent(struct clk_hw *hw) +{ + struct clk_master *master = to_clk_master(hw); + struct at91_pmc *pmc = master->pmc; + + return pmc_read(pmc, AT91_PMC_MCKR) & AT91_PMC_CSS; +} + +static const struct clk_ops master_ops = { + .prepare = clk_master_prepare, + .is_prepared = clk_master_is_prepared, + .recalc_rate = clk_master_recalc_rate, + .get_parent = clk_master_get_parent, +}; + +static struct clk * __init +at91_clk_register_master(struct at91_pmc *pmc, unsigned int irq, + const char *name, int num_parents, + const char **parent_names, + const struct clk_master_layout *layout, + const struct clk_master_characteristics *characteristics) +{ + int ret; + struct clk_master *master; + struct clk *clk = NULL; + struct clk_init_data init; + + if (!pmc || !irq || !name || !num_parents || !parent_names) + return ERR_PTR(-EINVAL); + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &master_ops; + init.parent_names = parent_names; + init.num_parents = num_parents; + init.flags = 0; + + master->hw.init = &init; + master->layout = layout; + master->characteristics = characteristics; + master->pmc = pmc; + master->irq = irq; + init_waitqueue_head(&master->wait); + irq_set_status_flags(master->irq, IRQ_NOAUTOEN); + ret = request_irq(master->irq, clk_master_irq_handler, + IRQF_TRIGGER_HIGH, "clk-master", master); + if (ret) + return ERR_PTR(ret); + + clk = clk_register(NULL, &master->hw); + if (IS_ERR(clk)) + kfree(master); + + return clk; +} + + +static const struct clk_master_layout at91rm9200_master_layout = { + .mask = 0x31F, + .pres_shift = 2, +}; + +static const struct clk_master_layout at91sam9x5_master_layout = { + .mask = 0x373, + .pres_shift = 4, +}; + + +static struct clk_master_characteristics * __init +of_at91_clk_master_get_characteristics(struct device_node *np) +{ + struct clk_master_characteristics *characteristics; + + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL); + if (!characteristics) + return NULL; + + if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output)) + goto out_free_characteristics; + + of_property_read_u32_array(np, "atmel,clk-divisors", + characteristics->divisors, 4); + + characteristics->have_div3_pres = + of_property_read_bool(np, "atmel,master-clk-have-div3-pres"); + + return characteristics; + +out_free_characteristics: + kfree(characteristics); + return NULL; +} + +static void __init +of_at91_clk_master_setup(struct device_node *np, struct at91_pmc *pmc, + const struct clk_master_layout *layout) +{ + struct clk *clk; + int num_parents; + int i; + unsigned int irq; + const char *parent_names[MASTER_SOURCE_MAX]; + const char *name = np->name; + struct clk_master_characteristics *characteristics; + + num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells"); + if (num_parents <= 0 || num_parents > MASTER_SOURCE_MAX) + return; + + for (i = 0; i < num_parents; ++i) { + parent_names[i] = of_clk_get_parent_name(np, i); + if (!parent_names[i]) + return; + } + + of_property_read_string(np, "clock-output-names", &name); + + characteristics = of_at91_clk_master_get_characteristics(np); + if (!characteristics) + return; + + irq = irq_of_parse_and_map(np, 0); + if (!irq) + return; + + clk = at91_clk_register_master(pmc, irq, name, num_parents, + parent_names, layout, + characteristics); + if (IS_ERR(clk)) + goto out_free_characteristics; + + of_clk_add_provider(np, of_clk_src_simple_get, clk); + return; + +out_free_characteristics: + kfree(characteristics); +} + +void __init of_at91rm9200_clk_master_setup(struct device_node *np, + struct at91_pmc *pmc) +{ + of_at91_clk_master_setup(np, pmc, &at91rm9200_master_layout); +} + +void __init of_at91sam9x5_clk_master_setup(struct device_node *np, + struct at91_pmc *pmc) +{ + of_at91_clk_master_setup(np, pmc, &at91sam9x5_master_layout); +} |