From patchwork Fri Oct 11 08:51:23 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Boris BREZILLON X-Patchwork-Id: 3021591 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 7F29C9F245 for ; Fri, 11 Oct 2013 08:54:27 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id F0C2A2032D for ; Fri, 11 Oct 2013 08:54:25 +0000 (UTC) Received: from casper.infradead.org (casper.infradead.org [85.118.1.10]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 8634620266 for ; Fri, 11 Oct 2013 08:54:20 +0000 (UTC) Received: from merlin.infradead.org ([2001:4978:20e::2]) by casper.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1VUYUB-0001Op-Ry; Fri, 11 Oct 2013 08:54:16 +0000 Received: from localhost ([::1] helo=merlin.infradead.org) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1VUYU9-0000wl-7t; Fri, 11 Oct 2013 08:54:13 +0000 Received: from 16.mo1.mail-out.ovh.net ([178.33.104.224] helo=mo1.mail-out.ovh.net) by merlin.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1VUYU4-0000w4-L8 for linux-arm-kernel@lists.infradead.org; Fri, 11 Oct 2013 08:54:10 +0000 Received: from mail621.ha.ovh.net (b6.ovh.net [213.186.33.56]) by mo1.mail-out.ovh.net (Postfix) with SMTP id 37734FFC15C for ; Fri, 11 Oct 2013 10:53:51 +0200 (CEST) Received: from b0.ovh.net (HELO queueout) (213.186.33.50) by b0.ovh.net with SMTP; 11 Oct 2013 10:54:28 +0200 Received: from unknown (HELO bbrezillon-laptop.private.s2ih) (b.brezillon@overkiz.com@80.245.18.66) by ns0.ovh.net with SMTP; 11 Oct 2013 10:54:26 +0200 From: Boris BREZILLON To: Rob Herring , Pawel Moll , Mark Rutland , Stephen Warren , Ian Campbell , Rob Landley , Andrew Victor , Nicolas Ferre , Jean-Christophe Plagniol-Villard , Russell King , Mike Turquette , Felipe Balbi , Greg Kroah-Hartman , Grant Likely , Ludovic Desroches , Josh Wu , Richard Genoud Subject: [PATCH v4 07/17] clk: at91: add PMC master clock Date: Fri, 11 Oct 2013 10:51:23 +0200 Message-Id: <1381481483-12248-1-git-send-email-b.brezillon@overkiz.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1381477081-6041-1-git-send-email-b.brezillon@overkiz.com> References: <1381477081-6041-1-git-send-email-b.brezillon@overkiz.com> X-Ovh-Tracer-Id: 13561745854504925408 X-Ovh-Remote: 80.245.18.66 () X-Ovh-Local: 213.186.33.20 (ns0.ovh.net) X-OVH-SPAMSTATE: OK X-OVH-SPAMSCORE: -100 X-OVH-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeeiledrvdefucetufdoteggodetrfcurfhrohhfihhlvgemucfqggfjnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd X-Spam-Check: DONE|U 0.5/N X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: -100 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeeiledrvdefucetufdoteggodetrfcurfhrohhfihhlvgemucfqggfjnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmd X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20131011_045409_391996_3A1BE66B X-CRM114-Status: GOOD ( 26.86 ) X-Spam-Score: -1.9 (-) Cc: devicetree@vger.kernel.org, linux-doc@vger.kernel.org, linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org, Boris BREZILLON , linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.4 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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_ */