diff mbox series

[3/4] spi: sun6i: add quirk for in-controller clock divider

Message ID BYAPR20MB24728A99C2F7505873A091B5BCF79@BYAPR20MB2472.namprd20.prod.outlook.com
State New
Headers show
Series [1/4] dt-bindings: spi: sun6i: add DT bindings for Allwinner R329 SPI | expand

Commit Message

icenowy@outlook.com April 22, 2022, 3:56 p.m. UTC
From: Icenowy Zheng <icenowy@aosc.io>

Previously SPI controllers in Allwinner SoCs has a clock divider inside.
However now the clock divider is removed and to set the transfer clock
rate it's only needed to set the SPI module clock to the target value.

Add a quirk for this kind of SPI controllers.

Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
---
 drivers/spi/spi-sun6i.c | 68 +++++++++++++++++++++++------------------
 1 file changed, 38 insertions(+), 30 deletions(-)

Comments

Samuel Holland April 22, 2022, 11:54 p.m. UTC | #1
On 4/22/22 10:56 AM, icenowy@outlook.com wrote:
> From: Icenowy Zheng <icenowy@aosc.io>
> 
> Previously SPI controllers in Allwinner SoCs has a clock divider inside.
> However now the clock divider is removed and to set the transfer clock
> rate it's only needed to set the SPI module clock to the target value.
> 
> Add a quirk for this kind of SPI controllers.
> 
> Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
> ---
>  drivers/spi/spi-sun6i.c | 68 +++++++++++++++++++++++------------------
>  1 file changed, 38 insertions(+), 30 deletions(-)
> 
> diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
> index 84c525b08ad0..fc81afc3a963 100644
> --- a/drivers/spi/spi-sun6i.c
> +++ b/drivers/spi/spi-sun6i.c
> @@ -87,6 +87,7 @@
>  
>  struct sun6i_spi_cfg {
>  	unsigned long		fifo_depth;
> +	bool			has_clk_ctl;
>  };
>  
>  struct sun6i_spi {
> @@ -260,7 +261,7 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
>  				  struct spi_transfer *tfr)
>  {
>  	struct sun6i_spi *sspi = spi_master_get_devdata(master);
> -	unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout;
> +	unsigned int div, div_cdr1, div_cdr2, timeout;
>  	unsigned int start, end, tx_time;
>  	unsigned int trig_level;
>  	unsigned int tx_len = 0, rx_len = 0;
> @@ -350,39 +351,44 @@ static int sun6i_spi_transfer_one(struct spi_master *master,
>  
>  	sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
>  
> -	/* Ensure that we have a parent clock fast enough */
> -	mclk_rate = clk_get_rate(sspi->mclk);
> -	if (mclk_rate < (2 * tfr->speed_hz)) {
> -		clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
> -		mclk_rate = clk_get_rate(sspi->mclk);
> -	}
> +	if (sspi->cfg->has_clk_ctl) {
> +		unsigned int mclk_rate = clk_get_rate(sspi->mclk);
> +		/* Ensure that we have a parent clock fast enough */
> +		if (mclk_rate < (2 * tfr->speed_hz)) {
> +			clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
> +			mclk_rate = clk_get_rate(sspi->mclk);
> +		}
>  
> -	/*
> -	 * Setup clock divider.
> -	 *
> -	 * We have two choices there. Either we can use the clock
> -	 * divide rate 1, which is calculated thanks to this formula:
> -	 * SPI_CLK = MOD_CLK / (2 ^ cdr)
> -	 * Or we can use CDR2, which is calculated with the formula:
> -	 * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
> -	 * Wether we use the former or the latter is set through the
> -	 * DRS bit.
> -	 *
> -	 * First try CDR2, and if we can't reach the expected
> -	 * frequency, fall back to CDR1.
> -	 */
> -	div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
> -	div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
> -	if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
> -		reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS;
> -		tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
> +		/*
> +		 * Setup clock divider.
> +		 *
> +		 * We have two choices there. Either we can use the clock
> +		 * divide rate 1, which is calculated thanks to this formula:
> +		 * SPI_CLK = MOD_CLK / (2 ^ cdr)
> +		 * Or we can use CDR2, which is calculated with the formula:
> +		 * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
> +		 * Wether we use the former or the latter is set through the
> +		 * DRS bit.
> +		 *
> +		 * First try CDR2, and if we can't reach the expected
> +		 * frequency, fall back to CDR1.
> +		 */
> +		div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
> +		div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
> +		if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
> +			reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS;
> +			tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
> +		} else {
> +			div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1));
> +			reg = SUN6I_CLK_CTL_CDR1(div);
> +			tfr->effective_speed_hz = mclk_rate / (1 << div);
> +		}
> +
> +		sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
>  	} else {
> -		div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1));
> -		reg = SUN6I_CLK_CTL_CDR1(div);
> -		tfr->effective_speed_hz = mclk_rate / (1 << div);
> +		clk_set_rate(sspi->mclk, tfr->speed_hz);

