2022-08-04 14:27:10 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
/*
|
|
|
|
* Copyright (C) 2022 Sumit Garg <sumit.garg@linaro.org>
|
|
|
|
*
|
|
|
|
* Based on Linux driver
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <common.h>
|
|
|
|
#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;
|
|
|
|
|
2023-03-13 00:32:04 +00:00
|
|
|
priv->base = dev_read_addr_ptr(dev);
|
|
|
|
if (!priv->base)
|
2022-08-04 14:27:10 +00:00
|
|
|
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),
|
|
|
|
};
|