From patchwork Sat Jan 20 17:13:41 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Lechner X-Patchwork-Id: 10176463 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id CF8036056F for ; Sat, 20 Jan 2018 17:18:28 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B01C52856E for ; Sat, 20 Jan 2018 17:18:28 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A239E2880B; Sat, 20 Jan 2018 17:18:28 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 447B92856E for ; Sat, 20 Jan 2018 17:18:27 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=euNyiXqfyJdsaJvZqkgfeT++rofxnSGQ5Y2HwcyIkJs=; b=NuUef8aw85n8flGn8aUSqpJVEw nkugdzATsSCo0jhCKKhefSGcQF7brBo7A7IgWtSamv32xQgY8EYzuxl5G/5fhSjyGCvMu0rIIQTe0 wWrwshUPTmJUVR3zv4KaqGQ6N2Ho40wl9nHxxc6EABX2+dEwGLOVopoRmKn23EgemUOBVvxP7s0JR 2wP2PkO8EQHVRCdqU5mpjy+dcBjAAFMdZT3qRBEndkVlSBJYEIoqnE8+M/EHXnQkjemoePdNUMasJ F32B3TVnCZ2MG/AMeSShlGu8C4FRCx210NLSns5OCf69YUCtW7ZbzwMFcTxqq10xtaH8AG93mq3Ig GSZ5fnzA==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.89 #1 (Red Hat Linux)) id 1ecwme-0008Sb-Bs; Sat, 20 Jan 2018 17:18:24 +0000 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by bombadil.infradead.org with esmtps (Exim 4.89 #1 (Red Hat Linux)) id 1ecwkk-0006KY-MD for linux-arm-kernel@bombadil.infradead.org; Sat, 20 Jan 2018 17:16:27 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=References:In-Reply-To:Message-Id:Date: Subject:Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=gFGYD1bYr6Ojyc6VNnGfpvgobWhvZ/Nx642j3A8D6VU=; b=Puy5ZTUH7ouM7ySZcfGNuVJWG nY/tqT00Q7fytBaE42plmycrVUmSIJMZOzCdHbgEZur9AZX5qY1bXghfPQn0nhqO5wkFIOsAdlgH2 Wavg12QtREVPii+uYyqehE/esy8hoRz1/9N7/yfppzZ2cQpXpBMQteRWMJPZe/C7hKMiZZtB1juva 5NQm1Lc/ZN1R/7i/4oOOVDYxK5wGDZHtd3JtwFFZh3Vx225fnaAv9SUywKPGVEkSron4ZjXQp7gQD iXqFaxi14rfWJ/h5FTwkhi9ZzunDAtL2lsOUCzAKQb0DY8Z6wZgzAKlXeOsL6kzzlT1jUWaDJhKyh feTRqCuxQ==; Received: from vern.gendns.com ([206.190.152.46]) by casper.infradead.org with esmtps (Exim 4.89 #1 (Red Hat Linux)) id 1ecwka-0005IB-E7 for linux-arm-kernel@lists.infradead.org; Sat, 20 Jan 2018 17:16:24 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lechnology.com; s=default; h=References:In-Reply-To:Message-Id:Date:Subject :Cc:To:From:Sender:Reply-To:MIME-Version:Content-Type: Content-Transfer-Encoding:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:List-Id: List-Help:List-Unsubscribe:List-Subscribe:List-Post:List-Owner:List-Archive; bh=gFGYD1bYr6Ojyc6VNnGfpvgobWhvZ/Nx642j3A8D6VU=; b=flJr8gDmXr0F5JyuVHivVqz2I tcra0ZnZ1mDk+nBJwU3TBd8A0OTJPTb2XofUU/xy2VBaWhKENI1yueu01RMJbU59XVM4aklqtj4hj YO//NvvzerBStYGbTOjsk0PzLK7YceaMv7VUFbDYoE+pugra/Y1c2eoDcSqse3ugYM6dz2dXfoK6P FdPxN3q7cPp0vnRQJ2nhN/5uP+Y+pOWAromnvGRy6u0jRMJzPa4UE1l2vH5juK6+mOlQ7qeasC7Tw cckFiTjcpy7tSKnshw0Pblmr2PtUPjgqk2XFmgzeae1J0Yr2brrpqmn4U08/foBqq+2NDlNoUqj5h pIYT3vRgw==; Received: from 108-198-5-147.lightspeed.okcbok.sbcglobal.net ([108.198.5.147]:53590 helo=freyr.lechnology.com) by vern.gendns.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES128-SHA256:128) (Exim 4.89_1) (envelope-from ) id 1ecwis-00059q-5K; Sat, 20 Jan 2018 12:14:30 -0500 From: David Lechner To: linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org Subject: [PATCH v6 02/41] clk: davinci: New driver for davinci PLL clocks Date: Sat, 20 Jan 2018 11:13:41 -0600 Message-Id: <1516468460-4908-3-git-send-email-david@lechnology.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1516468460-4908-1-git-send-email-david@lechnology.com> References: <1516468460-4908-1-git-send-email-david@lechnology.com> X-AntiAbuse: This header was added to track abuse, please include it with any abuse report X-AntiAbuse: Primary Hostname - vern.gendns.com X-AntiAbuse: Original Domain - lists.infradead.org X-AntiAbuse: Originator/Caller UID/GID - [47 12] / [47 12] X-AntiAbuse: Sender Address Domain - lechnology.com X-Get-Message-Sender-Via: vern.gendns.com: authenticated_id: davidmain+lechnology.com/only user confirmed/virtual account not confirmed X-Authenticated-Sender: vern.gendns.com: davidmain@lechnology.com X-Source: X-Source-Args: X-Source-Dir: X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180120_171616_923182_954B4F2E X-CRM114-Status: GOOD ( 34.30 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Rutland , David Lechner , Kevin Hilman , Stephen Boyd , Michael Turquette , Sekhar Nori , linux-kernel@vger.kernel.org, Bartosz Golaszewski , Rob Herring , Adam Ford MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP This adds a new driver for mach-davinci PLL clocks. This is porting the code from arch/arm/mach-davinci/clock.c to the common clock framework. Additionally, it adds device tree support for these clocks. The ifeq ($(CONFIG_COMMON_CLK), y) in the Makefile is needed to prevent compile errors until the clock code in arch/arm/mach-davinci is removed. Note: although there are similar clocks for TI Keystone we are not able to share the code for a few reasons. The keystone clocks are device tree only and use legacy one-node-per-clock bindings. Also the register layouts are a bit different, which would add even more if/else mess to the keystone clocks. And the keystone PLL driver doesn't support setting clock rates. Signed-off-by: David Lechner --- v6 changes: - Added R: Sekhar Nori to MAINTAINERS - Split main PLL clock into oscdiv, prediv, pllout, postdiv and pllen clocks - Added min/max rate checking for pllout in set_rate - Added min/max PLLM value checking for pllout in set_rate - Fixed sysclk set_rate (checking GOSTAT and setting GOSET) - Added *_clk_info structs for passing controller-specific info - Added quirks for optional PREDIV and POSTDIV registers - Added quirk for DM355 broken PREDIV register - Added quirk for DM365 2x PLLM register - Handle unlocking PLL registers via CFGCHIP - Use pr_fmt macro MAINTAINERS | 7 + drivers/clk/Makefile | 1 + drivers/clk/davinci/Makefile | 5 + drivers/clk/davinci/pll.c | 813 +++++++++++++++++++++++++++++++++++++++++++ drivers/clk/davinci/pll.h | 118 +++++++ 5 files changed, 944 insertions(+) create mode 100644 drivers/clk/davinci/Makefile create mode 100644 drivers/clk/davinci/pll.c create mode 100644 drivers/clk/davinci/pll.h diff --git a/MAINTAINERS b/MAINTAINERS index 95c3fa1..55e0f64 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13552,6 +13552,13 @@ F: arch/arm/mach-davinci/ F: drivers/i2c/busses/i2c-davinci.c F: arch/arm/boot/dts/da850* +TI DAVINCI SERIES CLOCK DRIVER +M: David Lechner +R: Sekhar Nori +S: Maintained +F: Documentation/devicetree/bindings/clock/ti/davinci/ +F: drivers/clk/davinci/ + TI DAVINCI SERIES GPIO DRIVER M: Keerthy L: linux-gpio@vger.kernel.org diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index f7f761b..c865fd0 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/ obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/ obj-y += bcm/ obj-$(CONFIG_ARCH_BERLIN) += berlin/ +obj-$(CONFIG_ARCH_DAVINCI) += davinci/ obj-$(CONFIG_H8300) += h8300/ obj-$(CONFIG_ARCH_HISI) += hisilicon/ obj-y += imgtec/ diff --git a/drivers/clk/davinci/Makefile b/drivers/clk/davinci/Makefile new file mode 100644 index 0000000..d9673bd --- /dev/null +++ b/drivers/clk/davinci/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 + +ifeq ($(CONFIG_COMMON_CLK), y) +obj-y += pll.o +endif diff --git a/drivers/clk/davinci/pll.c b/drivers/clk/davinci/pll.c new file mode 100644 index 0000000..cef78a1 --- /dev/null +++ b/drivers/clk/davinci/pll.c @@ -0,0 +1,813 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * PLL clock driver for TI Davinci SoCs + * + * Copyright (C) 2018 David Lechner + * + * Based on arch/arm/mach-davinci/clock.c + * Copyright (C) 2006-2007 Texas Instruments. + * Copyright (C) 2008-2009 Deep Root Systems, LLC + */ + +#define pr_fmt(fmt) "%s: " fmt "\n", __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pll.h" + +#define MAX_NAME_SIZE 20 +#define OSCIN_CLK_NAME "oscin" + +#define REVID 0x000 +#define PLLCTL 0x100 +#define OCSEL 0x104 +#define PLLSECCTL 0x108 +#define PLLM 0x110 +#define PREDIV 0x114 +#define PLLDIV1 0x118 +#define PLLDIV2 0x11c +#define PLLDIV3 0x120 +#define OSCDIV 0x124 +#define POSTDIV 0x128 +#define BPDIV 0x12c +#define PLLCMD 0x138 +#define PLLSTAT 0x13c +#define ALNCTL 0x140 +#define DCHANGE 0x144 +#define CKEN 0x148 +#define CKSTAT 0x14c +#define SYSTAT 0x150 +#define PLLDIV4 0x160 +#define PLLDIV5 0x164 +#define PLLDIV6 0x168 +#define PLLDIV7 0x16c +#define PLLDIV8 0x170 +#define PLLDIV9 0x174 + +#define PLLCTL_PLLEN BIT(0) +#define PLLCTL_PLLPWRDN BIT(1) +#define PLLCTL_PLLRST BIT(3) +#define PLLCTL_PLLDIS BIT(4) +#define PLLCTL_PLLENSRC BIT(5) +#define PLLCTL_CLKMODE BIT(8) + +/* shared by most *DIV registers */ +#define DIV_RATIO_SHIFT 0 +#define DIV_RATIO_WIDTH 5 +#define DIV_ENABLE_SHIFT 15 + +#define PLLCMD_GOSET BIT(0) +#define PLLSTAT_GOSTAT BIT(0) + +#define CKEN_OBSCLK_SHIFT 1 +#define CKEN_AUXEN_SHIFT 0 + +/* + * OMAP-L138 system reference guide recommends a wait for 4 OSCIN/CLKIN + * cycles to ensure that the PLLC has switched to bypass mode. Delay of 1us + * ensures we are good for all > 4MHz OSCIN/CLKIN inputs. Typically the input + * is ~25MHz. Units are micro seconds. + */ +#define PLL_BYPASS_TIME 1 + +/* From OMAP-L138 datasheet table 6-4. Units are micro seconds */ +#define PLL_RESET_TIME 1 + +/* + * From OMAP-L138 datasheet table 6-4; assuming prediv = 1, sqrt(pllm) = 4 + * Units are micro seconds. + */ +#define PLL_LOCK_TIME 20 + +/** + * struct davinci_pll_clk - Main PLL clock (aka PLLOUT) + * @hw: clk_hw for the pll + * @base: Base memory address + * @pllm_min: The minimum allowable PLLM[PLLM] value + * @pllm_max: The maxiumum allowable PLLM[PLLM] value + * @pllm_mask: Bitmask for PLLM[PLLM] value + */ +struct davinci_pll_clk { + struct clk_hw hw; + void __iomem *base; + u32 pllm_min; + u32 pllm_max; + u32 pllm_mask; +}; + +#define to_davinci_pll_clk(_hw) \ + container_of((_hw), struct davinci_pll_clk, hw) + +static unsigned long davinci_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + unsigned long rate = parent_rate; + u32 mult; + + mult = readl(pll->base + PLLM) & pll->pllm_mask; + rate *= mult + 1; + + return rate; +} + +static int davinci_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + struct clk_hw *parent = req->best_parent_hw; + unsigned long parent_rate = req->best_parent_rate; + unsigned long rate = req->rate; + unsigned long best_rate, r; + u32 mult; + + /* there is a limited range of valid outputs (see datasheet) */ + if (rate < req->min_rate) + return -EINVAL; + + rate = min(rate, req->max_rate); + mult = rate / parent_rate; + best_rate = parent_rate * mult; + + /* easy case when there is no PREDIV */ + if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) { + if (best_rate < req->min_rate) + return -EINVAL; + + if (mult < pll->pllm_min || mult > pll->pllm_max) + return -EINVAL; + + req->rate = best_rate; + + return 0; + } + + /* see if the PREDIV clock can help us */ + best_rate = 0; + + for (mult = pll->pllm_min; mult <= pll->pllm_max; mult++) { + parent_rate = clk_hw_round_rate(parent, rate / mult); + r = parent_rate * mult; + if (r < req->min_rate) + continue; + if (r > rate || r > req->max_rate) + break; + if (r > best_rate) { + best_rate = r; + req->rate = best_rate; + req->best_parent_rate = parent_rate; + if (best_rate == rate) + break; + } + } + + return 0; +} + +static int davinci_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + u32 mult; + + mult = rate / parent_rate; + writel(mult - 1, pll->base + PLLM); + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int davinci_pll_debug_init(struct clk_hw *hw, struct dentry *dentry); +#else +#define davinci_pll_debug_init NULL +#endif + +static const struct clk_ops davinci_pll_ops = { + .recalc_rate = davinci_pll_recalc_rate, + .determine_rate = davinci_pll_determine_rate, + .set_rate = davinci_pll_set_rate, + .debug_init = davinci_pll_debug_init, +}; + +/* PLLM works differently on DM365 */ +static unsigned long dm365_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + unsigned long rate = parent_rate; + u32 mult; + + mult = readl(pll->base + PLLM) & pll->pllm_mask; + rate *= mult * 2; + + return rate; +} + +static const struct clk_ops dm365_pll_ops = { + .recalc_rate = dm365_pll_recalc_rate, + .debug_init = davinci_pll_debug_init, +}; + +/** + * davinci_pll_div_register - common *DIV clock implementation + * @name: the clock name + * @parent_name: the parent clock name + * @reg: the *DIV register + * @fixed: if true, the divider is a fixed value + * @flags: bitmap of CLK_* flags from clock-provider.h + */ +static struct clk *davinci_pll_div_register(const char *name, + const char *parent_name, + void __iomem *reg, + bool fixed, u32 flags) +{ + const char * const *parent_names = parent_name ? &parent_name : NULL; + int num_parents = parent_name ? 1 : 0; + const struct clk_ops *divider_ops = &clk_divider_ops; + struct clk_gate *gate; + struct clk_divider *divider; + struct clk *clk; + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->reg = reg; + gate->bit_idx = DIV_ENABLE_SHIFT; + + divider = kzalloc(sizeof(*divider), GFP_KERNEL); + if (!divider) { + kfree(gate); + return ERR_PTR(-ENOMEM); + } + + divider->reg = reg; + divider->shift = DIV_RATIO_SHIFT; + divider->width = DIV_RATIO_WIDTH; + + if (fixed) { + divider->flags |= CLK_DIVIDER_READ_ONLY; + divider_ops = &clk_divider_ro_ops; + } + + clk = clk_register_composite(NULL, name, parent_names, num_parents, + NULL, NULL, ÷r->hw, divider_ops, + &gate->hw, &clk_gate_ops, flags); + if (IS_ERR(clk)) { + kfree(divider); + kfree(gate); + } + + return clk; +} + +struct davinci_pllen_clk { + struct clk_hw hw; + void __iomem *base; +}; + +#define to_davinci_pllen_clk(_hw) \ + container_of((_hw), struct davinci_pllen_clk, hw) + +static const struct clk_ops davinci_pllen_ops = { + /* this clocks just uses the clock notification feature */ +}; + +/* + * The PLL has to be switched into bypass mode while we are chaning the rate, + * so we do that on the PLLEN clock since it is the end of the line. This will + * switch to bypass before any of the parent clocks (PREDIV, PLL, POSTDIV) are + * changed and will switch back to the PLL after the changes have been made. + */ +static int davinci_pllen_rate_change(struct notifier_block *nb, + unsigned long flags, void *data) +{ + struct clk_notifier_data *cnd = data; + struct clk_hw *hw = __clk_get_hw(cnd->clk); + struct davinci_pllen_clk *pll = to_davinci_pllen_clk(hw); + u32 ctrl; + + ctrl = readl(pll->base + PLLCTL); + + if (flags == PRE_RATE_CHANGE) { + /* Switch the PLL to bypass mode */ + ctrl &= ~(PLLCTL_PLLENSRC | PLLCTL_PLLEN); + writel(ctrl, pll->base + PLLCTL); + + udelay(PLL_BYPASS_TIME); + + /* Reset and enable PLL */ + ctrl &= ~(PLLCTL_PLLRST | PLLCTL_PLLDIS); + writel(ctrl, pll->base + PLLCTL); + } else { + udelay(PLL_RESET_TIME); + + /* Bring PLL out of reset */ + ctrl |= PLLCTL_PLLRST; + writel(ctrl, pll->base + PLLCTL); + + udelay(PLL_LOCK_TIME); + + /* Remove PLL from bypass mode */ + ctrl |= PLLCTL_PLLEN; + writel(ctrl, pll->base + PLLCTL); + } + + return NOTIFY_OK; +} + +static struct notifier_block davinci_pllen_notifier = { + .notifier_call = davinci_pllen_rate_change, +}; + +/** + * davinci_pll_clk_register - Register a PLL clock + * @info: The device-specific clock info + * @parent_name: The parent clock name + * @base: The PLL's memory region + * + * This creates a series of clocks that represent the PLL. + * + * OSCIN > [PREDIV >] PLL > [POSTDIV >] PLLEN + * + * - OSCIN is the parent clock (on secondary PLL, may come from primary PLL) + * - PREDIV and POSTDIV are optional (depends on the PLL controller) + * - PLL is the PLL output (aka PLLOUT) + * - PLLEN is the bypass multiplexer + * + * Returns: The PLLOUT clock or a negative error code. + */ +struct clk *davinci_pll_clk_register(const struct davinci_pll_clk_info *info, + const char *parent_name, + void __iomem *base) +{ + char prediv_name[MAX_NAME_SIZE]; + char pllout_name[MAX_NAME_SIZE]; + char postdiv_name[MAX_NAME_SIZE]; + char pllen_name[MAX_NAME_SIZE]; + struct clk_init_data init; + struct davinci_pll_clk *pllout; + struct davinci_pllen_clk *pllen; + struct clk *pllout_clk, *clk; + + if (info->flags & PLL_HAS_OSCIN) { + clk = clk_register_fixed_factor(NULL, OSCIN_CLK_NAME, + parent_name, 0, 1, 1); + if (IS_ERR(clk)) + return clk; + + parent_name = OSCIN_CLK_NAME; + } + + if (info->flags & PLL_HAS_PREDIV) { + bool fixed = info->flags & PLL_PREDIV_FIXED_DIV; + u32 flags = 0; + + snprintf(prediv_name, MAX_NAME_SIZE, "%s_prediv", info->name); + + if (info->flags & PLL_PREDIV_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + /* Some? DM355 chips don't correctly report the PREDIV value */ + if (info->flags & PLL_PREDIV_FIXED8) + clk = clk_register_fixed_factor(NULL, prediv_name, + parent_name, flags, 1, 8); + else + clk = davinci_pll_div_register(prediv_name, parent_name, + base + PREDIV, fixed, flags); + if (IS_ERR(clk)) + return clk; + + parent_name = prediv_name; + } + + /* Unlock writing to PLL registers */ + if (info->unlock_reg) { + struct regmap *cfgchip; + + cfgchip = syscon_regmap_lookup_by_compatible("ti,da830-cfgchip"); + if (IS_ERR(cfgchip)) + pr_warn("Failed to get CFGCHIP (%ld)", PTR_ERR(cfgchip)); + else + regmap_write_bits(cfgchip, info->unlock_reg, + info->unlock_mask, 0); + } + + pllout = kzalloc(sizeof(*pllout), GFP_KERNEL); + if (!pllout) + return ERR_PTR(-ENOMEM); + + snprintf(pllout_name, MAX_NAME_SIZE, "%s_pllout", info->name); + + init.name = pllout_name; + if (info->flags & PLL_PLLM_2X) + init.ops = &dm365_pll_ops; + else + init.ops = &davinci_pll_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = 0; + + if (info->flags & PLL_HAS_PREDIV) + init.flags |= CLK_SET_RATE_PARENT; + + pllout->hw.init = &init; + pllout->base = base; + pllout->pllm_mask = info->pllm_mask; + pllout->pllm_min = info->pllm_min; + pllout->pllm_max = info->pllm_max; + + pllout_clk = clk_register(NULL, &pllout->hw); + if (IS_ERR(pllout_clk)) { + kfree(pllout); + return pllout_clk; + } + + clk_hw_set_rate_range(&pllout->hw, info->pllout_min_rate, + info->pllout_max_rate); + + parent_name = pllout_name; + + if (info->flags & PLL_HAS_POSTDIV) { + bool fixed = info->flags & PLL_POSTDIV_FIXED_DIV; + u32 flags = CLK_SET_RATE_PARENT; + + snprintf(postdiv_name, MAX_NAME_SIZE, "%s_postdiv", info->name); + + if (info->flags & PLL_POSTDIV_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + clk = davinci_pll_div_register(postdiv_name, parent_name, + base + POSTDIV, fixed, flags); + if (IS_ERR(clk)) + return clk; + + parent_name = postdiv_name; + } + + pllen = kzalloc(sizeof(*pllout), GFP_KERNEL); + if (!pllen) + return ERR_PTR(-ENOMEM); + + snprintf(pllen_name, MAX_NAME_SIZE, "%s_pllen", info->name); + + init.name = pllen_name; + init.ops = &davinci_pllen_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT; + + pllen->hw.init = &init; + pllen->base = base; + + clk = clk_register(NULL, &pllen->hw); + if (IS_ERR(clk)) { + kfree(pllen); + return clk; + } + + clk_notifier_register(clk, &davinci_pllen_notifier); + + return pllout_clk; +} + +/** + * davinci_pll_auxclk_register - Register bypass clock (AUXCLK) + * @name: The clock name + * @base: The PLL memory region + */ +struct clk *davinci_pll_auxclk_register(const char *name, + void __iomem *base) +{ + return clk_register_gate(NULL, name, OSCIN_CLK_NAME, 0, base + CKEN, + CKEN_AUXEN_SHIFT, 0, NULL); +} + +/** + * davinci_pll_sysclkbp_clk_register - Register bypass divider clock (SYSCLKBP) + * @name: The clock name + * @base: The PLL memory region + */ +struct clk *davinci_pll_sysclkbp_clk_register(const char *name, + void __iomem *base) +{ + return clk_register_divider(NULL, name, OSCIN_CLK_NAME, 0, base + BPDIV, + DIV_RATIO_SHIFT, DIV_RATIO_WIDTH, + CLK_DIVIDER_READ_ONLY, NULL); +} + +/** + * davinci_pll_obsclk_register - Register oscillator divider clock (OBSCLK) + * @info: The clock info + * @base: The PLL memory region + */ +struct clk * +davinci_pll_obsclk_register(const struct davinci_pll_obsclk_info *info, + void __iomem *base) +{ + struct clk_mux *mux; + struct clk_gate *gate; + struct clk_divider *divider; + struct clk *clk; + u32 oscdiv; + + mux = kzalloc(sizeof(*mux), GFP_KERNEL); + if (!mux) + return ERR_PTR(-ENOMEM); + + mux->reg = base + OCSEL; + mux->table = info->table; + mux->mask = info->ocsrc_mask; + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) { + kfree(mux); + return ERR_PTR(-ENOMEM); + } + + gate->reg = base + CKEN; + gate->bit_idx = CKEN_OBSCLK_SHIFT; + + divider = kzalloc(sizeof(*divider), GFP_KERNEL); + if (!divider) { + kfree(gate); + kfree(mux); + return ERR_PTR(-ENOMEM); + } + + divider->reg = base + OSCDIV; + divider->shift = DIV_RATIO_SHIFT; + divider->width = DIV_RATIO_WIDTH; + + /* make sure divider is enabled just in case bootloader disabled it */ + oscdiv = readl(base + OSCDIV); + oscdiv |= BIT(DIV_ENABLE_SHIFT); + writel(oscdiv, base + OSCDIV); + + clk = clk_register_composite(NULL, info->name, info->parent_names, + info->num_parents, + &mux->hw, &clk_mux_ops, + ÷r->hw, &clk_divider_ops, + &gate->hw, &clk_gate_ops, 0); + if (IS_ERR(clk)) { + kfree(divider); + kfree(gate); + kfree(mux); + } + + return clk; +} + +/* The PLL SYSCLKn clocks have a mechanism for synchronizing rate changes. */ +static int davinci_pll_sysclk_rate_change(struct notifier_block *nb, + unsigned long flags, void *data) +{ + struct clk_notifier_data *cnd = data; + struct clk_hw *hw = __clk_get_hw(clk_get_parent(cnd->clk)); + struct davinci_pllen_clk *pll = to_davinci_pllen_clk(hw); + u32 pllcmd, pllstat; + + switch (flags) { + case POST_RATE_CHANGE: + /* apply the changes */ + pllcmd = readl(pll->base + PLLCMD); + pllcmd |= PLLCMD_GOSET; + writel(pllcmd, pll->base + PLLCMD); + /* fallthrough */ + case PRE_RATE_CHANGE: + /* Wait until for outstanding changes to take effect */ + do { + pllstat = readl(pll->base + PLLSTAT); + } while (pllstat & PLLSTAT_GOSTAT); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block davinci_pll_sysclk_notifier = { + .notifier_call = davinci_pll_sysclk_rate_change, +}; + +/** + * davinci_pll_sysclk_register - Register divider clocks (SYSCLKn) + * @info: The clock info + * @base: The PLL memory region + */ +struct clk * +davinci_pll_sysclk_register(const struct davinci_pll_sysclk_info *info, + void __iomem *base) +{ + const struct clk_ops *divider_ops = &clk_divider_ops; + struct clk_gate *gate; + struct clk_divider *divider; + struct clk *clk; + u32 reg; + u32 flags = 0; + + /* PLLDIVn registers are not entirely consecutive */ + if (info->id < 4) + reg = PLLDIV1 + 4 * (info->id - 1); + else + reg = PLLDIV4 + 4 * (info->id - 4); + + gate = kzalloc(sizeof(*gate), GFP_KERNEL); + if (!gate) + return ERR_PTR(-ENOMEM); + + gate->reg = base + reg; + gate->bit_idx = DIV_ENABLE_SHIFT; + + divider = kzalloc(sizeof(*divider), GFP_KERNEL); + if (!divider) { + kfree(gate); + return ERR_PTR(-ENOMEM); + } + + divider->reg = base + reg; + divider->shift = DIV_RATIO_SHIFT; + divider->width = info->ratio_width; + divider->flags = 0; + + if (info->flags & SYSCLK_FIXED_DIV) { + divider->flags |= CLK_DIVIDER_READ_ONLY; + divider_ops = &clk_divider_ro_ops; + } + + /* Only the ARM clock can change the parent PLL rate */ + if (info->flags & SYSCLK_ARM_RATE) + flags |= CLK_SET_RATE_PARENT; + + if (info->flags & SYSCLK_ALWAYS_ENABLED) + flags |= CLK_IS_CRITICAL; + + clk = clk_register_composite(NULL, info->name, &info->parent_name, 1, + NULL, NULL, ÷r->hw, divider_ops, + &gate->hw, &clk_gate_ops, flags); + if (IS_ERR(clk)) { + kfree(divider); + kfree(gate); + } + + clk_notifier_register(clk, &davinci_pll_sysclk_notifier); + + return clk; +} + +#ifdef CONFIG_OF +void of_davinci_pll_init(struct device_node *node, + const struct davinci_pll_clk_info *info, + const struct davinci_pll_obsclk_info *obsclk_info, + const struct davinci_pll_sysclk_info *div_info, + u8 max_sysclk_id) +{ + struct device_node *child; + const char *parent_name; + void __iomem *base; + struct clk *clk; + + base = of_iomap(node, 0); + if (!base) { + pr_err("ioremap failed"); + return; + } + + if (info->flags & PLL_HAS_OSCIN) + parent_name = of_clk_get_parent_name(node, 0); + else + parent_name = OSCIN_CLK_NAME; + + clk = davinci_pll_clk_register(info, parent_name, base); + if (IS_ERR(clk)) { + pr_err("failed to register %s (%ld)", info->name, PTR_ERR(clk)); + return; + } + + child = of_get_child_by_name(node, "pllout"); + if (of_device_is_available(child)) + of_clk_add_provider(child, of_clk_src_simple_get, clk); + of_node_put(child); + + child = of_get_child_by_name(node, "sysclk"); + if (of_device_is_available(child)) { + struct clk_onecell_data *clk_data; + + clk_data = clk_alloc_onecell_data(max_sysclk_id + 1); + if (!clk_data) + return; + + for (; div_info->name; div_info++) { + clk = davinci_pll_sysclk_register(div_info, base); + if (IS_ERR(clk)) + pr_warn("failed to register %s (%ld)", + div_info->name, PTR_ERR(clk)); + else + clk_data->clks[div_info->id] = clk; + } + of_clk_add_provider(child, of_clk_src_onecell_get, clk_data); + } + of_node_put(child); + + child = of_get_child_by_name(node, "auxclk"); + if (of_device_is_available(child)) { + char child_name[MAX_NAME_SIZE]; + + snprintf(child_name, MAX_NAME_SIZE, "%s_auxclk", info->name); + + clk = davinci_pll_auxclk_register(child_name, base); + if (IS_ERR(clk)) + pr_warn("failed to register %s (%ld)", child_name, + PTR_ERR(clk)); + else + of_clk_add_provider(child, of_clk_src_simple_get, clk); + } + of_node_put(child); + + child = of_get_child_by_name(node, "obsclk"); + if (of_device_is_available(child)) { + if (obsclk_info) + clk = davinci_pll_obsclk_register(obsclk_info, base); + else + clk = ERR_PTR(-EINVAL); + + if (IS_ERR(clk)) + pr_warn("failed to register obsclk (%ld)", PTR_ERR(clk)); + else + of_clk_add_provider(child, of_clk_src_simple_get, clk); + } + of_node_put(child); +} +#endif + +#ifdef CONFIG_DEBUG_FS +#include + +#define DEBUG_REG(n) \ +{ \ + .name = #n, \ + .offset = n, \ +} + +static const struct debugfs_reg32 davinci_pll_regs[] = { + DEBUG_REG(REVID), + DEBUG_REG(PLLCTL), + DEBUG_REG(OCSEL), + DEBUG_REG(PLLSECCTL), + DEBUG_REG(PLLM), + DEBUG_REG(PREDIV), + DEBUG_REG(PLLDIV1), + DEBUG_REG(PLLDIV2), + DEBUG_REG(PLLDIV3), + DEBUG_REG(OSCDIV), + DEBUG_REG(POSTDIV), + DEBUG_REG(BPDIV), + DEBUG_REG(PLLCMD), + DEBUG_REG(PLLSTAT), + DEBUG_REG(ALNCTL), + DEBUG_REG(DCHANGE), + DEBUG_REG(CKEN), + DEBUG_REG(CKSTAT), + DEBUG_REG(SYSTAT), + DEBUG_REG(PLLDIV4), + DEBUG_REG(PLLDIV5), + DEBUG_REG(PLLDIV6), + DEBUG_REG(PLLDIV7), + DEBUG_REG(PLLDIV8), + DEBUG_REG(PLLDIV9), +}; + +static int davinci_pll_debug_init(struct clk_hw *hw, struct dentry *dentry) +{ + struct davinci_pll_clk *pll = to_davinci_pll_clk(hw); + struct debugfs_regset32 *regset; + struct dentry *d; + + regset = kzalloc(sizeof(regset), GFP_KERNEL); + if (!regset) + return -ENOMEM; + + regset->regs = davinci_pll_regs; + regset->nregs = ARRAY_SIZE(davinci_pll_regs); + regset->base = pll->base; + + d = debugfs_create_regset32("registers", 0400, dentry, regset); + if (IS_ERR(d)) { + kfree(regset); + return PTR_ERR(d); + } + + return 0; +} +#endif diff --git a/drivers/clk/davinci/pll.h b/drivers/clk/davinci/pll.h new file mode 100644 index 0000000..eff688c --- /dev/null +++ b/drivers/clk/davinci/pll.h @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Clock driver for TI Davinci PSC controllers + * + * Copyright (C) 2018 David Lechner + */ + +#ifndef __CLK_DAVINCI_PLL_H___ +#define __CLK_DAVINCI_PLL_H___ + +#include +#include + +#define PLL_HAS_OSCIN BIT(0) /* register OSCIN clock */ +#define PLL_HAS_PREDIV BIT(1) /* has prediv before PLL */ +#define PLL_PREDIV_ALWAYS_ENABLED BIT(2) /* don't clear DEN bit */ +#define PLL_PREDIV_FIXED_DIV BIT(3) /* fixed divider value */ +#define PLL_HAS_POSTDIV BIT(4) /* has postdiv after PLL */ +#define PLL_POSTDIV_ALWAYS_ENABLED BIT(5) /* don't clear DEN bit */ +#define PLL_POSTDIV_FIXED_DIV BIT(6) /* fixed divider value */ +#define PLL_HAS_EXTCLKSRC BIT(7) /* has selectable bypass */ +#define PLL_PLLM_2X BIT(8) /* PLLM value is 2x (DM365) */ +#define PLL_PREDIV_FIXED8 BIT(9) /* DM355 quirk */ + +/** davinci_pll_clk_info - controller-specific PLL info + * @name: The name of the PLL + * @unlock_reg: Option CFGCHIP register for unlocking PLL + * @unlock_mask: Bitmask used with @unlock_reg + * @pllm_mask: Bitmask for PLLM[PLLM] value + * @pllm_min: Minimum allowable value for PLLM[PLLM] + * @pllm_max: Maximum allowable value for PLLM[PLLM] + * @pllout_min_rate: Minimum allowable rate for PLLOUT + * @pllout_max_rate: Maximum allowable rate for PLLOUT + * @flags: Bitmap of PLL_* flags. + */ +struct davinci_pll_clk_info { + const char *name; + u32 unlock_reg; + u32 unlock_mask; + u32 pllm_mask; + u32 pllm_min; + u32 pllm_max; + unsigned long pllout_min_rate; + unsigned long pllout_max_rate; + u32 flags; +}; + +#define SYSCLK_ARM_RATE BIT(0) /* Controls ARM rate */ +#define SYSCLK_ALWAYS_ENABLED BIT(1) /* Or bad things happen */ +#define SYSCLK_FIXED_DIV BIT(2) /* Fixed divider */ + +/** davinci_pll_sysclk_info - SYSCLKn-specific info + * @name: The name of the clock + * @parent_name: The name of the parent clock + * @id: "n" in "SYSCLKn" + * @ratio_width: Width (in bits) of RATIO in PLLDIVn register + * @flags: Bitmap of SYSCLK_* flags. + */ +struct davinci_pll_sysclk_info { + const char *name; + const char *parent_name; + u32 id; + u32 ratio_width; + u32 flags; +}; + +#define SYSCLK(i, n, p, w, f) \ +{ \ + .name = #n, \ + .parent_name = #p, \ + .id = (i), \ + .ratio_width = (w), \ + .flags = (f), \ +} + +/** davinci_pll_obsclk_info - OBSCLK-specific info + * @name: The name of the clock + * @parent_names: Array of names of the parent clocks + * @num_parents: Length of @parent_names + * @table: Array of values to write to OCSEL[OCSRC] cooresponding to + * @parent_names + * @ocsrc_mask: Bitmask for OCSEL[OCSRC] + */ +struct davinci_pll_obsclk_info { + const char *name; + const char * const *parent_names; + u8 num_parents; + u32 *table; + u32 ocsrc_mask; +}; + +struct clk; + +struct clk *davinci_pll_clk_register(const struct davinci_pll_clk_info *info, + const char *parent_name, + void __iomem *base); +struct clk *davinci_pll_auxclk_register(const char *name, + void __iomem *base); +struct clk *davinci_pll_sysclkbp_clk_register(const char *name, + void __iomem *base); +struct clk * +davinci_pll_obsclk_register(const struct davinci_pll_obsclk_info *info, + void __iomem *base); +struct clk * +davinci_pll_sysclk_register(const struct davinci_pll_sysclk_info *info, + void __iomem *base); + +#ifdef CONFIG_OF +struct device_node; + +void of_davinci_pll_init(struct device_node *node, + const struct davinci_pll_clk_info *info, + const struct davinci_pll_obsclk_info *obsclk_info, + const struct davinci_pll_sysclk_info *div_info, + u8 max_sysclk_id); +#endif + +#endif /* __CLK_DAVINCI_PLL_H___ */