From patchwork Wed Nov 19 10:32:20 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Carlo Caione X-Patchwork-Id: 5335991 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 CA4D49F2ED for ; Wed, 19 Nov 2014 10:38:45 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id F049B20172 for ; Wed, 19 Nov 2014 10:38:43 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id E1ABD200E9 for ; Wed, 19 Nov 2014 10:38:41 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Xr2c6-0002CQ-EF; Wed, 19 Nov 2014 10:35:54 +0000 Received: from mail-wi0-x229.google.com ([2a00:1450:400c:c05::229]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Xr2ZB-0005iM-KE for linux-arm-kernel@lists.infradead.org; Wed, 19 Nov 2014 10:32:58 +0000 Received: by mail-wi0-f169.google.com with SMTP id r20so8403889wiv.4 for ; Wed, 19 Nov 2014 02:32:31 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=sender:from:to:cc:subject:date:message-id:in-reply-to:references; bh=c9/+WHqOeyGCpmyfEu3Fh0RGKlAOZUJcSeYL9BySLLg=; b=c7eRpMY75Ikku9ZhDsfAZj09yDuSLNwLGQvQGdcmRXtIRiYkrOqU1Hth3np4sR3XhX L8T5rSXb9w03GMzCax2Rwq4K5N+pSm2z594hgX3xff/IJAEp2yEDF3fe5CCwxhglUNAj J3sWC0dVeA6jTzcf8sIJdBO4dMmTnPgcyPeDHkmySV/juEyHEjMgeBvz6ljnrMtqMKrQ vxF0aYcu7YJqTWw7O+jvA5oHFjK0fvEZCTmwd+lbbqXghQei08qRrF2yCHB2Tqe/qds+ Xf+LgMtfwQYCuMPH7fgTM4kkAcR5aPrhfMMAQA+MBx5oDktT4GPcHxhUbJtOOYQXTzcW Nbcw== X-Received: by 10.180.82.170 with SMTP id j10mr12249555wiy.35.1416393151466; Wed, 19 Nov 2014 02:32:31 -0800 (PST) Received: from localhost.localdomain (host146-114-dynamic.0-87-r.retail.telecomitalia.it. [87.0.114.146]) by mx.google.com with ESMTPSA id dc8sm1623493wib.7.2014.11.19.02.32.30 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 19 Nov 2014 02:32:30 -0800 (PST) From: Carlo Caione To: mturquette@linaro.org, linux-arm-kernel@lists.infradead.org, b.galvani@gmail.com, jerry.cao@amlogic.com, victor.wan@amlogic.com, eric.hankinson@leandogsoftware.com, maxime.ripard@free-electrons.com, emilio@elopez.com.ar, robh+dt@kernel.org, p.zabel@pengutronix.de Subject: [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Date: Wed, 19 Nov 2014 11:32:20 +0100 Message-Id: <1416393143-20434-2-git-send-email-carlo@caione.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1416393143-20434-1-git-send-email-carlo@caione.org> References: <1416393143-20434-1-git-send-email-carlo@caione.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20141119_023254_157140_3E9A6B8F X-CRM114-Status: GOOD ( 23.37 ) X-Spam-Score: -0.7 (/) Cc: Carlo Caione X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 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=-2.5 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_LOW, T_DKIM_INVALID, T_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 patchset adds the infrastructure for registering and managing the core clocks found on Amlogic MesonX SoCs and also adds the support for the basic Meson6 clocks. Signed-off-by: Carlo Caione --- drivers/clk/Makefile | 1 + drivers/clk/meson/Makefile | 7 + drivers/clk/meson/clk-pll.c | 298 ++++++++++++++++++++++++++++++++ drivers/clk/meson/clkc.c | 151 ++++++++++++++++ drivers/clk/meson/clkc.h | 159 +++++++++++++++++ drivers/clk/meson/meson6-clkc.c | 160 +++++++++++++++++ include/dt-bindings/clock/meson6-clkc.h | 18 ++ 7 files changed, 794 insertions(+) create mode 100644 drivers/clk/meson/Makefile create mode 100644 drivers/clk/meson/clk-pll.c create mode 100644 drivers/clk/meson/clkc.c create mode 100644 drivers/clk/meson/clkc.h create mode 100644 drivers/clk/meson/meson6-clkc.c create mode 100644 include/dt-bindings/clock/meson6-clkc.h diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index d5fba5b..e93d134 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -51,6 +51,7 @@ ifeq ($(CONFIG_COMMON_CLK), y) obj-$(CONFIG_ARCH_MMP) += mmp/ endif obj-$(CONFIG_PLAT_ORION) += mvebu/ +obj-$(CONFIG_ARCH_MESON) += meson/ obj-$(CONFIG_ARCH_MXS) += mxs/ obj-$(CONFIG_COMMON_CLK_PXA) += pxa/ obj-$(CONFIG_COMMON_CLK_QCOM) += qcom/ diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile new file mode 100644 index 0000000..8f102e4 --- /dev/null +++ b/drivers/clk/meson/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for Meson specific clk +# + +obj-y += clkc.o +obj-y += meson6-clkc.o +obj-y += clk-pll.o diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c new file mode 100644 index 0000000..5b6d064 --- /dev/null +++ b/drivers/clk/meson/clk-pll.c @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2014 Carlo Caione + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clkc.h" + +/* + * A generic PLL in a Meson6/Meson8 SOC is composed as follows with a variable + * number of output divisors. + * + * PLL+DIVs + * ---------------------------------------------- + * | | + * | |---[DIV1]--->> out1 + * in ---[ 1/N ]---[ *M ]---[ *OD_FB ]---| .... | + * | ^ ^ ^ |---[DIV#]--->> out# + * | | | | | + * |--------------------------------------------- + * | | | + * FREF | VCO + * optional + * + * For each divisor DIV# the output frequency is calculated as: + * + * out# = in# * M * OD_FB / N / DIV# + */ + +#define PMASK(width) ((1U << (width)) - 1) +#define SETPMASK(width, shift) (PMASK(width) << (shift)) +#define CLRPMASK(width, shift) (~(SETPMASK(width, shift))) +#define PARM_GET(width, shift, reg) (((reg) & SETPMASK(width, shift)) >> (shift)) +#define PARM_SET(width, shift, reg, val) (((reg) & CLRPMASK(width, shift)) | (val << (shift))) + +#define MESON_FREF_MIN_MHZ 5 +#define MESON_FREF_MAX_MHZ 30 + +struct meson_clk_pll { + struct clk_hw hw; + void __iomem *base; + struct pll_conf *conf; + spinlock_t *lock; +}; +#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw) + +static void meson_clk_pll_get_parms(struct meson_clk_pll *pll, + unsigned long *rate, unsigned long p_rate, + u16 *best_n, u16 *best_m, u16 *best_od_fb) +{ + unsigned long rate_mhz, p_rate_mhz; + unsigned long pll_vco_min_mhz, pll_vco_max_mhz; + unsigned long cur_rate_mhz, best_rate_mhz; + u16 m, m_min, m_max, m_mask; + u16 n, n_min, n_max, n_mask, _n_min, _n_max; + u16 od_fb, od_fb_max; + + rate_mhz = *rate / 1000000; + p_rate_mhz = p_rate / 1000000; + pll_vco_min_mhz = pll->conf->vco_min_mhz; + pll_vco_max_mhz = pll->conf->vco_max_mhz; + best_rate_mhz = ULONG_MAX; + *best_n = 0; + *best_m = 0; + *best_od_fb = 1; + + m_mask = PMASK(pll->conf->m.width); + n_mask = PMASK(pll->conf->n.width); + + /* FREF = P_RATE / N */ + n_min = max_t(u16, DIV_ROUND_UP(p_rate_mhz, MESON_FREF_MAX_MHZ), 1); + n_max = min_t(u16, p_rate_mhz / MESON_FREF_MIN_MHZ, n_mask); + + od_fb_max = 1 << PMASK(pll->conf->od_fb.width); + + for (od_fb = 1; od_fb <= od_fb_max; od_fb <<= 1) { + + /* VCO = P_RATE * M * OD_FB / N */ + m_min = max_t(u16, DIV_ROUND_UP(pll_vco_min_mhz, + p_rate_mhz * od_fb) * n_min, 1); + m_max = min_t(u16, (pll_vco_max_mhz * n_max) / + (p_rate_mhz * od_fb), m_mask); + + + for (m = m_min; m <= m_max; m++) { + _n_min = max_t(u16, n_min, + DIV_ROUND_UP(p_rate_mhz * m * od_fb, + pll_vco_max_mhz)); + _n_max = min_t(u16, n_max, + p_rate_mhz * m * od_fb / pll_vco_min_mhz); + + for (n = _n_min; n <= _n_max; n++) { + cur_rate_mhz = p_rate_mhz * m * od_fb / n; + + if (abs(cur_rate_mhz - rate_mhz) < + abs(best_rate_mhz - rate_mhz)) { + best_rate_mhz = cur_rate_mhz; + *best_n = n; + *best_m = m; + *best_od_fb = od_fb; + if (best_rate_mhz == rate_mhz) + goto done; + } + } + } + } + +done: + *best_od_fb >>= 1; + *rate = best_rate_mhz * 1000000; + return; + +} + +static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct meson_clk_pll *pll = to_meson_clk_pll(hw); + struct parm *p_n, *p_m, *p_od_fb; + unsigned long parent_rate_mhz = parent_rate / 1000000; + unsigned long rate_mhz; + u16 n, m, od_fb = 1; + u32 reg_n, reg_m, reg_od_fb; + + p_n = &pll->conf->n; + p_m = &pll->conf->m; + p_od_fb = &pll->conf->od_fb; + + reg_n = readl(pll->base + p_n->reg_off); + n = PARM_GET(p_n->width, p_n->shift, reg_n); + + reg_m = readl(pll->base + p_m->reg_off); + m = PARM_GET(p_m->width, p_m->shift, reg_m); + + if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) { + reg_od_fb = readl(pll->base + p_od_fb->reg_off); + od_fb = PARM_GET(p_od_fb->width, p_od_fb->shift, reg_od_fb); + od_fb = 1 << od_fb; + } + + rate_mhz = parent_rate_mhz * m * od_fb / n; + + return rate_mhz * 1000000; +} + +static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct meson_clk_pll *pll = to_meson_clk_pll(hw); + u16 m, n, od_fb; + + meson_clk_pll_get_parms(pll, &rate, *parent_rate, &n, &m, &od_fb); + if (m == 0 || n == 0) + return -EINVAL; + + return rate; +} + +static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct meson_clk_pll *pll = to_meson_clk_pll(hw); + struct parm *p_n, *p_m, *p_od_fb; + unsigned long flags = 0; + u32 reg_n, reg_m, reg_od_fb; + u16 m, n, od_fb; + + spin_lock_irqsave(pll->lock, flags); + + if (parent_rate == 0 || rate == 0) + return -EINVAL; + + meson_clk_pll_get_parms(pll, &rate, parent_rate, &n, &m, &od_fb); + if (m == 0 || n == 0) + return -EINVAL; + + p_n = &pll->conf->n; + p_m = &pll->conf->m; + p_od_fb = &pll->conf->od_fb; + + reg_n = readl(pll->base + p_n->reg_off); + reg_n = PARM_SET(p_n->width, p_n->shift, reg_n, n); + writel(reg_n, pll->base + p_n->reg_off); + + reg_m = readl(pll->base + p_m->reg_off); + reg_m = PARM_SET(p_m->width, p_m->shift, reg_m, m); + writel(reg_m, pll->base + p_m->reg_off); + + if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) { + reg_od_fb = readl(pll->base + p_od_fb->reg_off); + reg_od_fb = PARM_SET(p_od_fb->width, p_od_fb->shift, reg_od_fb, + od_fb); + writel(reg_od_fb, pll->base + p_od_fb->reg_off); + } + + spin_unlock_irqrestore(pll->lock, flags); + + return 0; +} + +static const struct clk_ops meson_clk_pll_ops = { + .recalc_rate = meson_clk_pll_recalc_rate, + .round_rate = meson_clk_pll_round_rate, + .set_rate = meson_clk_pll_set_rate, +}; + + +static struct clk * __init meson_clk_pll_setup(struct pll_div_conf *pll_div_conf, + void __iomem *reg_base, + spinlock_t *lock) +{ + struct clk *clk; + struct meson_clk_pll *pll; + struct clk_init_data init; + const char *parent_name = pll_div_conf->clk_parent; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + pll->base = reg_base + pll_div_conf->reg_off; + pll->lock = lock; + pll->conf = pll_div_conf->pll_conf; + + init.name = pll_div_conf->pll_name; + init.ops = &meson_clk_pll_ops; + init.flags = pll_div_conf->flags; + init.parent_names = &parent_name; + init.num_parents = 1; + + pll->hw.init = &init; + + clk = clk_register(NULL, &pll->hw); + if (IS_ERR(clk)) + kfree(pll); + + return clk; +} + +void __init meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf, + void __iomem *reg_base, + spinlock_t *lock) +{ + struct clk *pclk, *clk; + struct div_conf *div; + void __iomem *div_base; + int d; + + pclk = meson_clk_pll_setup(pll_div_conf, reg_base, lock); + if (IS_ERR(pclk)) { + pr_warn("%s: Unable to create %s clock\n", __func__, + pll_div_conf->pll_name); + return; + } + + for (d = 0; d < MESON_DIVS_MAX; d++) { + div = &pll_div_conf->div[d]; + div_base = reg_base + pll_div_conf->reg_off + div->od.reg_off; + + if (div->clk_id == CLKID_UNUSED) + continue; + + clk = clk_register_divider(NULL, div->clk_name, + pll_div_conf->pll_name, + div->flags, div_base, + div->od.shift, div->od.width, + CLK_DIVIDER_POWER_OF_TWO, lock); + + if (IS_ERR(clk)) { + pr_warn("%s: Unable to create %s clock\n", __func__, + div->clk_name); + continue; + } + + meson_clk_add_lookup(clk, div->clk_id); + } +} + diff --git a/drivers/clk/meson/clkc.c b/drivers/clk/meson/clkc.c new file mode 100644 index 0000000..02b409e --- /dev/null +++ b/drivers/clk/meson/clkc.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014 Carlo Caione + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include + +#include "clkc.h" + +static DEFINE_SPINLOCK(clk_lock); + +static struct clk **clks; +static struct clk_onecell_data clk_data; + +void __init meson_clk_init(struct device_node *np, unsigned long nr_clks) +{ + clks = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL); + if (!clks) + pr_err("%s: could not allocate clock lookup table\n", __func__); + + clk_data.clks = clks; + clk_data.clk_num = nr_clks; + of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); +} + +void meson_clk_add_lookup(struct clk *clk, unsigned int id) +{ + if (clks && id) + clks[id] = clk; +} + +void __init meson_clk_register_pll_divs(struct pll_div_conf *pll_divs, + unsigned int nr_pll_divs, + void __iomem *pll_base) +{ + unsigned int i; + + for (i = 0; i < nr_pll_divs; i++) + meson_clk_register_pll_div(&pll_divs[i], pll_base, &clk_lock); +} + +static struct clk __init *meson_clk_register_mux_div(struct clk_conf *clk_conf, + void __iomem *clk_base) +{ + struct clk *clk; + struct clk_mux *mux = NULL; + struct clk_divider *div = NULL; + const struct clk_ops *mux_ops = NULL, *div_ops = NULL; + struct mux_div_conf *mux_div_conf = &clk_conf->conf.mux_div_conf; + + if (clk_conf->num_parents > 1) { + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->reg = clk_base + mux_div_conf->mux_parm.reg_off; + mux->shift = mux_div_conf->mux_parm.shift; + mux->mask = BIT(mux_div_conf->mux_parm.width) - 1; + mux->flags = mux_div_conf->mux_flags; + mux->lock = &clk_lock; + mux->table = mux_div_conf->mux_table; + mux_ops = (mux_div_conf->mux_flags & CLK_MUX_READ_ONLY) ? + &clk_mux_ro_ops : &clk_mux_ops; + } + + if (mux_div_conf->div_parm.width != MESON_PARM_NOT_APPLICABLE) { + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOMEM); + + div->flags = mux_div_conf->div_flags; + div->reg = clk_base + mux_div_conf->div_parm.reg_off; + div->shift = mux_div_conf->div_parm.shift; + div->width = mux_div_conf->div_parm.width; + div->lock = &clk_lock; + div->table = mux_div_conf->div_table; + div_ops = (mux_div_conf->div_flags & CLK_DIVIDER_READ_ONLY) ? + &clk_divider_ro_ops : &clk_divider_ops; + } + + clk = clk_register_composite(NULL, clk_conf->clk_name, + clk_conf->clks_parent, + clk_conf->num_parents, + mux ? &mux->hw : NULL, mux_ops, + div ? &div->hw : NULL, div_ops, + NULL, NULL, clk_conf->flags); + + return clk; +} + +void __init meson_clk_register_clks(struct clk_conf *clk_confs, + unsigned int nr_confs, + void __iomem *clk_base) +{ + unsigned int i; + struct clk *clk = NULL; + + for (i = 0; i < nr_confs; i++) { + struct clk_conf *clk_conf = &clk_confs[i]; + + switch (clk_conf->clk_type) { + case clk_fixed_rate: + clk = clk_register_fixed_rate(NULL, + clk_conf->clk_name, + (clk_conf->num_parents) ? + clk_conf->clks_parent[0] : NULL, + clk_conf->flags, + clk_conf->conf.fixed_rate); + break; + case clk_fixed_factor: + clk = clk_register_fixed_factor(NULL, + clk_conf->clk_name, + clk_conf->clks_parent[0], + clk_conf->flags, + clk_conf->conf.fixed_fact_conf.mult, + clk_conf->conf.fixed_fact_conf.div); + break; + case clk_mux_div: + clk = meson_clk_register_mux_div(clk_conf, clk_base); + break; + } + + if (!clk) { + pr_err("%s: unknown clock type %d\n", __func__, + clk_conf->clk_type); + continue; + } + + if (IS_ERR(clk)) { + pr_warn("%s: Unable to create %s clock\n", __func__, + clk_conf->clk_name); + continue; + } + + meson_clk_add_lookup(clk, clk_conf->clk_id); + } +} diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h new file mode 100644 index 0000000..6c0f611 --- /dev/null +++ b/drivers/clk/meson/clkc.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014 Carlo Caione + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __CLKC_H +#define __CLKC_H + +#include + +#define MESON_DIVS_MAX 2 +#define MESON_DIVS_WIDTH 2 +#define MESON_PARM_NOT_APPLICABLE 0 + +#define CLKID_UNUSED 0 + +struct parm { + u16 reg_off; + u8 shift; + u8 width; +}; +#define PARM(_r, _s, _w) { .reg_off = (_r), .shift = (_s), .width = (_w), } + +struct pll_conf { + unsigned long vco_min_mhz; + unsigned long vco_max_mhz; + struct parm m; + struct parm n; + struct parm od_fb; +}; +#define PLL_CONF3(_vmin, _vmax, _pm, _pn, _po) \ + { \ + .vco_min_mhz = (_vmin), \ + .vco_max_mhz = (_vmax), \ + .m = _pm, \ + .n = _pn, \ + .od_fb = _po, \ + } + +#define PLL_CONF2(_vmin, _vmax, _pm, _pn) \ + { \ + .vco_min_mhz = (_vmin), \ + .vco_max_mhz = (_vmax), \ + .m = _pm, \ + .n = _pn, \ + } + +struct div_conf { + unsigned int clk_id; + const char *clk_name; + struct parm od; + unsigned long flags; +}; +#define DIV_CONF(_name, _pod, _id, _f) \ + { \ + .clk_name = (_name), \ + .od = PARM(0x00, (_pod), MESON_DIVS_WIDTH), \ + .clk_id = (_id), \ + .flags = (_f), \ + } + +struct pll_div_conf { + u16 reg_off; + const char *pll_name; + const char *clk_parent; + unsigned long flags; + struct pll_conf *pll_conf; + struct div_conf div[MESON_DIVS_MAX]; +}; + +struct fixed_fact_conf { + unsigned int div; + unsigned int mult; +}; + +struct mux_div_conf { + u32 *mux_table; + u8 mux_flags; + struct clk_div_table *div_table; + + u8 div_flags; + struct parm mux_parm; + struct parm div_parm; +}; + +#define PNAME(x) static const char *x[] __initconst + +enum clk_type { clk_fixed_factor, clk_fixed_rate, clk_mux_div }; + +struct clk_conf { + enum clk_type clk_type; + unsigned int clk_id; + const char *clk_name; + const char **clks_parent; + int num_parents; + unsigned long flags; + union { + struct fixed_fact_conf fixed_fact_conf; + struct mux_div_conf mux_div_conf; + unsigned long fixed_rate; + } conf; +}; + +#define FIXED_ROOT(_id, _cn, _f, _r) \ + { \ + .clk_type = clk_fixed_rate, \ + .clk_id = (_id), \ + .clk_name = (_cn), \ + .flags = CLK_IS_ROOT | (_f), \ + .conf.fixed_rate = (_r), \ + } + +#define FIXED_DIV(_id, _cn, _cp, _f, _div) \ + { \ + .clk_type = clk_fixed_factor, \ + .clk_id = (_id), \ + .clk_name = (_cn), \ + .clks_parent = (_cp), \ + .num_parents = ARRAY_SIZE(_cp), \ + .flags = (_f), \ + .conf.fixed_fact_conf.div = (_div), \ + .conf.fixed_fact_conf.mult = 1, \ + } + +#define MUX_DIV(_id, _cn, _cp, _f, _mt, _mp, _dp) \ + { \ + .clk_type = clk_mux_div, \ + .clk_id = (_id), \ + .clk_name = (_cn), \ + .clks_parent = (_cp), \ + .num_parents = ARRAY_SIZE(_cp), \ + .flags = (_f), \ + .conf.mux_div_conf.mux_table = _mt, \ + .conf.mux_div_conf.mux_parm = _mp, \ + .conf.mux_div_conf.div_parm = _dp, \ + } + + +void meson_clk_init(struct device_node *np, unsigned long nr_clks); +void meson_clk_add_lookup(struct clk *clk, unsigned int id); +void meson_clk_register_pll_divs(struct pll_div_conf *pll_divs, + unsigned int nr_pll_divs, + void __iomem *reg_base); +void meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf, + void __iomem *reg_base, spinlock_t *lock); +void meson_clk_register_clks(struct clk_conf *clk_confs, + unsigned int nr_confs, + void __iomem *clk_base); diff --git a/drivers/clk/meson/meson6-clkc.c b/drivers/clk/meson/meson6-clkc.c new file mode 100644 index 0000000..9f809e1 --- /dev/null +++ b/drivers/clk/meson/meson6-clkc.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2014 Carlo Caione + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "clkc.h" + +#define MESON6_REG_MPLL_FIXED 0x0280 +#define MESON6_REG_DPLL_SYS 0x0260 +#define MESON6_REG_DPLL_DDR 0x01a0 +#define MESON6_REG_DPLL_VID2 0x011c +#define MESON6_REG_HPLL 0x0270 +#define MESON6_REG_HHI_MPEG 0x0174 + +#define MESON6_XTAL "xtal" + +static struct pll_conf dpll_conf = PLL_CONF2(750, 1512, PARM(0x00, 0, 9), + PARM(0x00, 9, 5)); + +static struct pll_conf mpll_conf = PLL_CONF3(1000, 2000, PARM(0x00, 0, 9), + PARM(0x00, 9, 5), + PARM(0x0c, 4, 1)); + +static struct pll_conf hpll_conf = PLL_CONF3(750, 1512, PARM(0x00, 0, 10), + PARM(0x00, 10, 5), + PARM(0x00, 20, 2)); + +static struct pll_div_conf meson_pll_divs[] __initdata = { + { + .clk_parent = MESON6_XTAL, + .reg_off = MESON6_REG_DPLL_SYS, + .pll_name = "vco_sys", + .pll_conf = &dpll_conf, + .flags = CLK_SET_RATE_PARENT, + .div = { + DIV_CONF("dpll_sys", 16, + CLKID_DPLL_SYS, + CLK_SET_RATE_PARENT), + } + }, + { + .clk_parent = MESON6_XTAL, + .reg_off = MESON6_REG_DPLL_DDR, + .pll_name = "vco_ddr", + .pll_conf = &dpll_conf, + .flags = CLK_SET_RATE_PARENT, + .div = { + DIV_CONF("dpll_ddr", 16, + CLKID_DPLL_DDR, + CLK_SET_RATE_PARENT), + } + }, + { + .clk_parent = MESON6_XTAL, + .reg_off = MESON6_REG_DPLL_VID2, + .pll_name = "vco_vid2", + .pll_conf = &dpll_conf, + .flags = CLK_SET_RATE_PARENT, + .div = { + DIV_CONF("dpll_vid2", 16, + CLKID_DPLL_VID2, + CLK_SET_RATE_PARENT), + } + }, + { + .clk_parent = MESON6_XTAL, + .reg_off = MESON6_REG_MPLL_FIXED, + .pll_name = "vco_fixed", + .pll_conf = &mpll_conf, + .flags = CLK_SET_RATE_PARENT, + .div = { + DIV_CONF("mpll_fixed", 16, + CLKID_MPLL_FIXED, + CLK_SET_RATE_PARENT), + } + }, + { + .clk_parent = MESON6_XTAL, + .reg_off = MESON6_REG_HPLL, + .pll_name = "vco_hpll", + .pll_conf = &hpll_conf, + .flags = CLK_SET_RATE_PARENT, + .div = { + DIV_CONF("hpll_lvds", 16, + CLKID_HPLL_LVDS, + CLK_SET_RATE_PARENT), + DIV_CONF("hpll_hdmi", 18, + CLKID_HPLL_HDMI, + CLK_SET_RATE_PARENT), + } + }, +}; + +PNAME(p_fclk_div) = { "mpll_fixed" }; +PNAME(p_clk81) = { "fclk_div2", "fclk_div3", "fclk_div5" }; + +static u32 mux_table_clk81[] __initdata = { 5, 6, 7 }; + +static struct clk_conf meson_clk_confs[] __initdata = { + FIXED_ROOT(CLKID_XTAL, MESON6_XTAL, 0, 0), + FIXED_ROOT(CLKID_RTC_XTAL, "rtc_xtal", 0, 32000), + FIXED_DIV(CLKID_FCLK_DIV2, "fclk_div2", p_fclk_div, 0, 2), + FIXED_DIV(CLKID_FCLK_DIV3, "fclk_div3", p_fclk_div, 0, 3), + FIXED_DIV(CLKID_FCLK_DIV5, "fclk_div5", p_fclk_div, 0, 5), + MUX_DIV(CLKID_CLK81, "clk81", p_clk81, 0, mux_table_clk81, + PARM(MESON6_REG_HHI_MPEG, 12, 3), + PARM(MESON6_REG_HHI_MPEG, 0, 7)), +}; + +static void __init meson_clkc_init(struct device_node *np) +{ + void __iomem *clk_base; + u32 xtal_rate; + + /* XTAL */ + clk_base = of_iomap(np, 0); + if (!clk_base) { + pr_err("%s: Unable to map xtal base\n", __func__); + return; + } + + xtal_rate = readl(clk_base) >> 4 & 0x3f; + xtal_rate *= 1000000; + meson_clk_confs[0].conf.fixed_rate = xtal_rate; + iounmap(clk_base); + + /* Generic clocks and PLLs */ + clk_base = of_iomap(np, 1); + if (!clk_base) { + pr_err("%s: Unable to map clk base\n", __func__); + return; + } + + meson_clk_init(np, CLK_NR_CLKS); + + meson_clk_register_clks(meson_clk_confs, ARRAY_SIZE(meson_clk_confs), + clk_base); + meson_clk_register_pll_divs(meson_pll_divs, ARRAY_SIZE(meson_pll_divs), + clk_base); +} +CLK_OF_DECLARE(meson6_clock, "amlogic,meson6-clkc", meson_clkc_init); diff --git a/include/dt-bindings/clock/meson6-clkc.h b/include/dt-bindings/clock/meson6-clkc.h new file mode 100644 index 0000000..06efef3 --- /dev/null +++ b/include/dt-bindings/clock/meson6-clkc.h @@ -0,0 +1,18 @@ +/* + * Meson6 clock tree IDs + */ + +#define CLKID_XTAL 1 +#define CLKID_RTC_XTAL 2 +#define CLKID_DPLL_SYS 3 +#define CLKID_DPLL_DDR 4 +#define CLKID_DPLL_VID2 5 +#define CLKID_MPLL_FIXED 6 +#define CLKID_HPLL_HDMI 7 +#define CLKID_HPLL_LVDS 8 +#define CLKID_FCLK_DIV2 9 +#define CLKID_FCLK_DIV3 10 +#define CLKID_FCLK_DIV5 11 +#define CLKID_CLK81 12 + +#define CLK_NR_CLKS (CLKID_CLK81 + 1)