Message ID | 20221023145653.177234-6-aidanmacdonald.0x0@gmail.com |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:6687:0:0:0:0:0 with SMTP id l7csp28316wru; Sun, 23 Oct 2022 08:02:08 -0700 (PDT) X-Google-Smtp-Source: AMsMyM66QpTgiqsthqajS0ZBRyraTz2vYKDnp2BNVEC1UmyDa0ixh1xFh66eL62adZ1ZOAZU5UdH X-Received: by 2002:a17:907:6e93:b0:78d:dff1:71e3 with SMTP id sh19-20020a1709076e9300b0078ddff171e3mr22701715ejc.94.1666537328665; Sun, 23 Oct 2022 08:02:08 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1666537328; cv=none; d=google.com; s=arc-20160816; b=YB2PI2LL/jO+O15wGK91cEJ2IaSdUjRA3Tyyyu8OwCqyxW3iMMlhZSpt2GqTRvn2LL n6+ewp0SX0LzStJOibPHz/IGf7BsQq7nFa7lrEryEDiO8x+LZ08yeJHPh4DP49IPMz8j uwFkBw/bov2Cw+F0KC3p3Qw1t8WGMLhnjHUp7h5H1BClmAXIBhjnqCrndvpunbHhi24e yGWBUHoQgArPPrwa0bMNeS//kam0LR9gEGhafyWlBiC4mdRozUPfY5vh+V4TjF4ITX0T vugfh87lDx2JYUb5NCUyqyXb8xK6OWyf17kUowfVVJWClzNGaow7evRA1sFUz/t1QGf5 5cLA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=rFj+0g9zC945JfgR9vxO7Ms2xSVk7a3nRyoILt5SjhM=; b=nsR2aoLy+2JfbuMsz9CHJUr8pBXpkvY/QsHahofwkeuXkT/m2RiZt5HsAhALTeeo1m lpvIuPeFTcJtIJytr0SsuXl+LMV6mdFQ/3Yd32mBC5FltMbHtMiPVjaggAA5mgUJ5diL fWNF21/7VakF6tRPipJbTQXRdsl+mtg+OERR2eJTlspPCCSj9zPJ3fCPJ4vR4S8OULWo 2ZaXH7CD8YfvSqZDI1mj6IFIqYE4pZQmMYaxorVieh8iStvHHmjB0SZb8J058v0aoHll 2LHxbHON21ei6Pqv1jACv1lnmF+N9UTBTn6y1Ki3sHzlf21G7VbK38IlEzwRO3mNV6qL OU0w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=HjAWWLBr; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id y10-20020a50eb0a000000b00458ab54baa5si21676039edp.503.2022.10.23.08.01.44; Sun, 23 Oct 2022 08:02:08 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@gmail.com header.s=20210112 header.b=HjAWWLBr; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230388AbiJWO5Y (ORCPT <rfc822;pwkd43@gmail.com> + 99 others); Sun, 23 Oct 2022 10:57:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35706 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229649AbiJWO5M (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Sun, 23 Oct 2022 10:57:12 -0400 Received: from mail-wm1-x32d.google.com (mail-wm1-x32d.google.com [IPv6:2a00:1450:4864:20::32d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E44B35C97B; Sun, 23 Oct 2022 07:57:09 -0700 (PDT) Received: by mail-wm1-x32d.google.com with SMTP id az22-20020a05600c601600b003c6b72797fdso5039358wmb.5; Sun, 23 Oct 2022 07:57:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=rFj+0g9zC945JfgR9vxO7Ms2xSVk7a3nRyoILt5SjhM=; b=HjAWWLBr2kEf7p1VaqiGbzqwOt0zJ6cdDBbwqgi7VVAnNs2J4k0wf7oVKh6hAFAGRP e0KQh+W/DzAfb4/8yyM3CEPegwqICG2RfnumnGcvadIWjucdvNBmEUOFKz/1qHXyuz6n siYeaEx0ijMQDm/XfFdQ9+6zj9jU7hq4g45OrgvY7iTVKznU+mesB1MKw7QwLxVza21C XG1zzw5H34g643YpeeIM/Lg12lGsS30Hyu6DmJ+aTZUUPdy3HMZqyNIVdn+G/HBUnMfW aLkPitcfjsbHboG5wQVHVnIiZ+s3rrliVKV6E2au2RA7RWOAyP2ypUICizEtHVWp3Pg+ 1WNw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=rFj+0g9zC945JfgR9vxO7Ms2xSVk7a3nRyoILt5SjhM=; b=ocGHGmekFKUX2IKcKmuwCDrckEBMYaflPdbRQUfSO29ODwotmfX7K17M0MhfhqxYV3 TTvMpa3OUWNoaq9Ck/QuKJW5aRFI35IbcvKvTQWMuu9euajeqKopgFP2MbzJw/HAkh7z kPUpT0onV2cAHA2SRe+FPCakslb9lEWLiOzrpGgEUyppb/krNmTzMOkrvHWXL8nelQKQ 8E3MRgqsVddBLdV7b9ChTjsv/YCwHHCdQxG7KM0wL98mkfUvYtIcBJbFS62l0Z9Fv09Y CDPtaHzDb8q3vJtD5Azlz7V4NHZ3G+IF/ThRjtoWGi8Sqs/Jrl4y7qs6K2OIyVjIaEv5 lS6Q== X-Gm-Message-State: ACrzQf0zHLk6z+nR8p/6lcXUdHdXtVHstbNSWyzJg0ADWtgVDG3bp0XG WSNo6srU130ehvuqSsBsQMg= X-Received: by 2002:a05:600c:1e89:b0:3c7:1e:acc2 with SMTP id be9-20020a05600c1e8900b003c7001eacc2mr18476310wmb.44.1666537028420; Sun, 23 Oct 2022 07:57:08 -0700 (PDT) Received: from localhost (94.197.10.75.threembb.co.uk. [94.197.10.75]) by smtp.gmail.com with ESMTPSA id hg16-20020a05600c539000b003c6b70a4d69sm8345331wmb.42.2022.10.23.07.57.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 23 Oct 2022 07:57:08 -0700 (PDT) From: Aidan MacDonald <aidanmacdonald.0x0@gmail.com> To: paul@crapouillou.net, mturquette@baylibre.com, sboyd@kernel.org, robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org Cc: zhouyu@wanyeetech.com, linux-mips@vger.kernel.org, linux-clk@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 5/5] clk: ingenic: Add X1000 audio clocks Date: Sun, 23 Oct 2022 15:56:53 +0100 Message-Id: <20221023145653.177234-6-aidanmacdonald.0x0@gmail.com> In-Reply-To: <20221023145653.177234-1-aidanmacdonald.0x0@gmail.com> References: <20221023145653.177234-1-aidanmacdonald.0x0@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1747491046137928512?= X-GMAIL-MSGID: =?utf-8?q?1747491046137928512?= |
Series |
Add support for X1000 audio clocks
|
|
Commit Message
Aidan MacDonald
Oct. 23, 2022, 2:56 p.m. UTC
The X1000's CGU supplies the I2S system clock to the AIC module
and ultimately the audio codec, represented by the "i2s" clock.
It is a simple mux which can either pass through EXCLK or a PLL
multiplied by a fractional divider (the "i2s_pll" clock).
The AIC contains a separate 1/N divider controlled by the I2S
driver, which generates the bit clock from the system clock.
The frame clock is always fixed to 1/64th of the bit clock.
Signed-off-by: Aidan MacDonald <aidanmacdonald.0x0@gmail.com>
---
drivers/clk/ingenic/x1000-cgu.c | 69 +++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
Comments
Hi Aidan, Le dim. 23 oct. 2022 à 15:56:53 +0100, Aidan MacDonald <aidanmacdonald.0x0@gmail.com> a écrit : > The X1000's CGU supplies the I2S system clock to the AIC module > and ultimately the audio codec, represented by the "i2s" clock. > It is a simple mux which can either pass through EXCLK or a PLL > multiplied by a fractional divider (the "i2s_pll" clock). > > The AIC contains a separate 1/N divider controlled by the I2S > driver, which generates the bit clock from the system clock. > The frame clock is always fixed to 1/64th of the bit clock. > > Signed-off-by: Aidan MacDonald <aidanmacdonald.0x0@gmail.com> > --- > drivers/clk/ingenic/x1000-cgu.c | 69 > +++++++++++++++++++++++++++++++++ > 1 file changed, 69 insertions(+) > > diff --git a/drivers/clk/ingenic/x1000-cgu.c > b/drivers/clk/ingenic/x1000-cgu.c > index b2ce3fb83f54..341276e5e1ef 100644 > --- a/drivers/clk/ingenic/x1000-cgu.c > +++ b/drivers/clk/ingenic/x1000-cgu.c > @@ -8,6 +8,7 @@ > #include <linux/delay.h> > #include <linux/io.h> > #include <linux/of.h> > +#include <linux/rational.h> > > #include <dt-bindings/clock/ingenic,x1000-cgu.h> > > @@ -168,6 +169,37 @@ static const struct clk_ops x1000_otg_phy_ops = { > .is_enabled = x1000_usb_phy_is_enabled, > }; > > +static void > +x1000_i2spll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info, > + unsigned long rate, unsigned long parent_rate, > + unsigned int *pm, unsigned int *pn, unsigned int *pod) > +{ > + const unsigned long m_max = GENMASK(pll_info->m_bits - 1, 0); > + const unsigned long n_max = GENMASK(pll_info->n_bits - 1, 0); > + unsigned long m, n; > + > + rational_best_approximation(rate, parent_rate, m_max, n_max, &m, > &n); > + > + /* n should not be less than 2*m */ > + if (n < 2 * m) > + n = 2 * m; > + > + *pm = m; > + *pn = n; > + *pod = 1; > +} > + > +static void > +x1000_i2spll_set_rate_hook(const struct ingenic_cgu_pll_info > *pll_info, > + unsigned long rate, unsigned long parent_rate) > +{ > + /* > + * For some reason, the I2S divider doesn't work properly after > + * updating I2SCDR unless I2SCDR1 is read & written back. > + */ > + writel(readl(cgu->base + CGU_REG_I2SCDR1), cgu->base + > CGU_REG_I2SCDR1); Not fond of the nesting here, just use a variable. Besides... According to the documentation, bits 31 and 30 of this register are misconnected: writing to bit 31 will be reflected in bit 30, and vice-versa. So this would work only if the bits 30 and 31 have the same value. And worse than that, where do you actually set the register's value? Because bits 30/31, if cleared, will automatically compute the M/N values to the I2SCDR fields, overriding what the driver's .set_rate() callback is doing. Either we want that, and in that case the I2S clock should be a custom clock (since it wouldn't need to compute or write M/N), or we don't, and in this case bits 30/31 of this register should be set. > +} > + > static const s8 pll_od_encoding[8] = { > 0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3, > }; > @@ -319,6 +351,37 @@ static const struct ingenic_cgu_clk_info > x1000_cgu_clocks[] = { > .gate = { CGU_REG_CLKGR, 25 }, > }, > > + [X1000_CLK_I2SPLLMUX] = { > + "i2s_pll_mux", CGU_CLK_MUX, > + .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL, -1, -1 }, If you have only 1 bit you can only have two parents, so you can remove the -1s. > + .mux = { CGU_REG_I2SCDR, 31, 1 }, > + }, > + > + [X1000_CLK_I2SPLL] = { > + "i2s_pll", CGU_CLK_PLL, > + .parents = { X1000_CLK_I2SPLLMUX, -1, -1, -1 }, .parents = { X1000_CLK_I2SPLLMUX, }, > + .pll = { > + .reg = CGU_REG_I2SCDR, > + .rate_multiplier = 1, > + .m_shift = 13, > + .m_bits = 9, > + .n_shift = 0, > + .n_bits = 13, > + .calc_m_n_od = x1000_i2spll_calc_m_n_od, > + .set_rate_hook = x1000_i2spll_set_rate_hook, > + }, > + }, > + > + [X1000_CLK_I2S] = { > + "i2s", CGU_CLK_MUX, > + .parents = { X1000_CLK_EXCLK, -1, -1, X1000_CLK_I2SPLL }, > + /* > + * NOTE: the mux is at bit 30; bit 29 enables the M/N divider. > + * Therefore, the divider is disabled when EXCLK is selected. > + */ > + .mux = { CGU_REG_I2SCDR, 29, 2 }, > + }, > + > [X1000_CLK_LCD] = { > "lcd", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE, > .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL }, > @@ -426,6 +489,12 @@ static const struct ingenic_cgu_clk_info > x1000_cgu_clocks[] = { > .gate = { CGU_REG_CLKGR, 9 }, > }, > > + [X1000_CLK_AIC] = { > + "aic", CGU_CLK_GATE, > + .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, .parents = { X1000_CLK_EXCLK, }, Cheers, -Paul > + .gate = { CGU_REG_CLKGR, 11 }, > + }, > + > [X1000_CLK_UART0] = { > "uart0", CGU_CLK_GATE, > .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, > -- > 2.38.1 >
Paul Cercueil <paul@crapouillou.net> writes: > Hi Aidan, > > Le dim. 23 oct. 2022 à 15:56:53 +0100, Aidan MacDonald > <aidanmacdonald.0x0@gmail.com> a écrit : >> The X1000's CGU supplies the I2S system clock to the AIC module >> and ultimately the audio codec, represented by the "i2s" clock. >> It is a simple mux which can either pass through EXCLK or a PLL >> multiplied by a fractional divider (the "i2s_pll" clock). >> The AIC contains a separate 1/N divider controlled by the I2S >> driver, which generates the bit clock from the system clock. >> The frame clock is always fixed to 1/64th of the bit clock. >> Signed-off-by: Aidan MacDonald <aidanmacdonald.0x0@gmail.com> >> --- >> drivers/clk/ingenic/x1000-cgu.c | 69 +++++++++++++++++++++++++++++++++ >> 1 file changed, 69 insertions(+) >> diff --git a/drivers/clk/ingenic/x1000-cgu.c >> b/drivers/clk/ingenic/x1000-cgu.c >> index b2ce3fb83f54..341276e5e1ef 100644 >> --- a/drivers/clk/ingenic/x1000-cgu.c >> +++ b/drivers/clk/ingenic/x1000-cgu.c >> @@ -8,6 +8,7 @@ >> #include <linux/delay.h> >> #include <linux/io.h> >> #include <linux/of.h> >> +#include <linux/rational.h> >> #include <dt-bindings/clock/ingenic,x1000-cgu.h> >> @@ -168,6 +169,37 @@ static const struct clk_ops x1000_otg_phy_ops = { >> .is_enabled = x1000_usb_phy_is_enabled, >> }; >> +static void >> +x1000_i2spll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info, >> + unsigned long rate, unsigned long parent_rate, >> + unsigned int *pm, unsigned int *pn, unsigned int *pod) >> +{ >> + const unsigned long m_max = GENMASK(pll_info->m_bits - 1, 0); >> + const unsigned long n_max = GENMASK(pll_info->n_bits - 1, 0); >> + unsigned long m, n; >> + >> + rational_best_approximation(rate, parent_rate, m_max, n_max, &m, &n); >> + >> + /* n should not be less than 2*m */ >> + if (n < 2 * m) >> + n = 2 * m; >> + >> + *pm = m; >> + *pn = n; >> + *pod = 1; >> +} >> + >> +static void >> +x1000_i2spll_set_rate_hook(const struct ingenic_cgu_pll_info *pll_info, >> + unsigned long rate, unsigned long parent_rate) >> +{ >> + /* >> + * For some reason, the I2S divider doesn't work properly after >> + * updating I2SCDR unless I2SCDR1 is read & written back. >> + */ >> + writel(readl(cgu->base + CGU_REG_I2SCDR1), cgu->base + CGU_REG_I2SCDR1); > > Not fond of the nesting here, just use a variable. > > Besides... According to the documentation, bits 31 and 30 of this register are > misconnected: writing to bit 31 will be reflected in bit 30, and vice-versa. So > this would work only if the bits 30 and 31 have the same value. From my tests it seems that reads are swapped w.r.t. the documented bit positions, but not writes. I'm assuming this is the case because when I write to the register with bit 30 (I2S_DEN) set, I can change I2SDIV_D freely, but if bit 30 is 0, then I2SDIV_D is replaced by I2SDIV_N/2 like the manual suggests. > And worse than that, where do you actually set the register's value? Because > bits 30/31, if cleared, will automatically compute the M/N values to the I2SCDR > fields, overriding what the driver's .set_rate() callback is doing. I don't initialize the register, but I2S_NEN (bit 31 of I2SCDR1) doesn't cause I2SCDR to be automatically updated like I see with I2SCDR1 when I2S_DEN = 0. It seems setting I2S_NEN to 1 has some other effect on the clock output, but I don't have an oscilloscope so I'm not sure exactly what it is. I'm limited to testing by ear. For example, with f_in = 1008 MHz, M = 7, N = 2500, the output should nominally be 2.8224 MHz (= 64 * 44.1 KHz). Now that works just fine for playing 44.1 KHz audio if I set I2S_NEN to 0, but setting it to 1, I need to increase M to 16 to get things to sound right. I've tested a couple other frequencies but there isn't an obvious pattern to what increase in M is needed (it always seems to need an increase). I don't really care to play around with it because everything works fine if I keep I2S_NEN = I2S_DEN = 0. I've tested that configuration from 8 KHz to 192 KHz at typical audio frequencies and randomly chosen non-standard frequencies, and haven't experienced any issues. > Either we want that, and in that case the I2S clock should be a custom clock > (since it wouldn't need to compute or write M/N), or we don't, and in this case > bits 30/31 of this register should be set. My experience suggests we should set I2S_NEN and I2S_DEN to 0 and calculate output frequency as F_out = F_in * I2SDIV_M / I2SDIV_N. Reading I2SCDR1 and writing the same value back is something I took from the Ingenic BSP kernels. (The manual also suggests doing this.) It worked fine for me so I didn't bother to invesigate it further. The important part actually seems to be refreshing I2SDIV_D to a "reasonable" value, which happens automatically when I2S_DEN = 0. But the automatic calculation only happens when writing the register. Writing 0 to I2SCDR1 solves both problems: the register is properly initialized and I2SDIV_D gets updated automatically. That seems to be the best solution. >> +} >> + >> static const s8 pll_od_encoding[8] = {itt >> 0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3, >> }; >> @@ -319,6 +351,37 @@ static const struct ingenic_cgu_clk_info >> x1000_cgu_clocks[] = { >> .gate = { CGU_REG_CLKGR, 25 }, >> }, >> + [X1000_CLK_I2SPLLMUX] = { >> + "i2s_pll_mux", CGU_CLK_MUX, >> + .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL, -1, -1 }, > > If you have only 1 bit you can only have two parents, so you can remove the > -1s. > Thanks, will do. Regards, Aidan >> + .mux = { CGU_REG_I2SCDR, 31, 1 }, >> + }, >> + >> + [X1000_CLK_I2SPLL] = { >> + "i2s_pll", CGU_CLK_PLL, >> + .parents = { X1000_CLK_I2SPLLMUX, -1, -1, -1 }, > > .parents = { X1000_CLK_I2SPLLMUX, }, > >> + .pll = { >> + .reg = CGU_REG_I2SCDR, >> + .rate_multiplier = 1, >> + .m_shift = 13, >> + .m_bits = 9, >> + .n_shift = 0, >> + .n_bits = 13, >> + .calc_m_n_od = x1000_i2spll_calc_m_n_od, >> + .set_rate_hook = x1000_i2spll_set_rate_hook, >> + }, >> + }, >> + >> + [X1000_CLK_I2S] = { >> + "i2s", CGU_CLK_MUX, >> + .parents = { X1000_CLK_EXCLK, -1, -1, X1000_CLK_I2SPLL }, >> + /* >> + * NOTE: the mux is at bit 30; bit 29 enables the M/N divider. >> + * Therefore, the divider is disabled when EXCLK is selected. >> + */ >> + .mux = { CGU_REG_I2SCDR, 29, 2 }, >> + }, >> + >> [X1000_CLK_LCD] = { >> "lcd", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE, >> .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL }, >> @@ -426,6 +489,12 @@ static const struct ingenic_cgu_clk_info >> x1000_cgu_clocks[] = { >> .gate = { CGU_REG_CLKGR, 9 }, >> }, >> + [X1000_CLK_AIC] = { >> + "aic", CGU_CLK_GATE, >> + .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, > > .parents = { X1000_CLK_EXCLK, }, > > Cheers, > -Paul > >> + .gate = { CGU_REG_CLKGR, 11 }, >> + }, >> + >> [X1000_CLK_UART0] = { >> "uart0", CGU_CLK_GATE, >> .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, >> -- >> 2.38.1 >>
diff --git a/drivers/clk/ingenic/x1000-cgu.c b/drivers/clk/ingenic/x1000-cgu.c index b2ce3fb83f54..341276e5e1ef 100644 --- a/drivers/clk/ingenic/x1000-cgu.c +++ b/drivers/clk/ingenic/x1000-cgu.c @@ -8,6 +8,7 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/of.h> +#include <linux/rational.h> #include <dt-bindings/clock/ingenic,x1000-cgu.h> @@ -168,6 +169,37 @@ static const struct clk_ops x1000_otg_phy_ops = { .is_enabled = x1000_usb_phy_is_enabled, }; +static void +x1000_i2spll_calc_m_n_od(const struct ingenic_cgu_pll_info *pll_info, + unsigned long rate, unsigned long parent_rate, + unsigned int *pm, unsigned int *pn, unsigned int *pod) +{ + const unsigned long m_max = GENMASK(pll_info->m_bits - 1, 0); + const unsigned long n_max = GENMASK(pll_info->n_bits - 1, 0); + unsigned long m, n; + + rational_best_approximation(rate, parent_rate, m_max, n_max, &m, &n); + + /* n should not be less than 2*m */ + if (n < 2 * m) + n = 2 * m; + + *pm = m; + *pn = n; + *pod = 1; +} + +static void +x1000_i2spll_set_rate_hook(const struct ingenic_cgu_pll_info *pll_info, + unsigned long rate, unsigned long parent_rate) +{ + /* + * For some reason, the I2S divider doesn't work properly after + * updating I2SCDR unless I2SCDR1 is read & written back. + */ + writel(readl(cgu->base + CGU_REG_I2SCDR1), cgu->base + CGU_REG_I2SCDR1); +} + static const s8 pll_od_encoding[8] = { 0x0, 0x1, -1, 0x2, -1, -1, -1, 0x3, }; @@ -319,6 +351,37 @@ static const struct ingenic_cgu_clk_info x1000_cgu_clocks[] = { .gate = { CGU_REG_CLKGR, 25 }, }, + [X1000_CLK_I2SPLLMUX] = { + "i2s_pll_mux", CGU_CLK_MUX, + .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL, -1, -1 }, + .mux = { CGU_REG_I2SCDR, 31, 1 }, + }, + + [X1000_CLK_I2SPLL] = { + "i2s_pll", CGU_CLK_PLL, + .parents = { X1000_CLK_I2SPLLMUX, -1, -1, -1 }, + .pll = { + .reg = CGU_REG_I2SCDR, + .rate_multiplier = 1, + .m_shift = 13, + .m_bits = 9, + .n_shift = 0, + .n_bits = 13, + .calc_m_n_od = x1000_i2spll_calc_m_n_od, + .set_rate_hook = x1000_i2spll_set_rate_hook, + }, + }, + + [X1000_CLK_I2S] = { + "i2s", CGU_CLK_MUX, + .parents = { X1000_CLK_EXCLK, -1, -1, X1000_CLK_I2SPLL }, + /* + * NOTE: the mux is at bit 30; bit 29 enables the M/N divider. + * Therefore, the divider is disabled when EXCLK is selected. + */ + .mux = { CGU_REG_I2SCDR, 29, 2 }, + }, + [X1000_CLK_LCD] = { "lcd", CGU_CLK_MUX | CGU_CLK_DIV | CGU_CLK_GATE, .parents = { X1000_CLK_SCLKA, X1000_CLK_MPLL }, @@ -426,6 +489,12 @@ static const struct ingenic_cgu_clk_info x1000_cgu_clocks[] = { .gate = { CGU_REG_CLKGR, 9 }, }, + [X1000_CLK_AIC] = { + "aic", CGU_CLK_GATE, + .parents = { X1000_CLK_EXCLK, -1, -1, -1 }, + .gate = { CGU_REG_CLKGR, 11 }, + }, + [X1000_CLK_UART0] = { "uart0", CGU_CLK_GATE, .parents = { X1000_CLK_EXCLK, -1, -1, -1 },