forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
9.7 KiB
438 lines
9.7 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (c) 2009-2018, Linux Foundation. All rights reserved. |
|
* Copyright (c) 2018-2020, Linaro Limited |
|
*/ |
|
|
|
#include <linux/clk.h> |
|
#include <linux/delay.h> |
|
#include <linux/io.h> |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/of.h> |
|
#include <linux/of_graph.h> |
|
#include <linux/phy/phy.h> |
|
#include <linux/platform_device.h> |
|
#include <linux/regulator/consumer.h> |
|
#include <linux/reset.h> |
|
#include <linux/slab.h> |
|
|
|
/* PHY register and bit definitions */ |
|
#define PHY_CTRL_COMMON0 0x078 |
|
#define SIDDQ BIT(2) |
|
#define PHY_IRQ_CMD 0x0d0 |
|
#define PHY_INTR_MASK0 0x0d4 |
|
#define PHY_INTR_CLEAR0 0x0dc |
|
#define DPDM_MASK 0x1e |
|
#define DP_1_0 BIT(4) |
|
#define DP_0_1 BIT(3) |
|
#define DM_1_0 BIT(2) |
|
#define DM_0_1 BIT(1) |
|
|
|
enum hsphy_voltage { |
|
VOL_NONE, |
|
VOL_MIN, |
|
VOL_MAX, |
|
VOL_NUM, |
|
}; |
|
|
|
enum hsphy_vreg { |
|
VDD, |
|
VDDA_1P8, |
|
VDDA_3P3, |
|
VREG_NUM, |
|
}; |
|
|
|
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_data *clks; |
|
int num_clks; |
|
struct reset_control *phy_reset; |
|
struct reset_control *por_reset; |
|
struct regulator_bulk_data vregs[VREG_NUM]; |
|
const struct hsphy_data *data; |
|
enum phy_mode mode; |
|
}; |
|
|
|
static int qcom_snps_hsphy_set_mode(struct phy *phy, enum phy_mode mode, |
|
int submode) |
|
{ |
|
struct hsphy_priv *priv = phy_get_drvdata(phy); |
|
|
|
priv->mode = PHY_MODE_INVALID; |
|
|
|
if (mode > 0) |
|
priv->mode = mode; |
|
|
|
return 0; |
|
} |
|
|
|
static void qcom_snps_hsphy_enable_hv_interrupts(struct hsphy_priv *priv) |
|
{ |
|
u32 val; |
|
|
|
/* Clear any existing interrupts before enabling the interrupts */ |
|
val = readb(priv->base + PHY_INTR_CLEAR0); |
|
val |= DPDM_MASK; |
|
writeb(val, priv->base + PHY_INTR_CLEAR0); |
|
|
|
writeb(0x0, priv->base + PHY_IRQ_CMD); |
|
usleep_range(200, 220); |
|
writeb(0x1, priv->base + PHY_IRQ_CMD); |
|
|
|
/* Make sure the interrupts are cleared */ |
|
usleep_range(200, 220); |
|
|
|
val = readb(priv->base + PHY_INTR_MASK0); |
|
switch (priv->mode) { |
|
case PHY_MODE_USB_HOST_HS: |
|
case PHY_MODE_USB_HOST_FS: |
|
case PHY_MODE_USB_DEVICE_HS: |
|
case PHY_MODE_USB_DEVICE_FS: |
|
val |= DP_1_0 | DM_0_1; |
|
break; |
|
case PHY_MODE_USB_HOST_LS: |
|
case PHY_MODE_USB_DEVICE_LS: |
|
val |= DP_0_1 | DM_1_0; |
|
break; |
|
default: |
|
/* No device connected */ |
|
val |= DP_0_1 | DM_0_1; |
|
break; |
|
} |
|
writeb(val, priv->base + PHY_INTR_MASK0); |
|
} |
|
|
|
static void qcom_snps_hsphy_disable_hv_interrupts(struct hsphy_priv *priv) |
|
{ |
|
u32 val; |
|
|
|
val = readb(priv->base + PHY_INTR_MASK0); |
|
val &= ~DPDM_MASK; |
|
writeb(val, priv->base + PHY_INTR_MASK0); |
|
|
|
/* Clear any pending interrupts */ |
|
val = readb(priv->base + PHY_INTR_CLEAR0); |
|
val |= DPDM_MASK; |
|
writeb(val, priv->base + PHY_INTR_CLEAR0); |
|
|
|
writeb(0x0, priv->base + PHY_IRQ_CMD); |
|
usleep_range(200, 220); |
|
|
|
writeb(0x1, priv->base + PHY_IRQ_CMD); |
|
usleep_range(200, 220); |
|
} |
|
|
|
static void qcom_snps_hsphy_enter_retention(struct hsphy_priv *priv) |
|
{ |
|
u32 val; |
|
|
|
val = readb(priv->base + PHY_CTRL_COMMON0); |
|
val |= SIDDQ; |
|
writeb(val, priv->base + PHY_CTRL_COMMON0); |
|
} |
|
|
|
static void qcom_snps_hsphy_exit_retention(struct hsphy_priv *priv) |
|
{ |
|
u32 val; |
|
|
|
val = readb(priv->base + PHY_CTRL_COMMON0); |
|
val &= ~SIDDQ; |
|
writeb(val, priv->base + PHY_CTRL_COMMON0); |
|
} |
|
|
|
static int qcom_snps_hsphy_power_on(struct phy *phy) |
|
{ |
|
struct hsphy_priv *priv = phy_get_drvdata(phy); |
|
int ret; |
|
|
|
ret = regulator_bulk_enable(VREG_NUM, priv->vregs); |
|
if (ret) |
|
return ret; |
|
|
|
qcom_snps_hsphy_disable_hv_interrupts(priv); |
|
qcom_snps_hsphy_exit_retention(priv); |
|
|
|
return 0; |
|
} |
|
|
|
static int qcom_snps_hsphy_power_off(struct phy *phy) |
|
{ |
|
struct hsphy_priv *priv = phy_get_drvdata(phy); |
|
|
|
qcom_snps_hsphy_enter_retention(priv); |
|
qcom_snps_hsphy_enable_hv_interrupts(priv); |
|
regulator_bulk_disable(VREG_NUM, priv->vregs); |
|
|
|
return 0; |
|
} |
|
|
|
static int qcom_snps_hsphy_reset(struct hsphy_priv *priv) |
|
{ |
|
int ret; |
|
|
|
ret = reset_control_assert(priv->phy_reset); |
|
if (ret) |
|
return ret; |
|
|
|
usleep_range(10, 15); |
|
|
|
ret = reset_control_deassert(priv->phy_reset); |
|
if (ret) |
|
return ret; |
|
|
|
usleep_range(80, 100); |
|
|
|
return 0; |
|
} |
|
|
|
static void qcom_snps_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) |
|
usleep_range(seq->delay, seq->delay + 10); |
|
} |
|
} |
|
|
|
static int qcom_snps_hsphy_por_reset(struct hsphy_priv *priv) |
|
{ |
|
int ret; |
|
|
|
ret = reset_control_assert(priv->por_reset); |
|
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. |
|
*/ |
|
qcom_snps_hsphy_exit_retention(priv); |
|
|
|
/* |
|
* As per databook, 10 usec delay is required between |
|
* PHY POR assert and de-assert. |
|
*/ |
|
usleep_range(10, 20); |
|
ret = reset_control_deassert(priv->por_reset); |
|
if (ret) |
|
return ret; |
|
|
|
/* |
|
* As per databook, it takes 75 usec for PHY to stabilize |
|
* after the reset. |
|
*/ |
|
usleep_range(80, 100); |
|
|
|
return 0; |
|
} |
|
|
|
static int qcom_snps_hsphy_init(struct phy *phy) |
|
{ |
|
struct hsphy_priv *priv = phy_get_drvdata(phy); |
|
int ret; |
|
|
|
ret = clk_bulk_prepare_enable(priv->num_clks, priv->clks); |
|
if (ret) |
|
return ret; |
|
|
|
ret = qcom_snps_hsphy_reset(priv); |
|
if (ret) |
|
goto disable_clocks; |
|
|
|
qcom_snps_hsphy_init_sequence(priv); |
|
|
|
ret = qcom_snps_hsphy_por_reset(priv); |
|
if (ret) |
|
goto disable_clocks; |
|
|
|
return 0; |
|
|
|
disable_clocks: |
|
clk_bulk_disable_unprepare(priv->num_clks, priv->clks); |
|
return ret; |
|
} |
|
|
|
static int qcom_snps_hsphy_exit(struct phy *phy) |
|
{ |
|
struct hsphy_priv *priv = phy_get_drvdata(phy); |
|
|
|
clk_bulk_disable_unprepare(priv->num_clks, priv->clks); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct phy_ops qcom_snps_hsphy_ops = { |
|
.init = qcom_snps_hsphy_init, |
|
.exit = qcom_snps_hsphy_exit, |
|
.power_on = qcom_snps_hsphy_power_on, |
|
.power_off = qcom_snps_hsphy_power_off, |
|
.set_mode = qcom_snps_hsphy_set_mode, |
|
.owner = THIS_MODULE, |
|
}; |
|
|
|
static const char * const qcom_snps_hsphy_clks[] = { |
|
"ref", |
|
"ahb", |
|
"sleep", |
|
}; |
|
|
|
static int qcom_snps_hsphy_probe(struct platform_device *pdev) |
|
{ |
|
struct device *dev = &pdev->dev; |
|
struct phy_provider *provider; |
|
struct hsphy_priv *priv; |
|
struct phy *phy; |
|
int ret; |
|
int i; |
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->base = devm_platform_ioremap_resource(pdev, 0); |
|
if (IS_ERR(priv->base)) |
|
return PTR_ERR(priv->base); |
|
|
|
priv->num_clks = ARRAY_SIZE(qcom_snps_hsphy_clks); |
|
priv->clks = devm_kcalloc(dev, priv->num_clks, sizeof(*priv->clks), |
|
GFP_KERNEL); |
|
if (!priv->clks) |
|
return -ENOMEM; |
|
|
|
for (i = 0; i < priv->num_clks; i++) |
|
priv->clks[i].id = qcom_snps_hsphy_clks[i]; |
|
|
|
ret = devm_clk_bulk_get(dev, priv->num_clks, priv->clks); |
|
if (ret) |
|
return ret; |
|
|
|
priv->phy_reset = devm_reset_control_get_exclusive(dev, "phy"); |
|
if (IS_ERR(priv->phy_reset)) |
|
return PTR_ERR(priv->phy_reset); |
|
|
|
priv->por_reset = devm_reset_control_get_exclusive(dev, "por"); |
|
if (IS_ERR(priv->por_reset)) |
|
return PTR_ERR(priv->por_reset); |
|
|
|
priv->vregs[VDD].supply = "vdd"; |
|
priv->vregs[VDDA_1P8].supply = "vdda1p8"; |
|
priv->vregs[VDDA_3P3].supply = "vdda3p3"; |
|
|
|
ret = devm_regulator_bulk_get(dev, VREG_NUM, priv->vregs); |
|
if (ret) |
|
return ret; |
|
|
|
/* Get device match data */ |
|
priv->data = device_get_match_data(dev); |
|
|
|
phy = devm_phy_create(dev, dev->of_node, &qcom_snps_hsphy_ops); |
|
if (IS_ERR(phy)) |
|
return PTR_ERR(phy); |
|
|
|
phy_set_drvdata(phy, priv); |
|
|
|
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); |
|
if (IS_ERR(provider)) |
|
return PTR_ERR(provider); |
|
|
|
ret = regulator_set_load(priv->vregs[VDDA_1P8].consumer, 19000); |
|
if (ret < 0) |
|
return ret; |
|
|
|
ret = regulator_set_load(priv->vregs[VDDA_3P3].consumer, 16000); |
|
if (ret < 0) |
|
goto unset_1p8_load; |
|
|
|
return 0; |
|
|
|
unset_1p8_load: |
|
regulator_set_load(priv->vregs[VDDA_1P8].consumer, 0); |
|
|
|
return ret; |
|
} |
|
|
|
/* |
|
* 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_init_seq init_seq_mdm9607[] = { |
|
HSPHY_INIT_CFG(0x80, 0x44, 0), |
|
HSPHY_INIT_CFG(0x81, 0x38, 0), |
|
HSPHY_INIT_CFG(0x82, 0x24, 0), |
|
HSPHY_INIT_CFG(0x83, 0x13, 0), |
|
}; |
|
|
|
static const struct hsphy_data hsphy_data_femtophy = { |
|
.init_seq = init_seq_femtophy, |
|
.init_seq_num = ARRAY_SIZE(init_seq_femtophy), |
|
}; |
|
|
|
static const struct hsphy_data hsphy_data_mdm9607 = { |
|
.init_seq = init_seq_mdm9607, |
|
.init_seq_num = ARRAY_SIZE(init_seq_mdm9607), |
|
}; |
|
|
|
static const struct of_device_id qcom_snps_hsphy_match[] = { |
|
{ .compatible = "qcom,usb-hs-28nm-femtophy", .data = &hsphy_data_femtophy, }, |
|
{ .compatible = "qcom,usb-hs-28nm-mdm9607", .data = &hsphy_data_mdm9607, }, |
|
{ }, |
|
}; |
|
MODULE_DEVICE_TABLE(of, qcom_snps_hsphy_match); |
|
|
|
static struct platform_driver qcom_snps_hsphy_driver = { |
|
.probe = qcom_snps_hsphy_probe, |
|
.driver = { |
|
.name = "qcom,usb-hs-28nm-phy", |
|
.of_match_table = qcom_snps_hsphy_match, |
|
}, |
|
}; |
|
module_platform_driver(qcom_snps_hsphy_driver); |
|
|
|
MODULE_DESCRIPTION("Qualcomm 28nm Hi-Speed USB PHY driver"); |
|
MODULE_LICENSE("GPL v2");
|
|
|