From nobody Sat Sep 27 20:21:56 2025 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D278DC49EC3 for ; Tue, 23 Aug 2022 10:12:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1353667AbiHWKLt (ORCPT ); Tue, 23 Aug 2022 06:11:49 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:44340 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1352152AbiHWKEh (ORCPT ); Tue, 23 Aug 2022 06:04:37 -0400 Received: from ams.source.kernel.org (ams.source.kernel.org [IPv6:2604:1380:4601:e00::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DCF1C7CABB; Tue, 23 Aug 2022 01:51:24 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ams.source.kernel.org (Postfix) with ESMTPS id F0AB1B81BF8; Tue, 23 Aug 2022 08:51:22 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 599C3C433C1; Tue, 23 Aug 2022 08:51:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=linuxfoundation.org; s=korg; t=1661244681; bh=AIm+iPxsLuS8cHO7rgsgO4mYB1yWFzEMV0ofdqrskFI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=z1P+6muCxh7fkK4YtIwEsaAxKAcxb969jvNxu8taVgQKtnlGaBBcue2OSa1JOR5U7 SPySLmk4L2NQy3OeftCdzeLTHTM0CYCo6xJ8DkPtLByaq+dpq1dBblmPXQMSqwSvEK tngpP7ZL/ModNese5PWiN7CABVqznRmH4Z3/PSnE= From: Greg Kroah-Hartman To: linux-kernel@vger.kernel.org Cc: Greg Kroah-Hartman , stable@vger.kernel.org, Da Xue , Neil Armstrong , Mark Brown Subject: [PATCH 5.15 133/244] spi: meson-spicc: add local pow2 clock ops to preserve rate between messages Date: Tue, 23 Aug 2022 10:24:52 +0200 Message-Id: <20220823080103.561905801@linuxfoundation.org> X-Mailer: git-send-email 2.37.2 In-Reply-To: <20220823080059.091088642@linuxfoundation.org> References: <20220823080059.091088642@linuxfoundation.org> User-Agent: quilt/0.67 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: Neil Armstrong commit 09992025dacd258c823f50e82db09d7ef06cdac4 upstream. At the end of a message, the HW gets a reset in meson_spicc_unprepare_trans= fer(), this resets the SPICC_CONREG register and notably the value set by the Common Clock Framework. This is problematic because: - the register value CCF can be different from the corresponding CCF cached= rate - CCF is allowed to change the clock rate whenever the HW state This introduces: - local pow2 clock ops checking the HW state before allowing a clock operat= ion - separation of legacy pow2 clock patch and new enhanced clock path - SPICC_CONREG datarate value is now value kepts across messages It has been checked that: - SPICC_CONREG datarate value is kept across messages - CCF is only allowed to change the SPICC_CONREG datarate value when busy - SPICC_CONREG datarate value is correct for each transfer This didn't appear before commit 3e0cf4d3fc29 ("spi: meson-spicc: add a lin= ear clock divider support") because we recalculated and wrote the rate for each xfer. Fixes: 3e0cf4d3fc29 ("spi: meson-spicc: add a linear clock divider support") Reported-by: Da Xue Signed-off-by: Neil Armstrong Link: https://lore.kernel.org/r/20220811134445.678446-1-narmstrong@baylibre= .com Signed-off-by: Mark Brown Signed-off-by: Greg Kroah-Hartman --- drivers/spi/spi-meson-spicc.c | 129 ++++++++++++++++++++++++++++++++-----= ----- 1 file changed, 101 insertions(+), 28 deletions(-) --- a/drivers/spi/spi-meson-spicc.c +++ b/drivers/spi/spi-meson-spicc.c @@ -156,6 +156,7 @@ struct meson_spicc_device { void __iomem *base; struct clk *core; struct clk *pclk; + struct clk_divider pow2_div; struct clk *clk; struct spi_message *message; struct spi_transfer *xfer; @@ -168,6 +169,8 @@ struct meson_spicc_device { unsigned long xfer_remain; }; =20 +#define pow2_clk_to_spicc(_div) container_of(_div, struct meson_spicc_devi= ce, pow2_div) + static void meson_spicc_oen_enable(struct meson_spicc_device *spicc) { u32 conf; @@ -421,7 +424,7 @@ static int meson_spicc_prepare_message(s { struct meson_spicc_device *spicc =3D spi_master_get_devdata(master); struct spi_device *spi =3D message->spi; - u32 conf =3D 0; + u32 conf =3D readl_relaxed(spicc->base + SPICC_CONREG) & SPICC_DATARATE_M= ASK; =20 /* Store current message */ spicc->message =3D message; @@ -458,8 +461,6 @@ static int meson_spicc_prepare_message(s /* Select CS */ conf |=3D FIELD_PREP(SPICC_CS_MASK, spi->chip_select); =20 - /* Default Clock rate core/4 */ - /* Default 8bit word */ conf |=3D FIELD_PREP(SPICC_BITLENGTH_MASK, 8 - 1); =20 @@ -476,12 +477,16 @@ static int meson_spicc_prepare_message(s static int meson_spicc_unprepare_transfer(struct spi_master *master) { struct meson_spicc_device *spicc =3D spi_master_get_devdata(master); + u32 conf =3D readl_relaxed(spicc->base + SPICC_CONREG) & SPICC_DATARATE_M= ASK; =20 /* Disable all IRQs */ writel(0, spicc->base + SPICC_INTREG); =20 device_reset_optional(&spicc->pdev->dev); =20 + /* Set default configuration, keeping datarate field */ + writel_relaxed(conf, spicc->base + SPICC_CONREG); + return 0; } =20 @@ -518,14 +523,60 @@ static void meson_spicc_cleanup(struct s * Clk path for G12A series: * pclk -> pow2 fixed div -> pow2 div -> mux -> out * pclk -> enh fixed div -> enh div -> mux -> out + * + * The pow2 divider is tied to the controller HW state, and the + * divider is only valid when the controller is initialized. + * + * A set of clock ops is added to make sure we don't read/set this + * clock rate while the controller is in an unknown state. */ =20 -static int meson_spicc_clk_init(struct meson_spicc_device *spicc) +static unsigned long meson_spicc_pow2_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *divider =3D to_clk_divider(hw); + struct meson_spicc_device *spicc =3D pow2_clk_to_spicc(divider); + + if (!spicc->master->cur_msg || !spicc->master->busy) + return 0; + + return clk_divider_ops.recalc_rate(hw, parent_rate); +} + +static int meson_spicc_pow2_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct clk_divider *divider =3D to_clk_divider(hw); + struct meson_spicc_device *spicc =3D pow2_clk_to_spicc(divider); + + if (!spicc->master->cur_msg || !spicc->master->busy) + return -EINVAL; + + return clk_divider_ops.determine_rate(hw, req); +} + +static int meson_spicc_pow2_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider *divider =3D to_clk_divider(hw); + struct meson_spicc_device *spicc =3D pow2_clk_to_spicc(divider); + + if (!spicc->master->cur_msg || !spicc->master->busy) + return -EINVAL; + + return clk_divider_ops.set_rate(hw, rate, parent_rate); +} + +const struct clk_ops meson_spicc_pow2_clk_ops =3D { + .recalc_rate =3D meson_spicc_pow2_recalc_rate, + .determine_rate =3D meson_spicc_pow2_determine_rate, + .set_rate =3D meson_spicc_pow2_set_rate, +}; + +static int meson_spicc_pow2_clk_init(struct meson_spicc_device *spicc) { struct device *dev =3D &spicc->pdev->dev; - struct clk_fixed_factor *pow2_fixed_div, *enh_fixed_div; - struct clk_divider *pow2_div, *enh_div; - struct clk_mux *mux; + struct clk_fixed_factor *pow2_fixed_div; struct clk_init_data init; struct clk *clk; struct clk_parent_data parent_data[2]; @@ -560,31 +611,45 @@ static int meson_spicc_clk_init(struct m if (WARN_ON(IS_ERR(clk))) return PTR_ERR(clk); =20 - pow2_div =3D devm_kzalloc(dev, sizeof(*pow2_div), GFP_KERNEL); - if (!pow2_div) - return -ENOMEM; - snprintf(name, sizeof(name), "%s#pow2_div", dev_name(dev)); init.name =3D name; - init.ops =3D &clk_divider_ops; - init.flags =3D CLK_SET_RATE_PARENT; + init.ops =3D &meson_spicc_pow2_clk_ops; + /* + * Set NOCACHE here to make sure we read the actual HW value + * since we reset the HW after each transfer. + */ + init.flags =3D CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE; parent_data[0].hw =3D &pow2_fixed_div->hw; init.num_parents =3D 1; =20 - pow2_div->shift =3D 16, - pow2_div->width =3D 3, - pow2_div->flags =3D CLK_DIVIDER_POWER_OF_TWO, - pow2_div->reg =3D spicc->base + SPICC_CONREG; - pow2_div->hw.init =3D &init; + spicc->pow2_div.shift =3D 16, + spicc->pow2_div.width =3D 3, + spicc->pow2_div.flags =3D CLK_DIVIDER_POWER_OF_TWO, + spicc->pow2_div.reg =3D spicc->base + SPICC_CONREG; + spicc->pow2_div.hw.init =3D &init; =20 - clk =3D devm_clk_register(dev, &pow2_div->hw); - if (WARN_ON(IS_ERR(clk))) - return PTR_ERR(clk); + spicc->clk =3D devm_clk_register(dev, &spicc->pow2_div.hw); + if (WARN_ON(IS_ERR(spicc->clk))) + return PTR_ERR(spicc->clk); =20 - if (!spicc->data->has_enhance_clk_div) { - spicc->clk =3D clk; - return 0; - } + return 0; +} + +static int meson_spicc_enh_clk_init(struct meson_spicc_device *spicc) +{ + struct device *dev =3D &spicc->pdev->dev; + struct clk_fixed_factor *enh_fixed_div; + struct clk_divider *enh_div; + struct clk_mux *mux; + struct clk_init_data init; + struct clk *clk; + struct clk_parent_data parent_data[2]; + char name[64]; + + memset(&init, 0, sizeof(init)); + memset(&parent_data, 0, sizeof(parent_data)); + + init.parent_data =3D parent_data; =20 /* algorithm for enh div: rate =3D freq / 2 / (N + 1) */ =20 @@ -637,7 +702,7 @@ static int meson_spicc_clk_init(struct m snprintf(name, sizeof(name), "%s#sel", dev_name(dev)); init.name =3D name; init.ops =3D &clk_mux_ops; - parent_data[0].hw =3D &pow2_div->hw; + parent_data[0].hw =3D &spicc->pow2_div.hw; parent_data[1].hw =3D &enh_div->hw; init.num_parents =3D 2; init.flags =3D CLK_SET_RATE_PARENT; @@ -754,12 +819,20 @@ static int meson_spicc_probe(struct plat =20 meson_spicc_oen_enable(spicc); =20 - ret =3D meson_spicc_clk_init(spicc); + ret =3D meson_spicc_pow2_clk_init(spicc); if (ret) { - dev_err(&pdev->dev, "clock registration failed\n"); + dev_err(&pdev->dev, "pow2 clock registration failed\n"); goto out_clk; } =20 + if (spicc->data->has_enhance_clk_div) { + ret =3D meson_spicc_enh_clk_init(spicc); + if (ret) { + dev_err(&pdev->dev, "clock registration failed\n"); + goto out_clk; + } + } + ret =3D devm_spi_register_master(&pdev->dev, master); if (ret) { dev_err(&pdev->dev, "spi master registration failed\n");