blob: c344809a6086556493450c39928d3e45901c77c3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2022 Sumit Garg <sumit.garg@linaro.org>
*
* Based on Linux driver
*/
#include <dm.h>
#include <generic-phy.h>
#include <reset.h>
#include <clk.h>
#include <asm/io.h>
#include <linux/delay.h>
/* PHY register and bit definitions */
#define PHY_CTRL_COMMON0 0x078
#define SIDDQ BIT(2)
struct hsphy_init_seq {
int offset;
int val;
int delay;
};
struct hsphy_data {
const struct hsphy_init_seq *init_seq;
unsigned int init_seq_num;
};
struct hsphy_priv {
void __iomem *base;
struct clk_bulk clks;
struct reset_ctl phy_rst;
struct reset_ctl por_rst;
const struct hsphy_data *data;
};
static int hsphy_power_on(struct phy *phy)
{
struct hsphy_priv *priv = dev_get_priv(phy->dev);
u32 val;
val = readb(priv->base + PHY_CTRL_COMMON0);
val &= ~SIDDQ;
writeb(val, priv->base + PHY_CTRL_COMMON0);
return 0;
}
static int hsphy_power_off(struct phy *phy)
{
struct hsphy_priv *priv = dev_get_priv(phy->dev);
u32 val;
val = readb(priv->base + PHY_CTRL_COMMON0);
val |= SIDDQ;
writeb(val, priv->base + PHY_CTRL_COMMON0);
return 0;
}
static int hsphy_reset(struct hsphy_priv *priv)
{
int ret;
ret = reset_assert(&priv->phy_rst);
if (ret)
return ret;
udelay(10);
ret = reset_deassert(&priv->phy_rst);
if (ret)
return ret;
udelay(80);
return 0;
}
static void hsphy_init_sequence(struct hsphy_priv *priv)
{
const struct hsphy_data *data = priv->data;
const struct hsphy_init_seq *seq;
int i;
/* Device match data is optional. */
if (!data)
return;
seq = data->init_seq;
for (i = 0; i < data->init_seq_num; i++, seq++) {
writeb(seq->val, priv->base + seq->offset);
if (seq->delay)
udelay(seq->delay);
}
}
static int hsphy_por_reset(struct hsphy_priv *priv)
{
int ret;
u32 val;
ret = reset_assert(&priv->por_rst);
if (ret)
return ret;
/*
* The Femto PHY is POR reset in the following scenarios.
*
* 1. After overriding the parameter registers.
* 2. Low power mode exit from PHY retention.
*
* Ensure that SIDDQ is cleared before bringing the PHY
* out of reset.
*/
val = readb(priv->base + PHY_CTRL_COMMON0);
val &= ~SIDDQ;
writeb(val, priv->base + PHY_CTRL_COMMON0);
/*
* As per databook, 10 usec delay is required between
* PHY POR assert and de-assert.
*/
udelay(10);
ret = reset_deassert(&priv->por_rst);
if (ret)
return ret;
/*
* As per databook, it takes 75 usec for PHY to stabilize
* after the reset.
*/
udelay(80);
return 0;
}
static int hsphy_clk_init(struct udevice *dev, struct hsphy_priv *priv)
{
int ret;
ret = clk_get_bulk(dev, &priv->clks);
if (ret == -ENOSYS || ret == -ENOENT)
return 0;
if (ret)
return ret;
ret = clk_enable_bulk(&priv->clks);
if (ret) {
clk_release_bulk(&priv->clks);
return ret;
}
return 0;
}
static int hsphy_init(struct phy *phy)
{
struct hsphy_priv *priv = dev_get_priv(phy->dev);
int ret;
ret = hsphy_clk_init(phy->dev, priv);
if (ret)
return ret;
ret = hsphy_reset(priv);
if (ret)
return ret;
hsphy_init_sequence(priv);
hsphy_por_reset(priv);
if (ret)
return ret;
return 0;
}
static int hsphy_probe(struct udevice *dev)
{
struct hsphy_priv *priv = dev_get_priv(dev);
int ret;
priv->base = dev_read_addr_ptr(dev);
if (!priv->base)
return -EINVAL;
ret = reset_get_by_name(dev, "phy", &priv->phy_rst);
if (ret)
return ret;
ret = reset_get_by_name(dev, "por", &priv->por_rst);
if (ret)
return ret;
priv->data = (const struct hsphy_data *)dev_get_driver_data(dev);
return 0;
}
static struct phy_ops hsphy_ops = {
.power_on = hsphy_power_on,
.power_off = hsphy_power_off,
.init = hsphy_init,
};
/*
* The macro is used to define an initialization sequence. Each tuple
* is meant to program 'value' into phy register at 'offset' with 'delay'
* in us followed.
*/
#define HSPHY_INIT_CFG(o, v, d) { .offset = o, .val = v, .delay = d, }
static const struct hsphy_init_seq init_seq_femtophy[] = {
HSPHY_INIT_CFG(0xc0, 0x01, 0),
HSPHY_INIT_CFG(0xe8, 0x0d, 0),
HSPHY_INIT_CFG(0x74, 0x12, 0),
HSPHY_INIT_CFG(0x98, 0x63, 0),
HSPHY_INIT_CFG(0x9c, 0x03, 0),
HSPHY_INIT_CFG(0xa0, 0x1d, 0),
HSPHY_INIT_CFG(0xa4, 0x03, 0),
HSPHY_INIT_CFG(0x8c, 0x23, 0),
HSPHY_INIT_CFG(0x78, 0x08, 0),
HSPHY_INIT_CFG(0x7c, 0xdc, 0),
HSPHY_INIT_CFG(0x90, 0xe0, 20),
HSPHY_INIT_CFG(0x74, 0x10, 0),
HSPHY_INIT_CFG(0x90, 0x60, 0),
};
static const struct hsphy_data data_femtophy = {
.init_seq = init_seq_femtophy,
.init_seq_num = ARRAY_SIZE(init_seq_femtophy),
};
static const struct udevice_id hsphy_ids[] = {
{ .compatible = "qcom,usb-hs-28nm-femtophy", .data = (ulong)&data_femtophy },
{ }
};
U_BOOT_DRIVER(qcom_usb_hs_28nm) = {
.name = "qcom-usb-hs-28nm",
.id = UCLASS_PHY,
.of_match = hsphy_ids,
.ops = &hsphy_ops,
.probe = hsphy_probe,
.priv_auto = sizeof(struct hsphy_priv),
};