blob: 455ad53ef525e18ae45068fa5d8c2be8a1b79335 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Marek Vasut <marex@denx.de>
*/
#include <asm/io.h>
#include <clk.h>
#include <clk-uclass.h>
#include <dm.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <power-domain-uclass.h>
#include <dt-bindings/power/imx8mp-power.h>
#define GPR_REG0 0x0
#define PCIE_CLOCK_MODULE_EN BIT(0)
#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;
struct clk clk_usb;
struct clk clk_pcie;
struct power_domain pd_bus;
struct power_domain pd_usb;
struct power_domain pd_pcie;
struct power_domain pd_usb_phy1;
struct power_domain pd_usb_phy2;
struct power_domain pd_pcie_phy;
};
static int imx8mp_hsiomix_set(struct power_domain *power_domain, bool power_on)
{
struct udevice *dev = power_domain->dev;
struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
struct power_domain *domain = NULL;
struct clk *clk = NULL;
u32 gpr_reg0_bits = 0;
int ret;
switch (power_domain->id) {
case IMX8MP_HSIOBLK_PD_USB:
domain = &priv->pd_usb;
clk = &priv->clk_usb;
gpr_reg0_bits |= USB_CLOCK_MODULE_EN;
break;
case IMX8MP_HSIOBLK_PD_USB_PHY1:
domain = &priv->pd_usb_phy1;
break;
case IMX8MP_HSIOBLK_PD_USB_PHY2:
domain = &priv->pd_usb_phy2;
break;
case IMX8MP_HSIOBLK_PD_PCIE:
domain = &priv->pd_pcie;
clk = &priv->clk_pcie;
gpr_reg0_bits |= PCIE_CLOCK_MODULE_EN;
break;
case IMX8MP_HSIOBLK_PD_PCIE_PHY:
domain = &priv->pd_pcie_phy;
/* Bits to deassert PCIe PHY reset */
gpr_reg0_bits |= PCIE_PHY_APB_RST | PCIE_PHY_INIT_RST;
break;
default:
dev_err(dev, "unknown power domain id: %ld\n",
power_domain->id);
return -EINVAL;
}
if (power_on) {
ret = power_domain_on(&priv->pd_bus);
if (ret)
return ret;
ret = power_domain_on(domain);
if (ret)
goto err_pd;
if (clk) {
ret = clk_enable(clk);
if (ret)
goto err_clk;
}
if (gpr_reg0_bits)
setbits_le32(priv->base + GPR_REG0, gpr_reg0_bits);
} else {
if (gpr_reg0_bits)
clrbits_le32(priv->base + GPR_REG0, gpr_reg0_bits);
if (clk)
clk_disable(clk);
power_domain_off(domain);
power_domain_off(&priv->pd_bus);
}
return 0;
err_clk:
power_domain_off(domain);
err_pd:
power_domain_off(&priv->pd_bus);
return ret;
}
static int imx8mp_hsiomix_on(struct power_domain *power_domain)
{
return imx8mp_hsiomix_set(power_domain, true);
}
static int imx8mp_hsiomix_off(struct power_domain *power_domain)
{
return imx8mp_hsiomix_set(power_domain, false);
}
static int imx8mp_hsiomix_of_xlate(struct power_domain *power_domain,
struct ofnode_phandle_args *args)
{
power_domain->id = args->args[0];
return 0;
}
static int hsio_pll_clk_enable(struct clk *clk)
{
void *base = (void *)dev_get_driver_data(clk->dev);
u32 val;
int ret;
/* Setup HSIO PLL as 100 MHz output clock */
clrsetbits_le32(base + GPR_REG2,
P_PLL_MASK | M_PLL_MASK | S_PLL_MASK,
FIELD_PREP(P_PLL_MASK, 12) |
FIELD_PREP(M_PLL_MASK, 800) |
FIELD_PREP(S_PLL_MASK, 4));
/* de-assert PLL reset */
setbits_le32(base + GPR_REG3, PLL_RST);
/* enable PLL */
setbits_le32(base + GPR_REG3, PLL_CKE);
/* Check if PLL is locked */
ret = readl_poll_sleep_timeout(base + GPR_REG1, val,
val & PLL_LOCK, 10, 100000);
if (ret)
dev_err(clk->dev, "failed to lock HSIO PLL\n");
return ret;
}
static int hsio_pll_clk_disable(struct clk *clk)
{
void *base = (void *)dev_get_driver_data(clk->dev);
clrbits_le32(base + GPR_REG3, PLL_CKE | PLL_RST);
return 0;
}
static const struct clk_ops hsio_pll_clk_ops = {
.enable = hsio_pll_clk_enable,
.disable = hsio_pll_clk_disable,
};
U_BOOT_DRIVER(hsio_pll) = {
.name = "hsio-pll",
.id = UCLASS_CLK,
.ops = &hsio_pll_clk_ops,
};
int imx8mp_hsiomix_bind(struct udevice *dev)
{
struct driver *drv;
drv = lists_driver_lookup_name("hsio-pll");
if (!drv)
return -ENOENT;
return device_bind_with_driver_data(dev, drv, "hsio-pll",
(ulong)dev_read_addr_ptr(dev),
dev_ofnode(dev), NULL);
}
static int imx8mp_hsiomix_probe(struct udevice *dev)
{
struct imx8mp_hsiomix_priv *priv = dev_get_priv(dev);
int ret;
priv->base = dev_read_addr_ptr(dev);
ret = clk_get_by_name(dev, "usb", &priv->clk_usb);
if (ret < 0)
return ret;
ret = clk_get_by_name(dev, "pcie", &priv->clk_pcie);
if (ret < 0)
return ret;
ret = power_domain_get_by_name(dev, &priv->pd_bus, "bus");
if (ret < 0)
return ret;
ret = power_domain_get_by_name(dev, &priv->pd_usb, "usb");
if (ret < 0)
goto err_pd_usb;
ret = power_domain_get_by_name(dev, &priv->pd_usb_phy1, "usb-phy1");
if (ret < 0)
goto err_pd_usb_phy1;
ret = power_domain_get_by_name(dev, &priv->pd_usb_phy2, "usb-phy2");
if (ret < 0)
goto err_pd_usb_phy2;
ret = power_domain_get_by_name(dev, &priv->pd_pcie, "pcie");
if (ret < 0)
goto err_pd_pcie;
ret = power_domain_get_by_name(dev, &priv->pd_pcie_phy, "pcie-phy");
if (ret < 0)
goto err_pd_pcie_phy;
return 0;
err_pd_pcie_phy:
power_domain_free(&priv->pd_pcie);
err_pd_pcie:
power_domain_free(&priv->pd_usb_phy2);
err_pd_usb_phy2:
power_domain_free(&priv->pd_usb_phy1);
err_pd_usb_phy1:
power_domain_free(&priv->pd_usb);
err_pd_usb:
power_domain_free(&priv->pd_bus);
return ret;
}
static const struct udevice_id imx8mp_hsiomix_ids[] = {
{ .compatible = "fsl,imx8mp-hsio-blk-ctrl" },
{ }
};
struct power_domain_ops imx8mp_hsiomix_ops = {
.on = imx8mp_hsiomix_on,
.off = imx8mp_hsiomix_off,
.of_xlate = imx8mp_hsiomix_of_xlate,
};
U_BOOT_DRIVER(imx8mp_hsiomix) = {
.name = "imx8mp_hsiomix",
.id = UCLASS_POWER_DOMAIN,
.of_match = imx8mp_hsiomix_ids,
.probe = imx8mp_hsiomix_probe,
.bind = imx8mp_hsiomix_bind,
.priv_auto = sizeof(struct imx8mp_hsiomix_priv),
.ops = &imx8mp_hsiomix_ops,
};