diff mbox series

[4/7] imx8mp: power-domain: Expose high performance PLL clock

Message ID 20240220131056.2962331-5-sumit.garg@linaro.org
State New
Headers show
Series imx8mp: Enable PCIe/NVMe support | expand

Commit Message

Sumit Garg Feb. 20, 2024, 1:10 p.m. UTC
PCIe PHY can use it when there is no external refclock provided.

Signed-off-by: Sumit Garg <sumit.garg@linaro.org>
---
 drivers/power/domain/imx8mp-hsiomix.c | 79 +++++++++++++++++++++++++--
 1 file changed, 73 insertions(+), 6 deletions(-)

Comments

Marek Vasut Feb. 20, 2024, 3:16 p.m. UTC | #1
On 2/20/24 14:10, Sumit Garg wrote:
> PCIe PHY can use it when there is no external refclock provided.

Commit message needs to be fixed.

> +static int hsio_pll_enable(struct udevice *dev)
> +{
> +	struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
> +	unsigned long start;
> +	u32 val;
> +
> +	/* Setup HSIO PLL */
> +	val = readl(priv->base + GPR_REG2);
> +	val &= ~(P_PLL_MASK | M_PLL_MASK | S_PLL_MASK);
> +	val |= (FIELD_PREP(P_PLL_MASK, 12) | FIELD_PREP(M_PLL_MASK, 800) |
> +		FIELD_PREP(S_PLL_MASK, 4));
> +	writel(val, priv->base + GPR_REG2);

clrsetbits_le32()

> +	/* de-assert PLL reset */
> +	setbits_le32(priv->base + GPR_REG3, PLL_RST);
> +
> +	/* enable PLL */
> +	setbits_le32(priv->base + GPR_REG3, PLL_CKE);
> +
> +	/* Check if PLL is locked */
> +	start = get_timer(0);

wait_for_bit() or readl_poll_timeout()

> +	for (;;) {
> +		if (readl(priv->base + GPR_REG1) & PLL_LOCK)
> +			break;
> +
> +		if (get_timer(start) > 100) {
> +			dev_err(dev, "failed to lock HSIO PLL\n");
> +			return -ETIMEDOUT;
> +		}
> +
> +		udelay(10);
> +	}
> +
> +	return 0;
> +}
> +
> +static void hsio_pll_disable(struct udevice *dev)
> +{
> +	struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
> +
> +	/* de-assert PLL reset */
> +	clrbits_le32(priv->base + GPR_REG3, PLL_RST);
> +
> +	/* enable PLL */
> +	clrbits_le32(priv->base + GPR_REG3, PLL_CKE);
> +}
> +
>   static int imx8mp_hsiomix_on(struct power_domain *power_domain)
>   {
>   	struct udevice *dev = power_domain->dev;
> @@ -69,16 +127,23 @@ static int imx8mp_hsiomix_on(struct power_domain *power_domain)
>   	if (ret)
>   		goto err_clk_pcie;
>   
> -	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB)
> +	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) {
>   		setbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN);
> -	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE)
> +	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE) {
>   		setbits_le32(priv->base + GPR_REG0, PCIE_CLOCK_MODULE_EN);
> -	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY)
> +	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY) {
>   		setbits_le32(priv->base + GPR_REG0, PCIE_PHY_APB_RST |
>   						    PCIE_PHY_INIT_RST);
>   
> +		ret = hsio_pll_enable(dev);

Is this how Linux handles this PLL ?

Seems like this should be either syscon or clock driver .

[...]
Sumit Garg Feb. 21, 2024, 6:14 a.m. UTC | #2
On Tue, 20 Feb 2024 at 21:02, Marek Vasut <marex@denx.de> wrote:
>
> On 2/20/24 14:10, Sumit Garg wrote:
> > PCIe PHY can use it when there is no external refclock provided.
>
> Commit message needs to be fixed.

How about the following?

    Expose high performance PLL clock, so the PCIe PHY can
    use it when there is no external refclock provided.

>
> > +static int hsio_pll_enable(struct udevice *dev)
> > +{
> > +     struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
> > +     unsigned long start;
> > +     u32 val;
> > +
> > +     /* Setup HSIO PLL */
> > +     val = readl(priv->base + GPR_REG2);
> > +     val &= ~(P_PLL_MASK | M_PLL_MASK | S_PLL_MASK);
> > +     val |= (FIELD_PREP(P_PLL_MASK, 12) | FIELD_PREP(M_PLL_MASK, 800) |
> > +             FIELD_PREP(S_PLL_MASK, 4));
> > +     writel(val, priv->base + GPR_REG2);
>
> clrsetbits_le32()

Ack

>
> > +     /* de-assert PLL reset */
> > +     setbits_le32(priv->base + GPR_REG3, PLL_RST);
> > +
> > +     /* enable PLL */
> > +     setbits_le32(priv->base + GPR_REG3, PLL_CKE);
> > +
> > +     /* Check if PLL is locked */
> > +     start = get_timer(0);
>
> wait_for_bit() or readl_poll_timeout()

Let me use readl_poll_timeout() instead.

>
> > +     for (;;) {
> > +             if (readl(priv->base + GPR_REG1) & PLL_LOCK)
> > +                     break;
> > +
> > +             if (get_timer(start) > 100) {
> > +                     dev_err(dev, "failed to lock HSIO PLL\n");
> > +                     return -ETIMEDOUT;
> > +             }
> > +
> > +             udelay(10);
> > +     }
> > +
> > +     return 0;
> > +}
> > +
> > +static void hsio_pll_disable(struct udevice *dev)
> > +{
> > +     struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
> > +
> > +     /* de-assert PLL reset */
> > +     clrbits_le32(priv->base + GPR_REG3, PLL_RST);
> > +
> > +     /* enable PLL */
> > +     clrbits_le32(priv->base + GPR_REG3, PLL_CKE);
> > +}
> > +
> >   static int imx8mp_hsiomix_on(struct power_domain *power_domain)
> >   {
> >       struct udevice *dev = power_domain->dev;
> > @@ -69,16 +127,23 @@ static int imx8mp_hsiomix_on(struct power_domain *power_domain)
> >       if (ret)
> >               goto err_clk_pcie;
> >
> > -     if (power_domain->id == IMX8MP_HSIOBLK_PD_USB)
> > +     if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) {
> >               setbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN);
> > -     else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE)
> > +     } else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE) {
> >               setbits_le32(priv->base + GPR_REG0, PCIE_CLOCK_MODULE_EN);
> > -     else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY)
> > +     } else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY) {
> >               setbits_le32(priv->base + GPR_REG0, PCIE_PHY_APB_RST |
> >                                                   PCIE_PHY_INIT_RST);
> >
> > +             ret = hsio_pll_enable(dev);
>
> Is this how Linux handles this PLL ?
>
> Seems like this should be either syscon or clock driver .

It isn't similar to what Linux does but I can't find suitable
infrastructure in U-Boot to expose it as a regular clock. Are there
any APIs available similar to devm_of_clk_add_hw_provider() in Linux?

-Sumit

>
> [...]
Marek Vasut Feb. 21, 2024, 9:37 a.m. UTC | #3
On 2/21/24 07:14, Sumit Garg wrote:
> On Tue, 20 Feb 2024 at 21:02, Marek Vasut <marex@denx.de> wrote:
>>
>> On 2/20/24 14:10, Sumit Garg wrote:
>>> PCIe PHY can use it when there is no external refclock provided.
>>
>> Commit message needs to be fixed.
> 
> How about the following?
> 
>      Expose high performance PLL clock, so the PCIe PHY can
>      use it when there is no external refclock provided.

If this code is imported from Linux
2cbee26e5d59 ("soc: imx: imx8mp-blk-ctrl: expose high performance PLL 
clock")
then just include that reference too .

[...]

>>> @@ -69,16 +127,23 @@ static int imx8mp_hsiomix_on(struct power_domain *power_domain)
>>>        if (ret)
>>>                goto err_clk_pcie;
>>>
>>> -     if (power_domain->id == IMX8MP_HSIOBLK_PD_USB)
>>> +     if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) {
>>>                setbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN);
>>> -     else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE)
>>> +     } else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE) {
>>>                setbits_le32(priv->base + GPR_REG0, PCIE_CLOCK_MODULE_EN);
>>> -     else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY)
>>> +     } else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY) {
>>>                setbits_le32(priv->base + GPR_REG0, PCIE_PHY_APB_RST |
>>>                                                    PCIE_PHY_INIT_RST);
>>>
>>> +             ret = hsio_pll_enable(dev);
>>
>> Is this how Linux handles this PLL ?
>>
>> Seems like this should be either syscon or clock driver .
> 
> It isn't similar to what Linux does but I can't find suitable
> infrastructure in U-Boot to expose it as a regular clock. Are there
> any APIs available similar to devm_of_clk_add_hw_provider() in Linux?

Have a look at the very end of:

drivers/clk/renesas/clk-rcar-gen3.c

that registers clock and reset drivers for the same IP at the same 
address. In this case, you would register power domain and clock drivers 
instead .
diff mbox series

Patch

diff --git a/drivers/power/domain/imx8mp-hsiomix.c b/drivers/power/domain/imx8mp-hsiomix.c
index 62145e0261b..4cefe642724 100644
--- a/drivers/power/domain/imx8mp-hsiomix.c
+++ b/drivers/power/domain/imx8mp-hsiomix.c
@@ -9,6 +9,8 @@ 
 #include <dm.h>
 #include <dm/device.h>
 #include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
 #include <power-domain-uclass.h>
 
 #include <dt-bindings/power/imx8mp-power.h>
@@ -18,6 +20,15 @@ 
 #define  USB_CLOCK_MODULE_EN	BIT(1)
 #define  PCIE_PHY_APB_RST	BIT(4)
 #define  PCIE_PHY_INIT_RST	BIT(5)
+#define GPR_REG1		0x4
+#define  PLL_LOCK		BIT(13)
+#define GPR_REG2		0x8
+#define  P_PLL_MASK		GENMASK(5, 0)
+#define  M_PLL_MASK		GENMASK(15, 6)
+#define  S_PLL_MASK		GENMASK(18, 16)
+#define GPR_REG3		0xc
+#define  PLL_CKE		BIT(17)
+#define  PLL_RST		BIT(31)
 
 struct imx8mp_hsiomix_priv {
 	void __iomem *base;
@@ -31,6 +42,53 @@  struct imx8mp_hsiomix_priv {
 	struct power_domain pd_pcie_phy;
 };
 
+static int hsio_pll_enable(struct udevice *dev)
+{
+	struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
+	unsigned long start;
+	u32 val;
+
+	/* Setup HSIO PLL */
+	val = readl(priv->base + GPR_REG2);
+	val &= ~(P_PLL_MASK | M_PLL_MASK | S_PLL_MASK);
+	val |= (FIELD_PREP(P_PLL_MASK, 12) | FIELD_PREP(M_PLL_MASK, 800) |
+		FIELD_PREP(S_PLL_MASK, 4));
+	writel(val, priv->base + GPR_REG2);
+
+	/* de-assert PLL reset */
+	setbits_le32(priv->base + GPR_REG3, PLL_RST);
+
+	/* enable PLL */
+	setbits_le32(priv->base + GPR_REG3, PLL_CKE);
+
+	/* Check if PLL is locked */
+	start = get_timer(0);
+	for (;;) {
+		if (readl(priv->base + GPR_REG1) & PLL_LOCK)
+			break;
+
+		if (get_timer(start) > 100) {
+			dev_err(dev, "failed to lock HSIO PLL\n");
+			return -ETIMEDOUT;
+		}
+
+		udelay(10);
+	}
+
+	return 0;
+}
+
+static void hsio_pll_disable(struct udevice *dev)
+{
+	struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
+
+	/* de-assert PLL reset */
+	clrbits_le32(priv->base + GPR_REG3, PLL_RST);
+
+	/* enable PLL */
+	clrbits_le32(priv->base + GPR_REG3, PLL_CKE);
+}
+
 static int imx8mp_hsiomix_on(struct power_domain *power_domain)
 {
 	struct udevice *dev = power_domain->dev;
@@ -69,16 +127,23 @@  static int imx8mp_hsiomix_on(struct power_domain *power_domain)
 	if (ret)
 		goto err_clk_pcie;
 
-	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB)
+	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) {
 		setbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN);
-	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE)
+	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE) {
 		setbits_le32(priv->base + GPR_REG0, PCIE_CLOCK_MODULE_EN);
-	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY)
+	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY) {
 		setbits_le32(priv->base + GPR_REG0, PCIE_PHY_APB_RST |
 						    PCIE_PHY_INIT_RST);
 
+		ret = hsio_pll_enable(dev);
+		if (ret)
+			goto err_hsio_pll;
+	}
+
 	return 0;
 
+err_hsio_pll:
+	clk_disable(&priv->clk_pcie);
 err_clk_pcie:
 	clk_disable(&priv->clk_usb);
 err_clk_usb:
@@ -93,13 +158,15 @@  static int imx8mp_hsiomix_off(struct power_domain *power_domain)
 	struct udevice *dev = power_domain->dev;
 	struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
 
-	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB)
+	if (power_domain->id == IMX8MP_HSIOBLK_PD_USB) {
 		clrbits_le32(priv->base + GPR_REG0, USB_CLOCK_MODULE_EN);
-	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE)
+	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE) {
 		clrbits_le32(priv->base + GPR_REG0, PCIE_CLOCK_MODULE_EN);
-	else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY)
+	} else if (power_domain->id == IMX8MP_HSIOBLK_PD_PCIE_PHY) {
 		clrbits_le32(priv->base + GPR_REG0, PCIE_PHY_APB_RST |
 						    PCIE_PHY_INIT_RST);
+		hsio_pll_disable(dev);
+	}
 
 	clk_disable(&priv->clk_usb);
 	clk_disable(&priv->clk_pcie);