clk_set_rate() may not set the exact requested rate. Should we set
tfr->effective_speed_hz based on clk_get_rate() afterward?

Regards,
Samuel

>  	}
>  
> -	sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
>  	/* Finally enable the bus - doing so before might raise SCK to HIGH */
>  	reg = sun6i_spi_read(sspi, SUN6I_GBL_CTL_REG);
>  	reg |= SUN6I_GBL_CTL_BUS_ENABLE;
> @@ -702,10 +708,12 @@ static int sun6i_spi_remove(struct platform_device *pdev)
>  
>  static const struct sun6i_spi_cfg sun6i_a31_spi_cfg = {
>  	.fifo_depth	= SUN6I_FIFO_DEPTH,
> +	.has_clk_ctl	= true,
>  };
>  
>  static const struct sun6i_spi_cfg sun8i_h3_spi_cfg = {
>  	.fifo_depth	= SUN8I_FIFO_DEPTH,
> +	.has_clk_ctl	= true,
>  };
>  
>  static const struct of_device_id sun6i_spi_match[] = {
>
Icenowy Zheng April 22, 2022, 11:57 p.m. UTC | #2
在 2022-04-22星期五的 18:54 -0500,Samuel Holland写道:
> On 4/22/22 10:56 AM, icenowy@outlook.com wrote:
> > From: Icenowy Zheng <icenowy@aosc.io>
> > 
> > Previously SPI controllers in Allwinner SoCs has a clock divider
> > inside.
> > However now the clock divider is removed and to set the transfer
> > clock
> > rate it's only needed to set the SPI module clock to the target
> > value.
> > 
> > Add a quirk for this kind of SPI controllers.
> > 
> > Signed-off-by: Icenowy Zheng <icenowy@aosc.io>
> > ---
> >  drivers/spi/spi-sun6i.c | 68 +++++++++++++++++++++++--------------
> > ----
> >  1 file changed, 38 insertions(+), 30 deletions(-)
> > 
> > diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
> > index 84c525b08ad0..fc81afc3a963 100644
> > --- a/drivers/spi/spi-sun6i.c
> > +++ b/drivers/spi/spi-sun6i.c
> > @@ -87,6 +87,7 @@
> >  
> >  struct sun6i_spi_cfg {
> >         unsigned long           fifo_depth;
> > +       bool                    has_clk_ctl;
> >  };
> >  
> >  struct sun6i_spi {
> > @@ -260,7 +261,7 @@ static int sun6i_spi_transfer_one(struct
> > spi_master *master,
> >                                   struct spi_transfer *tfr)
> >  {
> >         struct sun6i_spi *sspi = spi_master_get_devdata(master);
> > -       unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout;
> > +       unsigned int div, div_cdr1, div_cdr2, timeout;
> >         unsigned int start, end, tx_time;
> >         unsigned int trig_level;
> >         unsigned int tx_len = 0, rx_len = 0;
> > @@ -350,39 +351,44 @@ static int sun6i_spi_transfer_one(struct
> > spi_master *master,
> >  
> >         sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
> >  
> > -       /* Ensure that we have a parent clock fast enough */
> > -       mclk_rate = clk_get_rate(sspi->mclk);
> > -       if (mclk_rate < (2 * tfr->speed_hz)) {
> > -               clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
> > -               mclk_rate = clk_get_rate(sspi->mclk);
> > -       }
> > +       if (sspi->cfg->has_clk_ctl) {
> > +               unsigned int mclk_rate = clk_get_rate(sspi->mclk);
> > +               /* Ensure that we have a parent clock fast enough
> > */
> > +               if (mclk_rate < (2 * tfr->speed_hz)) {
> > +                       clk_set_rate(sspi->mclk, 2 * tfr-
> > >speed_hz);
> > +                       mclk_rate = clk_get_rate(sspi->mclk);
> > +               }
> >  
> > -       /*
> > -        * Setup clock divider.
> > -        *
> > -        * We have two choices there. Either we can use the clock
> > -        * divide rate 1, which is calculated thanks to this
> > formula:
> > -        * SPI_CLK = MOD_CLK / (2 ^ cdr)
> > -        * Or we can use CDR2, which is calculated with the
> > formula:
> > -        * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
> > -        * Wether we use the former or the latter is set through
> > the
> > -        * DRS bit.
> > -        *
> > -        * First try CDR2, and if we can't reach the expected
> > -        * frequency, fall back to CDR1.
> > -        */
> > -       div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
> > -       div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
> > -       if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
> > -               reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) |
> > SUN6I_CLK_CTL_DRS;
> > -               tfr->effective_speed_hz = mclk_rate / (2 *
> > div_cdr2);
> > +               /*
> > +                * Setup clock divider.
> > +                *
> > +                * We have two choices there. Either we can use the
> > clock
> > +                * divide rate 1, which is calculated thanks to
> > this formula:
> > +                * SPI_CLK = MOD_CLK / (2 ^ cdr)
> > +                * Or we can use CDR2, which is calculated with the
> > formula:
> > +                * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
> > +                * Wether we use the former or the latter is set
> > through the
> > +                * DRS bit.
> > +                *
> > +                * First try CDR2, and if we can't reach the
> > expected
> > +                * frequency, fall back to CDR1.
> > +                */
> > +               div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
> > +               div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
> > +               if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
> > +                       reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) |
> > SUN6I_CLK_CTL_DRS;
> > +                       tfr->effective_speed_hz = mclk_rate / (2 *
> > div_cdr2);
> > +               } else {
> > +                       div = min(SUN6I_CLK_CTL_CDR1_MASK,
> > order_base_2(div_cdr1));
> > +                       reg = SUN6I_CLK_CTL_CDR1(div);
> > +                       tfr->effective_speed_hz = mclk_rate / (1 <<
> > div);
> > +               }
> > +
> > +               sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
> >         } else {
> > -               div = min(SUN6I_CLK_CTL_CDR1_MASK,
> > order_base_2(div_cdr1));
> > -               reg = SUN6I_CLK_CTL_CDR1(div);
> > -               tfr->effective_speed_hz = mclk_rate / (1 << div);
> > +               clk_set_rate(sspi->mclk, tfr->speed_hz);
> 
> clk_set_rate() may not set the exact requested rate. Should we set
> tfr->effective_speed_hz based on clk_get_rate() afterward?

Sounds right. I should add this in the next revision.

> 
> Regards,
> Samuel
> 
> >         }
> >  
> > -       sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
> >         /* Finally enable the bus - doing so before might raise SCK
> > to HIGH */
> >         reg = sun6i_spi_read(sspi, SUN6I_GBL_CTL_REG);
> >         reg |= SUN6I_GBL_CTL_BUS_ENABLE;
> > @@ -702,10 +708,12 @@ static int sun6i_spi_remove(struct
> > platform_device *pdev)
> >  
> >  static const struct sun6i_spi_cfg sun6i_a31_spi_cfg = {
> >         .fifo_depth     = SUN6I_FIFO_DEPTH,
> > +       .has_clk_ctl    = true,
> >  };
> >  
> >  static const struct sun6i_spi_cfg sun8i_h3_spi_cfg = {
> >         .fifo_depth     = SUN8I_FIFO_DEPTH,
> > +       .has_clk_ctl    = true,
> >  };
> >  
> >  static const struct of_device_id sun6i_spi_match[] = {
> > 
>
diff mbox series

Patch

diff --git a/drivers/spi/spi-sun6i.c b/drivers/spi/spi-sun6i.c
index 84c525b08ad0..fc81afc3a963 100644
--- a/drivers/spi/spi-sun6i.c
+++ b/drivers/spi/spi-sun6i.c
@@ -87,6 +87,7 @@ 
 
 struct sun6i_spi_cfg {
 	unsigned long		fifo_depth;
+	bool			has_clk_ctl;
 };
 
 struct sun6i_spi {
@@ -260,7 +261,7 @@  static int sun6i_spi_transfer_one(struct spi_master *master,
 				  struct spi_transfer *tfr)
 {
 	struct sun6i_spi *sspi = spi_master_get_devdata(master);
-	unsigned int mclk_rate, div, div_cdr1, div_cdr2, timeout;
+	unsigned int div, div_cdr1, div_cdr2, timeout;
 	unsigned int start, end, tx_time;
 	unsigned int trig_level;
 	unsigned int tx_len = 0, rx_len = 0;
@@ -350,39 +351,44 @@  static int sun6i_spi_transfer_one(struct spi_master *master,
 
 	sun6i_spi_write(sspi, SUN6I_TFR_CTL_REG, reg);
 
-	/* Ensure that we have a parent clock fast enough */
-	mclk_rate = clk_get_rate(sspi->mclk);
-	if (mclk_rate < (2 * tfr->speed_hz)) {
-		clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
-		mclk_rate = clk_get_rate(sspi->mclk);
-	}
+	if (sspi->cfg->has_clk_ctl) {
+		unsigned int mclk_rate = clk_get_rate(sspi->mclk);
+		/* Ensure that we have a parent clock fast enough */
+		if (mclk_rate < (2 * tfr->speed_hz)) {
+			clk_set_rate(sspi->mclk, 2 * tfr->speed_hz);
+			mclk_rate = clk_get_rate(sspi->mclk);
+		}
 
-	/*
-	 * Setup clock divider.
-	 *
-	 * We have two choices there. Either we can use the clock
-	 * divide rate 1, which is calculated thanks to this formula:
-	 * SPI_CLK = MOD_CLK / (2 ^ cdr)
-	 * Or we can use CDR2, which is calculated with the formula:
-	 * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
-	 * Wether we use the former or the latter is set through the
-	 * DRS bit.
-	 *
-	 * First try CDR2, and if we can't reach the expected
-	 * frequency, fall back to CDR1.
-	 */
-	div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
-	div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
-	if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
-		reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS;
-		tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
+		/*
+		 * Setup clock divider.
+		 *
+		 * We have two choices there. Either we can use the clock
+		 * divide rate 1, which is calculated thanks to this formula:
+		 * SPI_CLK = MOD_CLK / (2 ^ cdr)
+		 * Or we can use CDR2, which is calculated with the formula:
+		 * SPI_CLK = MOD_CLK / (2 * (cdr + 1))
+		 * Wether we use the former or the latter is set through the
+		 * DRS bit.
+		 *
+		 * First try CDR2, and if we can't reach the expected
+		 * frequency, fall back to CDR1.
+		 */
+		div_cdr1 = DIV_ROUND_UP(mclk_rate, tfr->speed_hz);
+		div_cdr2 = DIV_ROUND_UP(div_cdr1, 2);
+		if (div_cdr2 <= (SUN6I_CLK_CTL_CDR2_MASK + 1)) {
+			reg = SUN6I_CLK_CTL_CDR2(div_cdr2 - 1) | SUN6I_CLK_CTL_DRS;
+			tfr->effective_speed_hz = mclk_rate / (2 * div_cdr2);
+		} else {
+			div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1));
+			reg = SUN6I_CLK_CTL_CDR1(div);
+			tfr->effective_speed_hz = mclk_rate / (1 << div);
+		}
+
+		sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
 	} else {
-		div = min(SUN6I_CLK_CTL_CDR1_MASK, order_base_2(div_cdr1));
-		reg = SUN6I_CLK_CTL_CDR1(div);
-		tfr->effective_speed_hz = mclk_rate / (1 << div);
+		clk_set_rate(sspi->mclk, tfr->speed_hz);
 	}
 
-	sun6i_spi_write(sspi, SUN6I_CLK_CTL_REG, reg);
 	/* Finally enable the bus - doing so before might raise SCK to HIGH */
 	reg = sun6i_spi_read(sspi, SUN6I_GBL_CTL_REG);
 	reg |= SUN6I_GBL_CTL_BUS_ENABLE;
@@ -702,10 +708,12 @@  static int sun6i_spi_remove(struct platform_device *pdev)
 
 static const struct sun6i_spi_cfg sun6i_a31_spi_cfg = {
 	.fifo_depth	= SUN6I_FIFO_DEPTH,
+	.has_clk_ctl	= true,
 };
 
 static const struct sun6i_spi_cfg sun8i_h3_spi_cfg = {
 	.fifo_depth	= SUN8I_FIFO_DEPTH,
+	.has_clk_ctl	= true,
 };
 
 static const struct of_device_id sun6i_spi_match[] = {