Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757724Ab3JQP7k (ORCPT ); Thu, 17 Oct 2013 11:59:40 -0400 Received: from eusmtp01.atmel.com ([212.144.249.243]:4466 "EHLO eusmtp01.atmel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1757683Ab3JQP7g (ORCPT ); Thu, 17 Oct 2013 11:59:36 -0400 Message-ID: <52600962.9060601@atmel.com> Date: Thu, 17 Oct 2013 17:59:30 +0200 From: Nicolas Ferre Organization: atmel User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.0 MIME-Version: 1.0 To: Boris BREZILLON , Rob Herring , Pawel Moll , Mark Rutland , Stephen Warren , Ian Campbell , Rob Landley , Andrew Victor , Jean-Christophe Plagniol-Villard , Russell King , "Mike Turquette" , Felipe Balbi , "Greg Kroah-Hartman" , Grant Likely , Ludovic Desroches , Josh Wu , Richard Genoud CC: , , , , Subject: Re: [PATCH v4 07/17] clk: at91: add PMC master clock References: <1381477081-6041-1-git-send-email-b.brezillon@overkiz.com> <1381481483-12248-1-git-send-email-b.brezillon@overkiz.com> In-Reply-To: <1381481483-12248-1-git-send-email-b.brezillon@overkiz.com> Content-Type: text/plain; charset="ISO-8859-1"; format=flowed Content-Transfer-Encoding: 7bit X-Originating-IP: [10.161.30.18] Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 11928 Lines: 401 On 11/10/2013 10:51, Boris BREZILLON : > 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 Acked-by: Nicolas Ferre > --- > drivers/clk/at91/Makefile | 2 +- > drivers/clk/at91/clk-master.c | 278 +++++++++++++++++++++++++++++++++++++++++ > drivers/clk/at91/clk-pll.c | 10 +- > drivers/clk/at91/pmc.c | 9 ++ > drivers/clk/at91/pmc.h | 5 + > 5 files changed, 295 insertions(+), 9 deletions(-) > create mode 100644 drivers/clk/at91/clk-master.c > > diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile > index 902bbf1..e28fb2b 100644 > --- a/drivers/clk/at91/Makefile > +++ b/drivers/clk/at91/Makefile > @@ -3,4 +3,4 @@ > # > > obj-y += pmc.o > -obj-y += clk-main.o clk-pll.o clk-plldiv.o > +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 0000000..58af23d > --- /dev/null > +++ b/drivers/clk/at91/clk-master.c > @@ -0,0 +1,278 @@ > +/* > + * drivers/clk/at91/clk-master.c > + * > + * Copyright (C) 2013 Boris BREZILLON > + * > + * 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 > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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) > +{ > + u32 tmp; > + struct clk_master_characteristics *characteristics; > + > + characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL); > + if (!characteristics) > + return NULL; > + > + if (of_property_read_u32_index(np, "atmel,clk-output-range", 0, &tmp)) > + goto out_free_characteristics; > + characteristics->output.min = tmp; > + > + if (of_property_read_u32_index(np, "atmel,clk-output-range", 1, &tmp)) > + goto out_free_characteristics; > + characteristics->output.max = tmp; > + > + 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); > +} > diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c > index 44caddf..351adc5 100644 > --- a/drivers/clk/at91/clk-pll.c > +++ b/drivers/clk/at91/clk-pll.c > @@ -82,6 +82,8 @@ static int clk_pll_prepare(struct clk_hw *hw) > struct clk_pll *pll = to_clk_pll(hw); > struct at91_pmc *pmc = pll->pmc; > const struct clk_pll_layout *layout = pll->layout; > + const struct clk_pll_characteristics *characteristics = > + pll->characteristics; > u8 id = pll->id; > u32 mask = PLL_STATUS_MASK(id); > int offset = PLL_REG(id); > @@ -271,18 +273,10 @@ static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, > unsigned long parent_rate) > { > struct clk_pll *pll = to_clk_pll(hw); > - struct at91_pmc *pmc = pll->pmc; > - const struct clk_pll_layout *layout = pll->layout; > - const struct clk_pll_characteristics *characteristics = > - pll->characteristics; > - u8 id = pll->id; > - int offset = PLL_REG(id); > long ret; > u32 div; > u32 mul; > u32 index; > - u32 tmp; > - u8 out = 0; > > ret = clk_pll_get_best_div_mul(pll, rate, parent_rate, > &div, &mul, &index); > diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c > index c882e97..6f08879 100644 > --- a/drivers/clk/at91/pmc.c > +++ b/drivers/clk/at91/pmc.c > @@ -234,6 +234,15 @@ static const struct of_device_id pmc_clk_ids[] __initdata = { > .compatible = "atmel,at91sam9x5-clk-plldiv", > .data = of_at91sam9x5_clk_plldiv_setup, > }, > + /* Master clock */ > + { > + .compatible = "atmel,at91rm9200-clk-master", > + .data = of_at91rm9200_clk_master_setup, > + }, > + { > + .compatible = "atmel,at91sam9x5-clk-master", > + .data = of_at91sam9x5_clk_master_setup, > + }, > { /*sentinel*/ } > }; > > diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h > index 35ccbbc..2e1dcb8 100644 > --- a/drivers/clk/at91/pmc.h > +++ b/drivers/clk/at91/pmc.h > @@ -69,4 +69,9 @@ extern void __init of_sama5d3_clk_pll_setup(struct device_node *np, > extern void __init of_at91sam9x5_clk_plldiv_setup(struct device_node *np, > struct at91_pmc *pmc); > > +extern void __init of_at91rm9200_clk_master_setup(struct device_node *np, > + struct at91_pmc *pmc); > +extern void __init of_at91sam9x5_clk_master_setup(struct device_node *np, > + struct at91_pmc *pmc); > + > #endif /* __PMC_H_ */ > -- Nicolas Ferre -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/