From patchwork Wed Jun 24 10:41:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Anderson X-Patchwork-Id: 1316054 X-Patchwork-Delegate: uboot@andestech.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=85.214.62.61; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20161025 header.b=vCcp/x0i; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [85.214.62.61]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 49sKVv3RDCz9sQt for ; Wed, 24 Jun 2020 20:43:19 +1000 (AEST) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id E41FA82050; Wed, 24 Jun 2020 12:42:41 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="vCcp/x0i"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 00D8782030; Wed, 24 Jun 2020 12:42:09 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,SPF_HELO_NONE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.2 Received: from mail-qk1-x741.google.com (mail-qk1-x741.google.com [IPv6:2607:f8b0:4864:20::741]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id CD6F982008 for ; Wed, 24 Jun 2020 12:41:56 +0200 (CEST) Authentication-Results: phobos.denx.de; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=seanga2@gmail.com Received: by mail-qk1-x741.google.com with SMTP id j80so1361837qke.0 for ; Wed, 24 Jun 2020 03:41:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=+jJFAs8H7se0VVHrDL/CQtVboXYRneqX6bRJ4DRcZF0=; b=vCcp/x0ixjPPaOt+Da5rL1vBo6CW7ibd2NfVmO3JMPyrDeONU0UftohHxDk5LaNzmc dGPClAcXXFy65hwjYL2D33ukS1nbYgA0+LdS4sq5fCVyrcPA9J4zTTg9u98u/x4uAh4b NGWE3nSL67J6CV9LduZ7VU/2m2bAakL+QO4xP2ydcpDK64pPyDYZrrrjRmcE8ndboTRY HTlpEawa3dbAz6F4fajROlwO0GWnCs4/uEvvNLGJr0qmdVczyfN8C4ZTIKJe3KEUPPum L/AFVXAJGNirlBKPSrIV1h3FwsFxHBfCE78hiwv1uqGqBg1Dk3S1eKB3yA1bowNiiNHu 7jqg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=+jJFAs8H7se0VVHrDL/CQtVboXYRneqX6bRJ4DRcZF0=; b=XBe6zJPLPxhP2xv+O/3s4IVNik31CG19LJDgaEX7WtLEZ/3VtuNmHdtyx5tjUj90Fk eCi9gic/j4izlKYibi/J5RLAIbTh2R8qKUYUOlzyQjs6aLP8nRzU5Zq16L8dM6fe4a0i 7tZs6752xIQHWyJ+teMRu91cUHgOKsf5h1GUir7HJlTX5EqgMuNHp6e6JzJWrLY1n92S om+WGg01Ol4EM5FI5VUWjZCdF4QRF5+Ig7kHxZJoA+NaAO/Ul8GAdJ7SvvbhkZXGV7m0 pFiGeJo+9yHlSt9b7FrgW8ktsBxZCr6BxolPfYx3WEx3Cj8MdZmF2vG5u8FCguwdPA3w kZ6Q== X-Gm-Message-State: AOAM532vn09KRTPmJBmwhPezQeWGHTu3l4N9juvh/vM96eo0QjrVyeOg yW3ebIdGIrHqyfOW5TIbGOLpNEmNAc9Gww== X-Google-Smtp-Source: ABdhPJwTmR2VYbDMRgaueMMIwe8utARVOd7+NN2gTZsTFsEqGFTSr/D55U2hsrcLw4xTYzHj/T7vNA== X-Received: by 2002:a05:620a:218e:: with SMTP id g14mr8073211qka.430.1592995314612; Wed, 24 Jun 2020 03:41:54 -0700 (PDT) Received: from godwin.fios-router.home (pool-108-51-35-162.washdc.fios.verizon.net. [108.51.35.162]) by smtp.gmail.com with ESMTPSA id y40sm3438464qtc.29.2020.06.24.03.41.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 24 Jun 2020 03:41:53 -0700 (PDT) From: Sean Anderson To: u-boot@lists.denx.de Cc: Rick Chen , Bin Meng , Sean Anderson , Lukasz Majewski Subject: [PATCH v14 04/20] clk: Add K210 pll support Date: Wed, 24 Jun 2020 06:41:09 -0400 Message-Id: <20200624104125.363509-5-seanga2@gmail.com> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200624104125.363509-1-seanga2@gmail.com> References: <20200624104125.363509-1-seanga2@gmail.com> MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.30rc1 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.2 at phobos.denx.de X-Virus-Status: Clean This pll code is primarily based on the code from the kendryte standalone sdk in lib/drivers/sysctl.c. k210_pll_calc_config is roughly analogous to the algorithm used to set the pll frequency, but it has been completely rewritten to be fixed-point based. Signed-off-by: Sean Anderson CC: Lukasz Majewski --- Changes in v8: - Rename k210_pll_params to k210_pll_config to avoid a name conflict with the rest of the clock code Changes in v6: - Reformat code to reduce checkpatch errors Changes in v5: - Add function to register from a struct Changes in v4: - Rename the reference clock to "divider clock", and input clock to "reference clock" to match the upstream documentation. - Add a test for calc_params. This currently resides in test/dm, but perhaps it should be moved to its own directory. - Update MAINTAINERS - Update copyright - Lint Changes in v3: - Add an option to not include support for setting the pll rate. This saves around 1K in the final executable. - Remove udelays to suppress warnings - Bypass PLL after enabling, instead of before - Check if the PLL is enabled already before doing a reset - Fix bug with locked mask Changes in v2: - Rename driver to "k210_clk_pll" - Add additional in-line documentation on algorithm and PLLs - Restrict the range of internal VCO and reference frequencies - Don't load driver before relocation - Remove spurious references to mach-k210 drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/kendryte/Kconfig | 12 + drivers/clk/kendryte/Makefile | 1 + drivers/clk/kendryte/pll.c | 601 ++++++++++++++++++++++++++++++++++ include/kendryte/pll.h | 57 ++++ include/test/export.h | 16 + test/dm/Makefile | 1 + test/dm/k210_pll.c | 96 ++++++ 9 files changed, 786 insertions(+) create mode 100644 drivers/clk/kendryte/Kconfig create mode 100644 drivers/clk/kendryte/Makefile create mode 100644 drivers/clk/kendryte/pll.c create mode 100644 include/kendryte/pll.h create mode 100644 include/test/export.h create mode 100644 test/dm/k210_pll.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 8b8b719999..82cb1874e1 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -156,6 +156,7 @@ source "drivers/clk/analogbits/Kconfig" source "drivers/clk/at91/Kconfig" source "drivers/clk/exynos/Kconfig" source "drivers/clk/imx/Kconfig" +source "drivers/clk/kendryte/Kconfig" source "drivers/clk/meson/Kconfig" source "drivers/clk/mvebu/Kconfig" source "drivers/clk/owl/Kconfig" diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index e01783391d..d911954581 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_CLK_BOSTON) += clk_boston.o obj-$(CONFIG_CLK_EXYNOS) += exynos/ obj-$(CONFIG_$(SPL_TPL_)CLK_INTEL) += intel/ obj-$(CONFIG_CLK_HSDK) += clk-hsdk-cgu.o +obj-$(CONFIG_CLK_K210) += kendryte/ obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o obj-$(CONFIG_CLK_OWL) += owl/ obj-$(CONFIG_CLK_RENESAS) += renesas/ diff --git a/drivers/clk/kendryte/Kconfig b/drivers/clk/kendryte/Kconfig new file mode 100644 index 0000000000..7b69c8afaf --- /dev/null +++ b/drivers/clk/kendryte/Kconfig @@ -0,0 +1,12 @@ +config CLK_K210 + bool "Clock support for Kendryte K210" + depends on CLK && CLK_CCF + help + This enables support clock driver for Kendryte K210 platforms. + +config CLK_K210_SET_RATE + bool "Enable setting the Kendryte K210 PLL rate" + depends on CLK_K210 + help + Add functionality to calculate new rates for K210 PLLs. Enabling this + feature adds around 1K to U-Boot's final size. diff --git a/drivers/clk/kendryte/Makefile b/drivers/clk/kendryte/Makefile new file mode 100644 index 0000000000..c56d93ea1c --- /dev/null +++ b/drivers/clk/kendryte/Makefile @@ -0,0 +1 @@ +obj-y += pll.o diff --git a/drivers/clk/kendryte/pll.c b/drivers/clk/kendryte/pll.c new file mode 100644 index 0000000000..19e358856a --- /dev/null +++ b/drivers/clk/kendryte/pll.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2019-20 Sean Anderson + */ +#define LOG_CATEGORY UCLASS_CLK +#include + +#include +/* For DIV_ROUND_DOWN_ULL, defined in linux/kernel.h */ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLK_K210_PLL "k210_clk_pll" + +#ifdef CONFIG_CLK_K210_SET_RATE +static int k210_pll_enable(struct clk *clk); +static int k210_pll_disable(struct clk *clk); + +/* + * The PLL included with the Kendryte K210 appears to be a True Circuits, Inc. + * General-Purpose PLL. The logical layout of the PLL with internal feedback is + * approximately the following: + * + * +---------------+ + * |reference clock| + * +---------------+ + * | + * v + * +--+ + * |/r| + * +--+ + * | + * v + * +-------------+ + * |divided clock| + * +-------------+ + * | + * v + * +--------------+ + * |phase detector|<---+ + * +--------------+ | + * | | + * v +--------------+ + * +---+ |feedback clock| + * |VCO| +--------------+ + * +---+ ^ + * | +--+ | + * +--->|/f|---+ + * | +--+ + * v + * +---+ + * |/od| + * +---+ + * | + * v + * +------+ + * |output| + * +------+ + * + * The k210 PLLs have three factors: r, f, and od. Because of the feedback mode, + * the effect of the division by f is to multiply the input frequency. The + * equation for the output rate is + * rate = (rate_in * f) / (r * od). + * Moving knowns to one side of the equation, we get + * rate / rate_in = f / (r * od) + * Rearranging slightly, + * abs_error = abs((rate / rate_in) - (f / (r * od))). + * To get relative, error, we divide by the expected ratio + * error = abs((rate / rate_in) - (f / (r * od))) / (rate / rate_in). + * Simplifying, + * error = abs(1 - f / (r * od)) / (rate / rate_in) + * error = abs(1 - (f * rate_in) / (r * od * rate)) + * Using the constants ratio = rate / rate_in and inv_ratio = rate_in / rate, + * error = abs((f * inv_ratio) / (r * od) - 1) + * This is the error used in evaluating parameters. + * + * r and od are four bits each, while f is six bits. Because r and od are + * multiplied together, instead of the full 256 values possible if both bits + * were used fully, there are only 97 distinct products. Combined with f, there + * are 6208 theoretical settings for the PLL. However, most of these settings + * can be ruled out immediately because they do not have the correct ratio. + * + * In addition to the constraint of approximating the desired ratio, parameters + * must also keep internal pll frequencies within acceptable ranges. The divided + * clock's minimum and maximum frequencies have a ratio of around 128. This + * leaves fairly substantial room to work with, especially since the only + * affected parameter is r. The VCO's minimum and maximum frequency have a ratio + * of 5, which is considerably more restrictive. + * + * The r and od factors are stored in a table. This is to make it easy to find + * the next-largest product. Some products have multiple factorizations, but + * only when one factor has at least a 2.5x ratio to the factors of the other + * factorization. This is because any smaller ratio would not make a difference + * when ensuring the VCO's frequency is within spec. + * + * Throughout the calculation function, fixed point arithmetic is used. Because + * the range of rate and rate_in may be up to 1.75 GHz, or around 2^30, 64-bit + * 32.32 fixed-point numbers are used to represent ratios. In general, to + * implement division, the numerator is first multiplied by 2^32. This gives a + * result where the whole number part is in the upper 32 bits, and the fraction + * is in the lower 32 bits. + * + * In general, rounding is done to the closest integer. This helps find the best + * approximation for the ratio. Rounding in one direction (e.g down) could cause + * the function to miss a better ratio with one of the parameters increased by + * one. + */ + +/* + * The factors table was generated with the following python code: + * + * def p(x, y): + * return (1.0*x/y > 2.5) or (1.0*y/x > 2.5) + * + * factors = {} + * for i in range(1, 17): + * for j in range(1, 17): + * fs = factors.get(i*j) or [] + * if fs == [] or all([ + * (p(i, x) and p(i, y)) or (p(j, x) and p(j, y)) + * for (x, y) in fs]): + * fs.append((i, j)) + * factors[i*j] = fs + * + * for k, l in sorted(factors.items()): + * for v in l: + * print("PACK(%s, %s)," % v) + */ +#define PACK(r, od) (((((r) - 1) & 0xF) << 4) | (((od) - 1) & 0xF)) +#define UNPACK_R(val) ((((val) >> 4) & 0xF) + 1) +#define UNPACK_OD(val) (((val) & 0xF) + 1) +static const u8 factors[] = { + PACK(1, 1), + PACK(1, 2), + PACK(1, 3), + PACK(1, 4), + PACK(1, 5), + PACK(1, 6), + PACK(1, 7), + PACK(1, 8), + PACK(1, 9), + PACK(3, 3), + PACK(1, 10), + PACK(1, 11), + PACK(1, 12), + PACK(3, 4), + PACK(1, 13), + PACK(1, 14), + PACK(1, 15), + PACK(3, 5), + PACK(1, 16), + PACK(4, 4), + PACK(2, 9), + PACK(2, 10), + PACK(3, 7), + PACK(2, 11), + PACK(2, 12), + PACK(5, 5), + PACK(2, 13), + PACK(3, 9), + PACK(2, 14), + PACK(2, 15), + PACK(2, 16), + PACK(3, 11), + PACK(5, 7), + PACK(3, 12), + PACK(3, 13), + PACK(4, 10), + PACK(3, 14), + PACK(4, 11), + PACK(3, 15), + PACK(3, 16), + PACK(7, 7), + PACK(5, 10), + PACK(4, 13), + PACK(6, 9), + PACK(5, 11), + PACK(4, 14), + PACK(4, 15), + PACK(7, 9), + PACK(4, 16), + PACK(5, 13), + PACK(6, 11), + PACK(5, 14), + PACK(6, 12), + PACK(5, 15), + PACK(7, 11), + PACK(6, 13), + PACK(5, 16), + PACK(9, 9), + PACK(6, 14), + PACK(8, 11), + PACK(6, 15), + PACK(7, 13), + PACK(6, 16), + PACK(7, 14), + PACK(9, 11), + PACK(10, 10), + PACK(8, 13), + PACK(7, 15), + PACK(9, 12), + PACK(10, 11), + PACK(7, 16), + PACK(9, 13), + PACK(8, 15), + PACK(11, 11), + PACK(9, 14), + PACK(8, 16), + PACK(10, 13), + PACK(11, 12), + PACK(9, 15), + PACK(10, 14), + PACK(11, 13), + PACK(9, 16), + PACK(10, 15), + PACK(11, 14), + PACK(12, 13), + PACK(10, 16), + PACK(11, 15), + PACK(12, 14), + PACK(13, 13), + PACK(11, 16), + PACK(12, 15), + PACK(13, 14), + PACK(12, 16), + PACK(13, 15), + PACK(14, 14), + PACK(13, 16), + PACK(14, 15), + PACK(14, 16), + PACK(15, 15), + PACK(15, 16), + PACK(16, 16), +}; + +TEST_STATIC int k210_pll_calc_config(u32 rate, u32 rate_in, + struct k210_pll_config *best) +{ + int i; + s64 error, best_error; + u64 ratio, inv_ratio; /* fixed point 32.32 ratio of the rates */ + u64 max_r; + u64 r, f, od; + + /* + * Can't go over 1.75 GHz or under 21.25 MHz due to limitations on the + * VCO frequency. These are not the same limits as below because od can + * reduce the output frequency by 16. + */ + if (rate > 1750000000 || rate < 21250000) + return -EINVAL; + + /* Similar restrictions on the input rate */ + if (rate_in > 1750000000 || rate_in < 13300000) + return -EINVAL; + + ratio = DIV_ROUND_CLOSEST_ULL((u64)rate << 32, rate_in); + inv_ratio = DIV_ROUND_CLOSEST_ULL((u64)rate_in << 32, rate); + /* Can't increase by more than 64 or reduce by more than 256 */ + if (rate > rate_in && ratio > (64ULL << 32)) + return -EINVAL; + else if (rate <= rate_in && inv_ratio > (256ULL << 32)) + return -EINVAL; + + /* + * The divided clock (rate_in / r) must stay between 1.75 GHz and 13.3 + * MHz. There is no minimum, since the only way to get a higher input + * clock than 26 MHz is to use a clock generated by a PLL. Because PLLs + * cannot output frequencies greater than 1.75 GHz, the minimum would + * never be greater than one. + */ + max_r = DIV_ROUND_DOWN_ULL(rate_in, 13300000); + + /* Variables get immediately incremented, so start at -1th iteration */ + i = -1; + f = 0; + r = 0; + od = 0; + best_error = S64_MAX; + error = best_error; + /* do-while here so we always try at least one ratio */ + do { + /* + * Whether we swapped r and od while enforcing frequency limits + */ + bool swapped = false; + u64 last_od = od; + u64 last_r = r; + + /* + * Try the next largest value for f (or r and od) and + * recalculate the other parameters based on that + */ + if (rate > rate_in) { + /* + * Skip factors of the same product if we already tried + * out that product + */ + do { + i++; + r = UNPACK_R(factors[i]); + od = UNPACK_OD(factors[i]); + } while (i + 1 < ARRAY_SIZE(factors) && + r * od == last_r * last_od); + + /* Round close */ + f = (r * od * ratio + BIT(31)) >> 32; + if (f > 64) + f = 64; + } else { + u64 tmp = ++f * inv_ratio; + bool round_up = !!(tmp & BIT(31)); + u32 goal = (tmp >> 32) + round_up; + u32 err, last_err; + + /* Get the next r/od pair in factors */ + while (r * od < goal && i + 1 < ARRAY_SIZE(factors)) { + i++; + r = UNPACK_R(factors[i]); + od = UNPACK_OD(factors[i]); + } + + /* + * This is a case of double rounding. If we rounded up + * above, we need to round down (in cases of ties) here. + * This prevents off-by-one errors resulting from + * choosing X+2 over X when X.Y rounds up to X+1 and + * there is no r * od = X+1. For the converse, when X.Y + * is rounded down to X, we should choose X+1 over X-1. + */ + err = abs(r * od - goal); + last_err = abs(last_r * last_od - goal); + if (last_err < err || (round_up && last_err == err)) { + i--; + r = last_r; + od = last_od; + } + } + + /* + * Enforce limits on internal clock frequencies. If we + * aren't in spec, try swapping r and od. If everything is + * in-spec, calculate the relative error. + */ + while (true) { + /* + * Whether the intermediate frequencies are out-of-spec + */ + bool out_of_spec = false; + + if (r > max_r) { + out_of_spec = true; + } else { + /* + * There is no way to only divide once; we need + * to examine the frequency with and without the + * effect of od. + */ + u64 vco = DIV_ROUND_CLOSEST_ULL(rate_in * f, r); + + if (vco > 1750000000 || vco < 340000000) + out_of_spec = true; + } + + if (out_of_spec) { + if (!swapped) { + u64 tmp = r; + + r = od; + od = tmp; + swapped = true; + continue; + } else { + /* + * Try looking ahead to see if there are + * additional factors for the same + * product. + */ + if (i + 1 < ARRAY_SIZE(factors)) { + u64 new_r, new_od; + + i++; + new_r = UNPACK_R(factors[i]); + new_od = UNPACK_OD(factors[i]); + if (r * od == new_r * new_od) { + r = new_r; + od = new_od; + swapped = false; + continue; + } + i--; + } + break; + } + } + + error = DIV_ROUND_CLOSEST_ULL(f * inv_ratio, r * od); + /* The lower 16 bits are spurious */ + error = abs((error - BIT(32))) >> 16; + + if (error < best_error) { + best->r = r; + best->f = f; + best->od = od; + best_error = error; + } + break; + } + } while (f < 64 && i + 1 < ARRAY_SIZE(factors) && error != 0); + + if (best_error == S64_MAX) + return -EINVAL; + + log_debug("best error %lld\n", best_error); + return 0; +} + +static ulong k210_pll_set_rate(struct clk *clk, ulong rate) +{ + int err; + long long rate_in = clk_get_parent_rate(clk); + struct k210_pll_config config = {}; + struct k210_pll *pll = to_k210_pll(clk); + u32 reg; + + if (rate_in < 0) + return rate_in; + + log_debug("Calculating parameters with rate=%lu and rate_in=%lld\n", + rate, rate_in); + err = k210_pll_calc_config(rate, rate_in, &config); + if (err) + return err; + log_debug("Got r=%u f=%u od=%u\n", config.r, config.f, config.od); + + /* + * Don't use clk_disable as it might not actually disable the pll due to + * refcounting + */ + k210_pll_disable(clk); + + reg = readl(pll->reg); + reg &= ~K210_PLL_CLKR + & ~K210_PLL_CLKF + & ~K210_PLL_CLKOD + & ~K210_PLL_BWADJ; + reg |= FIELD_PREP(K210_PLL_CLKR, config.r - 1) + | FIELD_PREP(K210_PLL_CLKF, config.f - 1) + | FIELD_PREP(K210_PLL_CLKOD, config.od - 1) + | FIELD_PREP(K210_PLL_BWADJ, config.f - 1); + writel(reg, pll->reg); + + err = k210_pll_enable(clk); + if (err) + return err; + + serial_setbrg(); + return clk_get_rate(clk); +} +#endif /* CONFIG_CLK_K210_SET_RATE */ + +static ulong k210_pll_get_rate(struct clk *clk) +{ + long long rate_in = clk_get_parent_rate(clk); + struct k210_pll *pll = to_k210_pll(clk); + u64 r, f, od; + u32 reg = readl(pll->reg); + + if (rate_in < 0 || (reg & K210_PLL_BYPASS)) + return rate_in; + + if (!(reg & K210_PLL_PWRD)) + return 0; + + r = FIELD_GET(K210_PLL_CLKR, reg) + 1; + f = FIELD_GET(K210_PLL_CLKF, reg) + 1; + od = FIELD_GET(K210_PLL_CLKOD, reg) + 1; + + return DIV_ROUND_DOWN_ULL(((u64)rate_in) * f, r * od); +} + +/* + * Wait for the PLL to be locked. If the PLL is not locked, try clearing the + * slip before retrying + */ +static void k210_pll_waitfor_lock(struct k210_pll *pll) +{ + u32 mask = GENMASK(pll->width - 1, 0) << pll->shift; + + while (true) { + u32 reg = readl(pll->lock); + + if ((reg & mask) == mask) + break; + + reg |= BIT(pll->shift + K210_PLL_CLEAR_SLIP); + writel(reg, pll->lock); + } +} + +/* Adapted from sysctl_pll_enable */ +static int k210_pll_enable(struct clk *clk) +{ + struct k210_pll *pll = to_k210_pll(clk); + u32 reg = readl(pll->reg); + + if ((reg | K210_PLL_PWRD) && !(reg | K210_PLL_RESET)) + return 0; + + reg |= K210_PLL_PWRD; + writel(reg, pll->reg); + + /* Ensure reset is low before asserting it */ + reg &= ~K210_PLL_RESET; + writel(reg, pll->reg); + reg |= K210_PLL_RESET; + writel(reg, pll->reg); + nop(); + nop(); + reg &= ~K210_PLL_RESET; + writel(reg, pll->reg); + + k210_pll_waitfor_lock(pll); + + reg &= ~K210_PLL_BYPASS; + writel(reg, pll->reg); + + return 0; +} + +static int k210_pll_disable(struct clk *clk) +{ + struct k210_pll *pll = to_k210_pll(clk); + u32 reg = readl(pll->reg); + + /* + * Bypassing before powering off is important so child clocks don't stop + * working. This is especially important for pll0, the indirect parent + * of the cpu clock. + */ + reg |= K210_PLL_BYPASS; + writel(reg, pll->reg); + + reg &= ~K210_PLL_PWRD; + writel(reg, pll->reg); + return 0; +} + +const struct clk_ops k210_pll_ops = { + .get_rate = k210_pll_get_rate, +#ifdef CONFIG_CLK_K210_SET_RATE + .set_rate = k210_pll_set_rate, +#endif + .enable = k210_pll_enable, + .disable = k210_pll_disable, +}; + +struct clk *k210_register_pll_struct(const char *name, const char *parent_name, + struct k210_pll *pll) +{ + int ret; + struct clk *clk = &pll->clk; + + ret = clk_register(clk, CLK_K210_PLL, name, parent_name); + if (ret) + return ERR_PTR(ret); + return clk; +} + +struct clk *k210_register_pll(const char *name, const char *parent_name, + void __iomem *reg, void __iomem *lock, u8 shift, + u8 width) +{ + struct clk *clk; + struct k210_pll *pll; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + pll->reg = reg; + pll->lock = lock; + pll->shift = shift; + pll->width = width; + + clk = k210_register_pll_struct(name, parent_name, pll); + if (IS_ERR(clk)) + kfree(pll); + return clk; +} + +U_BOOT_DRIVER(k210_pll) = { + .name = CLK_K210_PLL, + .id = UCLASS_CLK, + .ops = &k210_pll_ops, +}; diff --git a/include/kendryte/pll.h b/include/kendryte/pll.h new file mode 100644 index 0000000000..c8e3200799 --- /dev/null +++ b/include/kendryte/pll.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2019-20 Sean Anderson + */ +#ifndef K210_PLL_H +#define K210_PLL_H + +#include +#include + +#define K210_PLL_CLKR GENMASK(3, 0) +#define K210_PLL_CLKF GENMASK(9, 4) +#define K210_PLL_CLKOD GENMASK(13, 10) /* Output Divider */ +#define K210_PLL_BWADJ GENMASK(19, 14) /* BandWidth Adjust */ +#define K210_PLL_RESET BIT(20) +#define K210_PLL_PWRD BIT(21) /* PoWeReD */ +#define K210_PLL_INTFB BIT(22) /* Internal FeedBack */ +#define K210_PLL_BYPASS BIT(23) +#define K210_PLL_TEST BIT(24) +#define K210_PLL_EN BIT(25) +#define K210_PLL_TEST_EN BIT(26) + +#define K210_PLL_LOCK 0 +#define K210_PLL_CLEAR_SLIP 2 +#define K210_PLL_TEST_OUT 3 + +struct k210_pll { + struct clk clk; + void __iomem *reg; /* Base PLL register */ + void __iomem *lock; /* Common PLL lock register */ + u8 shift; /* Offset of bits in lock register */ + u8 width; /* Width of lock bits to test against */ +}; + +#define to_k210_pll(_clk) container_of(_clk, struct k210_pll, clk) + +struct k210_pll_config { + u8 r; + u8 f; + u8 od; +}; + +#ifdef CONFIG_UNIT_TEST +TEST_STATIC int k210_pll_calc_config(u32 rate, u32 rate_in, + struct k210_pll_config *best); +#define nop() +#endif + +extern const struct clk_ops k210_pll_ops; + +struct clk *k210_register_pll_struct(const char *name, const char *parent_name, + struct k210_pll *pll); +struct clk *k210_register_pll(const char *name, const char *parent_name, + void __iomem *reg, void __iomem *lock, u8 shift, + u8 width); + +#endif /* K210_PLL_H */ diff --git a/include/test/export.h b/include/test/export.h new file mode 100644 index 0000000000..afc755a8ff --- /dev/null +++ b/include/test/export.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Sean Anderson + */ + +#ifndef TEST_EXPORT_H +#define TEST_EXPORT_H + +/* Declare something static, unless we are doing unit tests */ +#ifdef CONFIG_UNIT_TEST +#define TEST_STATIC +#else +#define TEST_STATIC static +#endif + +#endif /* TEST_EXPORT_H */ diff --git a/test/dm/Makefile b/test/dm/Makefile index 6c18fd04ce..5094a7866a 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -73,4 +73,5 @@ obj-$(CONFIG_DMA) += dma.o obj-$(CONFIG_DM_MDIO) += mdio.o obj-$(CONFIG_DM_MDIO_MUX) += mdio_mux.o obj-$(CONFIG_DM_RNG) += rng.o +obj-$(CONFIG_CLK_K210_SET_RATE) += k210_pll.o endif diff --git a/test/dm/k210_pll.c b/test/dm/k210_pll.c new file mode 100644 index 0000000000..54764f269c --- /dev/null +++ b/test/dm/k210_pll.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 Sean Anderson + */ + +#include +/* For DIV_ROUND_DOWN_ULL, defined in linux/kernel.h */ +#include +#include +#include +#include + +static int dm_test_k210_pll_calc_config(u32 rate, u32 rate_in, + struct k210_pll_config *best) +{ + u64 f, r, od, max_r, inv_ratio; + s64 error, best_error; + + best_error = S64_MAX; + error = best_error; + max_r = min(16ULL, DIV_ROUND_DOWN_ULL(rate_in, 13300000)); + inv_ratio = DIV_ROUND_CLOSEST_ULL((u64)rate_in << 32, rate); + + /* Brute force it */ + for (r = 1; r <= max_r; r++) { + for (f = 1; f <= 64; f++) { + for (od = 1; od <= 16; od++) { + u64 vco = DIV_ROUND_CLOSEST_ULL(rate_in * f, r); + + if (vco > 1750000000 || vco < 340000000) + continue; + + error = DIV_ROUND_CLOSEST_ULL(f * inv_ratio, + r * od); + /* The lower 16 bits are spurious */ + error = abs((error - BIT(32))) >> 16; + if (error < best_error) { + best->r = r; + best->f = f; + best->od = od; + best_error = error; + } + } + } + } + + if (best_error == S64_MAX) + return -EINVAL; + return 0; +} + +static int dm_test_k210_pll_compare(struct k210_pll_config *ours, + struct k210_pll_config *theirs) +{ + return (u32)ours->f * theirs->r * theirs->od != + (u32)theirs->f * ours->r * ours->od; +} + +static int dm_test_k210_pll(struct unit_test_state *uts) +{ + struct k210_pll_config ours, theirs; + + /* General range checks */ + ut_asserteq(-EINVAL, k210_pll_calc_config(0, 26000000, &theirs)); + ut_asserteq(-EINVAL, k210_pll_calc_config(390000000, 0, &theirs)); + ut_asserteq(-EINVAL, k210_pll_calc_config(2000000000, 26000000, + &theirs)); + ut_asserteq(-EINVAL, k210_pll_calc_config(390000000, 2000000000, + &theirs)); + ut_asserteq(-EINVAL, k210_pll_calc_config(1500000000, 20000000, + &theirs)); + + /* Verify we get the same output with brute-force */ + ut_assertok(dm_test_k210_pll_calc_config(390000000, 26000000, &ours)); + ut_assertok(k210_pll_calc_config(390000000, 26000000, &theirs)); + ut_assertok(dm_test_k210_pll_compare(&ours, &theirs)); + + ut_assertok(dm_test_k210_pll_calc_config(26000000, 390000000, &ours)); + ut_assertok(k210_pll_calc_config(26000000, 390000000, &theirs)); + ut_assertok(dm_test_k210_pll_compare(&ours, &theirs)); + + ut_assertok(dm_test_k210_pll_calc_config(400000000, 26000000, &ours)); + ut_assertok(k210_pll_calc_config(400000000, 26000000, &theirs)); + ut_assertok(dm_test_k210_pll_compare(&ours, &theirs)); + + ut_assertok(dm_test_k210_pll_calc_config(27000000, 26000000, &ours)); + ut_assertok(k210_pll_calc_config(27000000, 26000000, &theirs)); + ut_assertok(dm_test_k210_pll_compare(&ours, &theirs)); + + ut_assertok(dm_test_k210_pll_calc_config(26000000, 27000000, &ours)); + ut_assertok(k210_pll_calc_config(26000000, 27000000, &theirs)); + ut_assertok(dm_test_k210_pll_compare(&ours, &theirs)); + + return 0; +} +DM_TEST(dm_test_k210_pll, 0);