blob: 04f0f0e7817d0f2bbd38c814bd5b5ca6507511ff [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (C) 2023 Bhupesh Sharma <bhupesh.sharma@linaro.org>
*
* Based on Linux driver
*/
#include <dm.h>
#include <dm/device_compat.h>
#include <dm/devres.h>
#include <generic-phy.h>
#include <malloc.h>
#include <reset.h>
#include <asm/io.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#define USB2_PHY_USB_PHY_UTMI_CTRL0 (0x3c)
#define SLEEPM BIT(0)
#define OPMODE_MASK GENMASK(4, 3)
#define OPMODE_NORMAL (0x00)
#define OPMODE_NONDRIVING BIT(3)
#define TERMSEL BIT(5)
#define USB2_PHY_USB_PHY_UTMI_CTRL5 (0x50)
#define POR BIT(1)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0 (0x54)
#define SIDDQ BIT(2)
#define RETENABLEN BIT(3)
#define FSEL_MASK GENMASK(6, 4)
#define FSEL_DEFAULT (0x3 << 4)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1 (0x58)
#define VBUSVLDEXTSEL0 BIT(4)
#define PLLBTUNE BIT(5)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2 (0x5c)
#define VREGBYPASS BIT(0)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL1 (0x60)
#define VBUSVLDEXT0 BIT(0)
#define USB2_PHY_USB_PHY_HS_PHY_CTRL2 (0x64)
#define USB2_AUTO_RESUME BIT(0)
#define USB2_SUSPEND_N BIT(2)
#define USB2_SUSPEND_N_SEL BIT(3)
#define USB2_PHY_USB_PHY_CFG0 (0x94)
#define UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN BIT(0)
#define UTMI_PHY_CMN_CTRL_OVERRIDE_EN BIT(1)
#define USB2_PHY_USB_PHY_REFCLK_CTRL (0xa0)
#define REFCLK_SEL_MASK GENMASK(1, 0)
#define REFCLK_SEL_DEFAULT (0x2 << 0)
struct qcom_snps_hsphy {
void __iomem *base;
struct reset_ctl_bulk resets;
};
/*
* We should just be able to use clrsetbits_le32() here, but this results
* in crashes on some boards. This is suspected to be because of some bus
* arbitration quirks with the PHY (i.e. it takes several bus clock cycles
* for the write to actually go through). The readl_relaxed() at the end will
* block until the write is completed (and all registers updated), and thus
* ensure that we don't access the PHY registers when they're in an
* undetermined state.
*/
static inline void qcom_snps_hsphy_write_mask(void __iomem *base, u32 offset,
u32 mask, u32 val)
{
u32 reg;
reg = readl_relaxed(base + offset);
reg &= ~mask;
reg |= val & mask;
writel_relaxed(reg, base + offset);
/* Ensure above write is completed */
readl_relaxed(base + offset);
}
static int qcom_snps_hsphy_usb_init(struct phy *phy)
{
struct qcom_snps_hsphy *priv = dev_get_priv(phy->dev);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_CFG0,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_UTMI_CTRL5, POR,
POR);
qcom_snps_hsphy_write_mask(priv->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, FSEL_MASK, 0);
qcom_snps_hsphy_write_mask(priv->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1,
PLLBTUNE, PLLBTUNE);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_REFCLK_CTRL,
REFCLK_SEL_DEFAULT, REFCLK_SEL_MASK);
qcom_snps_hsphy_write_mask(priv->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1,
VBUSVLDEXTSEL0, VBUSVLDEXTSEL0);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_HS_PHY_CTRL1,
VBUSVLDEXT0, VBUSVLDEXT0);
qcom_snps_hsphy_write_mask(priv->base,
USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2,
VREGBYPASS, VREGBYPASS);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N,
USB2_SUSPEND_N_SEL | USB2_SUSPEND_N);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_UTMI_CTRL0,
SLEEPM, SLEEPM);
qcom_snps_hsphy_write_mask(
priv->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, SIDDQ, 0);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_UTMI_CTRL5, POR,
0);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2,
USB2_SUSPEND_N_SEL, 0);
qcom_snps_hsphy_write_mask(priv->base, USB2_PHY_USB_PHY_CFG0,
UTMI_PHY_CMN_CTRL_OVERRIDE_EN, 0);
return 0;
}
static int qcom_snps_hsphy_power_on(struct phy *phy)
{
struct qcom_snps_hsphy *priv = dev_get_priv(phy->dev);
int ret;
ret = reset_deassert_bulk(&priv->resets);
if (ret)
return ret;
ret = qcom_snps_hsphy_usb_init(phy);
if (ret)
return ret;
return 0;
}
static int qcom_snps_hsphy_power_off(struct phy *phy)
{
struct qcom_snps_hsphy *priv = dev_get_priv(phy->dev);
reset_assert_bulk(&priv->resets);
return 0;
}
static int qcom_snps_hsphy_phy_probe(struct udevice *dev)
{
struct qcom_snps_hsphy *priv = dev_get_priv(dev);
int ret;
priv->base = dev_read_addr_ptr(dev);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
ret = reset_get_bulk(dev, &priv->resets);
if (ret < 0) {
printf("failed to get resets, ret = %d\n", ret);
return ret;
}
reset_deassert_bulk(&priv->resets);
return 0;
}
static struct phy_ops qcom_snps_hsphy_phy_ops = {
.power_on = qcom_snps_hsphy_power_on,
.power_off = qcom_snps_hsphy_power_off,
};
static const struct udevice_id qcom_snps_hsphy_phy_ids[] = {
{ .compatible = "qcom,sm8150-usb-hs-phy" },
{ .compatible = "qcom,usb-snps-hs-5nm-phy" },
{ .compatible = "qcom,usb-snps-hs-7nm-phy" },
{ .compatible = "qcom,usb-snps-femto-v2-phy" },
{}
};
U_BOOT_DRIVER(qcom_usb_qcom_snps_hsphy) = {
.name = "qcom-snps-hsphy",
.id = UCLASS_PHY,
.of_match = qcom_snps_hsphy_phy_ids,
.ops = &qcom_snps_hsphy_phy_ops,
.probe = qcom_snps_hsphy_phy_probe,
.priv_auto = sizeof(struct qcom_snps_hsphy),
};