#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
+#include <linux/phy/pcie.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
/* Main init sequence for PHY blocks - serdes, tx, rx, pcs */
const struct qmp_phy_cfg_tables tables;
/*
- * Additional init sequence for PHY blocks, providing additional
- * register programming. Unless required it can be left omitted.
+ * Additional init sequences for PHY blocks, providing additional
+ * register programming. They are used for providing separate sequences
+ * for the Root Complex and End Point use cases.
+ *
+ * If EP mode is not supported, both tables can be left unset.
*/
const struct qmp_phy_cfg_tables *tables_rc;
+ const struct qmp_phy_cfg_tables *tables_ep;
/* clock ids to be requested */
const char * const *clk_list;
* @pcs_misc: iomapped memory space for lane's pcs_misc
* @pipe_clk: pipe clock
* @qmp: QMP phy to which this lane belongs
+ * @mode: currently selected PHY mode
*/
struct qmp_phy {
struct phy *phy;
void __iomem *pcs_misc;
struct clk *pipe_clk;
struct qcom_qmp *qmp;
+ int mode;
};
/**
struct qmp_phy *qphy = phy_get_drvdata(phy);
struct qcom_qmp *qmp = qphy->qmp;
const struct qmp_phy_cfg *cfg = qphy->cfg;
+ const struct qmp_phy_cfg_tables *mode_tables;
void __iomem *pcs = qphy->pcs;
void __iomem *status;
unsigned int mask, val, ready;
int ret;
+ if (qphy->mode == PHY_MODE_PCIE_RC)
+ mode_tables = cfg->tables_rc;
+ else
+ mode_tables = cfg->tables_ep;
+
qmp_pcie_serdes_init(qphy, &cfg->tables);
- qmp_pcie_serdes_init(qphy, cfg->tables_rc);
+ qmp_pcie_serdes_init(qphy, mode_tables);
ret = clk_prepare_enable(qphy->pipe_clk);
if (ret) {
/* Tx, Rx, and PCS configurations */
qmp_pcie_lanes_init(qphy, &cfg->tables);
- qmp_pcie_lanes_init(qphy, cfg->tables_rc);
+ qmp_pcie_lanes_init(qphy, mode_tables);
qmp_pcie_pcs_init(qphy, &cfg->tables);
- qmp_pcie_pcs_init(qphy, cfg->tables_rc);
+ qmp_pcie_pcs_init(qphy, mode_tables);
/* Pull PHY out of reset state */
qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
return qmp_pcie_exit(phy);
}
+static int qmp_pcie_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct qmp_phy *qphy = phy_get_drvdata(phy);
+
+ switch (submode) {
+ case PHY_MODE_PCIE_RC:
+ case PHY_MODE_PCIE_EP:
+ qphy->mode = submode;
+ break;
+ default:
+ dev_err(&phy->dev, "Unsupported submode %d\n", submode);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int qmp_pcie_vreg_init(struct device *dev, const struct qmp_phy_cfg *cfg)
{
struct qcom_qmp *qmp = dev_get_drvdata(dev);
static const struct phy_ops qmp_pcie_ops = {
.power_on = qmp_pcie_enable,
.power_off = qmp_pcie_disable,
+ .set_mode = qmp_pcie_set_mode,
.owner = THIS_MODULE,
};
if (!qphy)
return -ENOMEM;
+ qphy->mode = PHY_MODE_PCIE_RC;
+
qphy->cfg = cfg;
qphy->serdes = serdes;
/*
if (IS_ERR(qphy->pcs_misc)) {
if (cfg->tables.pcs_misc ||
- (cfg->tables_rc && cfg->tables_rc->pcs_misc))
+ (cfg->tables_rc && cfg->tables_rc->pcs_misc) ||
+ (cfg->tables_ep && cfg->tables_ep->pcs_misc))
return PTR_ERR(qphy->pcs_misc);
}