linux/drivers/phy/qualcomm/phy-qcom-uniphy-pcie-28lp.c

332 lines
8.1 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2025, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/units.h>
#define RST_ASSERT_DELAY_MIN_US 100
#define RST_ASSERT_DELAY_MAX_US 150
#define PIPE_CLK_DELAY_MIN_US 5000
#define PIPE_CLK_DELAY_MAX_US 5100
#define CLK_EN_DELAY_MIN_US 30
#define CLK_EN_DELAY_MAX_US 50
#define CDR_CTRL_REG_1 0x80
#define CDR_CTRL_REG_2 0x84
#define CDR_CTRL_REG_3 0x88
#define CDR_CTRL_REG_4 0x8c
#define CDR_CTRL_REG_5 0x90
#define CDR_CTRL_REG_6 0x94
#define CDR_CTRL_REG_7 0x98
#define SSCG_CTRL_REG_1 0x9c
#define SSCG_CTRL_REG_2 0xa0
#define SSCG_CTRL_REG_3 0xa4
#define SSCG_CTRL_REG_4 0xa8
#define SSCG_CTRL_REG_5 0xac
#define SSCG_CTRL_REG_6 0xb0
#define PCS_INTERNAL_CONTROL_2 0x2d8
#define PHY_CFG_PLLCFG 0x220
#define PHY_CFG_EIOS_DTCT_REG 0x3e4
#define PHY_CFG_GEN3_ALIGN_HOLDOFF_TIME 0x3e8
enum qcom_uniphy_pcie_type {
PHY_TYPE_PCIE = 1,
PHY_TYPE_PCIE_GEN2,
PHY_TYPE_PCIE_GEN3,
};
struct qcom_uniphy_pcie_regs {
u32 offset;
u32 val;
};
struct qcom_uniphy_pcie_data {
int lane_offset; /* offset between the lane register bases */
u32 phy_type;
const struct qcom_uniphy_pcie_regs *init_seq;
u32 init_seq_num;
u32 pipe_clk_rate;
};
struct qcom_uniphy_pcie {
struct phy phy;
struct device *dev;
const struct qcom_uniphy_pcie_data *data;
struct clk_bulk_data *clks;
int num_clks;
struct reset_control *resets;
void __iomem *base;
int lanes;
};
#define phy_to_dw_phy(x) container_of((x), struct qca_uni_pcie_phy, phy)
static const struct qcom_uniphy_pcie_regs ipq5018_regs[] = {
{
.offset = SSCG_CTRL_REG_4,
.val = 0x1cb9,
}, {
.offset = SSCG_CTRL_REG_5,
.val = 0x023a,
}, {
.offset = SSCG_CTRL_REG_3,
.val = 0xd360,
}, {
.offset = SSCG_CTRL_REG_1,
.val = 0x1,
}, {
.offset = SSCG_CTRL_REG_2,
.val = 0xeb,
}, {
.offset = CDR_CTRL_REG_4,
.val = 0x3f9,
}, {
.offset = CDR_CTRL_REG_5,
.val = 0x1c9,
}, {
.offset = CDR_CTRL_REG_2,
.val = 0x419,
}, {
.offset = CDR_CTRL_REG_1,
.val = 0x200,
}, {
.offset = PCS_INTERNAL_CONTROL_2,
.val = 0xf101,
},
};
static const struct qcom_uniphy_pcie_regs ipq5332_regs[] = {
{
.offset = PHY_CFG_PLLCFG,
.val = 0x30,
}, {
.offset = PHY_CFG_EIOS_DTCT_REG,
.val = 0x53ef,
}, {
.offset = PHY_CFG_GEN3_ALIGN_HOLDOFF_TIME,
.val = 0xcf,
},
};
static const struct qcom_uniphy_pcie_data ipq5018_data = {
.lane_offset = 0x800,
.phy_type = PHY_TYPE_PCIE_GEN2,
.init_seq = ipq5018_regs,
.init_seq_num = ARRAY_SIZE(ipq5018_regs),
.pipe_clk_rate = 125 * MEGA,
};
static const struct qcom_uniphy_pcie_data ipq5332_data = {
.lane_offset = 0x800,
.phy_type = PHY_TYPE_PCIE_GEN3,
.init_seq = ipq5332_regs,
.init_seq_num = ARRAY_SIZE(ipq5332_regs),
.pipe_clk_rate = 250 * MEGA,
};
static void qcom_uniphy_pcie_init(struct qcom_uniphy_pcie *phy)
{
const struct qcom_uniphy_pcie_data *data = phy->data;
const struct qcom_uniphy_pcie_regs *init_seq;
void __iomem *base = phy->base;
int lane, i;
for (lane = 0; lane < phy->lanes; lane++) {
init_seq = data->init_seq;
for (i = 0; i < data->init_seq_num; i++)
writel(init_seq[i].val, base + init_seq[i].offset);
base += data->lane_offset;
}
}
static int qcom_uniphy_pcie_power_off(struct phy *x)
{
struct qcom_uniphy_pcie *phy = phy_get_drvdata(x);
clk_bulk_disable_unprepare(phy->num_clks, phy->clks);
return reset_control_assert(phy->resets);
}
static int qcom_uniphy_pcie_power_on(struct phy *x)
{
struct qcom_uniphy_pcie *phy = phy_get_drvdata(x);
int ret;
ret = reset_control_assert(phy->resets);
if (ret) {
dev_err(phy->dev, "reset assert failed (%d)\n", ret);
return ret;
}
usleep_range(RST_ASSERT_DELAY_MIN_US, RST_ASSERT_DELAY_MAX_US);
ret = reset_control_deassert(phy->resets);
if (ret) {
dev_err(phy->dev, "reset deassert failed (%d)\n", ret);
return ret;
}
usleep_range(PIPE_CLK_DELAY_MIN_US, PIPE_CLK_DELAY_MAX_US);
ret = clk_bulk_prepare_enable(phy->num_clks, phy->clks);
if (ret) {
dev_err(phy->dev, "clk prepare and enable failed %d\n", ret);
return ret;
}
usleep_range(CLK_EN_DELAY_MIN_US, CLK_EN_DELAY_MAX_US);
qcom_uniphy_pcie_init(phy);
return 0;
}
static inline int qcom_uniphy_pcie_get_resources(struct platform_device *pdev,
struct qcom_uniphy_pcie *phy)
{
struct resource *res;
phy->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(phy->base))
return PTR_ERR(phy->base);
phy->num_clks = devm_clk_bulk_get_all(phy->dev, &phy->clks);
if (phy->num_clks < 0)
return phy->num_clks;
phy->resets = devm_reset_control_array_get_exclusive(phy->dev);
if (IS_ERR(phy->resets))
return PTR_ERR(phy->resets);
return 0;
}
/*
* Register a fixed rate pipe clock.
*
* The <s>_pipe_clksrc generated by PHY goes to the GCC that gate
* controls it. The <s>_pipe_clk coming out of the GCC is requested
* by the PHY driver for its operations.
* We register the <s>_pipe_clksrc here. The gcc driver takes care
* of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk.
* Below picture shows this relationship.
*
* +---------------+
* | PHY block |<<---------------------------------------+
* | | |
* | +-------+ | +-----+ |
* I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+
* clk | +-------+ | +-----+
* +---------------+
*/
static inline int phy_pipe_clk_register(struct qcom_uniphy_pcie *phy, int id)
{
const struct qcom_uniphy_pcie_data *data = phy->data;
struct clk_hw *hw;
char name[64];
snprintf(name, sizeof(name), "phy%d_pipe_clk_src", id);
hw = devm_clk_hw_register_fixed_rate(phy->dev, name, NULL, 0,
data->pipe_clk_rate);
if (IS_ERR(hw))
return dev_err_probe(phy->dev, PTR_ERR(hw),
"Unable to register %s\n", name);
return devm_of_clk_add_hw_provider(phy->dev, of_clk_hw_simple_get, hw);
}
static const struct of_device_id qcom_uniphy_pcie_id_table[] = {
{
.compatible = "qcom,ipq5018-uniphy-pcie-phy",
.data = &ipq5018_data,
}, {
.compatible = "qcom,ipq5332-uniphy-pcie-phy",
.data = &ipq5332_data,
}, {
/* Sentinel */
},
};
MODULE_DEVICE_TABLE(of, qcom_uniphy_pcie_id_table);
static const struct phy_ops pcie_ops = {
.power_on = qcom_uniphy_pcie_power_on,
.power_off = qcom_uniphy_pcie_power_off,
.owner = THIS_MODULE,
};
static int qcom_uniphy_pcie_probe(struct platform_device *pdev)
{
struct phy_provider *phy_provider;
struct device *dev = &pdev->dev;
struct qcom_uniphy_pcie *phy;
struct phy *generic_phy;
int ret;
phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
platform_set_drvdata(pdev, phy);
phy->dev = &pdev->dev;
phy->data = of_device_get_match_data(dev);
if (!phy->data)
return -EINVAL;
ret = of_property_read_u32(dev_of_node(dev), "num-lanes", &phy->lanes);
if (ret)
return dev_err_probe(dev, ret, "Couldn't read num-lanes\n");
ret = qcom_uniphy_pcie_get_resources(pdev, phy);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"failed to get resources: %d\n", ret);
generic_phy = devm_phy_create(phy->dev, NULL, &pcie_ops);
if (IS_ERR(generic_phy))
return PTR_ERR(generic_phy);
phy_set_drvdata(generic_phy, phy);
ret = phy_pipe_clk_register(phy, generic_phy->id);
if (ret)
dev_err(&pdev->dev, "failed to register phy pipe clk\n");
phy_provider = devm_of_phy_provider_register(phy->dev,
of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
return 0;
}
static struct platform_driver qcom_uniphy_pcie_driver = {
.probe = qcom_uniphy_pcie_probe,
.driver = {
.name = "qcom-uniphy-pcie",
.of_match_table = qcom_uniphy_pcie_id_table,
},
};
module_platform_driver(qcom_uniphy_pcie_driver);
MODULE_DESCRIPTION("PCIE QCOM UNIPHY driver");
MODULE_LICENSE("GPL");