Commit f3198fde authored by Xiangxu Yin's avatar Xiangxu Yin Committed by Vinod Koul
Browse files

phy: qcom: qmp-usbc: Add DP PHY ops for USB/DP switchable Type-C PHYs

parent 9ab26cb7
Loading
Loading
Loading
Loading
+193 −1
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@
#include "phy-qcom-qmp.h"
#include "phy-qcom-qmp-pcs-misc-v3.h"

#include "phy-qcom-qmp-dp-phy.h"

#define PHY_INIT_COMPLETE_TIMEOUT		10000
#define SW_PORTSELECT_VAL			BIT(0)
#define SW_PORTSELECT_MUX			BIT(1)
@@ -711,6 +713,159 @@ static int qmp_usbc_usb_set_mode(struct phy *phy, enum phy_mode mode, int submod
	return 0;
}

static int qmp_usbc_dp_enable(struct phy *phy)
{
	struct qmp_usbc *qmp = phy_get_drvdata(phy);
	const struct qmp_phy_cfg *cfg = qmp->cfg;
	int ret;

	if (qmp->dp_init_count) {
		dev_err(qmp->dev, "DP already inited\n");
		return 0;
	}

	mutex_lock(&qmp->phy_mutex);

	ret = qmp_usbc_com_init(phy);
	if (ret)
		goto dp_init_unlock;

	qmp_usbc_set_phy_mode(qmp, true);

	cfg->dp_aux_init(qmp);

	qmp->dp_init_count++;

dp_init_unlock:
	mutex_unlock(&qmp->phy_mutex);
	return ret;
}

static int qmp_usbc_dp_disable(struct phy *phy)
{
	struct qmp_usbc *qmp = phy_get_drvdata(phy);

	mutex_lock(&qmp->phy_mutex);

	qmp_usbc_com_exit(phy);

	qmp->dp_init_count--;

	mutex_unlock(&qmp->phy_mutex);

	return 0;
}

static int qmp_usbc_dp_configure(struct phy *phy, union phy_configure_opts *opts)
{
	const struct phy_configure_opts_dp *dp_opts = &opts->dp;
	struct qmp_usbc *qmp = phy_get_drvdata(phy);
	const struct qmp_phy_cfg *cfg = qmp->cfg;

	mutex_lock(&qmp->phy_mutex);

	memcpy(&qmp->dp_opts, dp_opts, sizeof(*dp_opts));
	if (qmp->dp_opts.set_voltages) {
		cfg->configure_dp_tx(qmp);
		qmp->dp_opts.set_voltages = 0;
	}

	mutex_unlock(&qmp->phy_mutex);

	return 0;
}

static int qmp_usbc_dp_calibrate(struct phy *phy)
{
	struct qmp_usbc *qmp = phy_get_drvdata(phy);
	const struct qmp_phy_cfg *cfg = qmp->cfg;
	int ret = 0;

	mutex_lock(&qmp->phy_mutex);

	if (cfg->calibrate_dp_phy) {
		ret = cfg->calibrate_dp_phy(qmp);
		if (ret) {
			dev_err(qmp->dev, "dp calibrate err(%d)\n", ret);
			mutex_unlock(&qmp->phy_mutex);
			return ret;
		}
	}

	mutex_unlock(&qmp->phy_mutex);
	return 0;
}

static int qmp_usbc_dp_serdes_init(struct qmp_usbc *qmp)
{
	const struct qmp_phy_cfg *cfg = qmp->cfg;
	void __iomem *serdes = qmp->dp_serdes;
	const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts;

	qmp_configure(qmp->dev, serdes, cfg->dp_serdes_tbl,
		      cfg->dp_serdes_tbl_num);

	switch (dp_opts->link_rate) {
	case 1620:
		qmp_configure(qmp->dev, serdes, cfg->serdes_tbl_rbr,
			      cfg->serdes_tbl_rbr_num);
		break;
	case 2700:
		qmp_configure(qmp->dev, serdes, cfg->serdes_tbl_hbr,
			      cfg->serdes_tbl_hbr_num);
		break;
	case 5400:
		qmp_configure(qmp->dev, serdes, cfg->serdes_tbl_hbr2,
			      cfg->serdes_tbl_hbr2_num);
		break;
	default:
		/* Other link rates aren't supported */
		return -EINVAL;
	}

	return 0;
}

static int qmp_usbc_dp_power_on(struct phy *phy)
{
	struct qmp_usbc *qmp = phy_get_drvdata(phy);
	const struct qmp_phy_cfg *cfg = qmp->cfg;

	void __iomem *tx = qmp->dp_tx;
	void __iomem *tx2 = qmp->dp_tx2;

	mutex_lock(&qmp->phy_mutex);

	qmp_usbc_dp_serdes_init(qmp);

	qmp_configure_lane(qmp->dev, tx, cfg->dp_tx_tbl, cfg->dp_tx_tbl_num, 1);
	qmp_configure_lane(qmp->dev, tx2, cfg->dp_tx_tbl, cfg->dp_tx_tbl_num, 2);

	/* Configure special DP tx tunings */
	cfg->configure_dp_tx(qmp);

	/* Configure link rate, swing, etc. */
	cfg->configure_dp_phy(qmp);

	mutex_unlock(&qmp->phy_mutex);

	return 0;
}

static int qmp_usbc_dp_power_off(struct phy *phy)
{
	struct qmp_usbc *qmp = phy_get_drvdata(phy);

	mutex_lock(&qmp->phy_mutex);

	/* Assert DP PHY power down */
	writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL);

	mutex_unlock(&qmp->phy_mutex);

	return 0;
}

static const struct phy_ops qmp_usbc_usb_phy_ops = {
	.init		= qmp_usbc_usb_enable,
	.exit		= qmp_usbc_usb_disable,
@@ -718,6 +873,16 @@ static const struct phy_ops qmp_usbc_usb_phy_ops = {
	.owner		= THIS_MODULE,
};

static const struct phy_ops qmp_usbc_dp_phy_ops = {
	.init		= qmp_usbc_dp_enable,
	.exit		= qmp_usbc_dp_disable,
	.configure	= qmp_usbc_dp_configure,
	.calibrate	= qmp_usbc_dp_calibrate,
	.power_on	= qmp_usbc_dp_power_on,
	.power_off	= qmp_usbc_dp_power_off,
	.owner		= THIS_MODULE,
};

static void qmp_usbc_enable_autonomous_mode(struct qmp_usbc *qmp)
{
	const struct qmp_phy_cfg *cfg = qmp->cfg;
@@ -1299,6 +1464,23 @@ static int qmp_usbc_parse_tcsr(struct qmp_usbc *qmp)
	return 0;
}

static struct phy *qmp_usbc_phy_xlate(struct device *dev, const struct of_phandle_args *args)
{
	struct qmp_usbc *qmp = dev_get_drvdata(dev);

	if (args->args_count == 0)
		return qmp->usb_phy;

	switch (args->args[0]) {
	case QMP_USB43DP_USB3_PHY:
		return qmp->usb_phy;
	case QMP_USB43DP_DP_PHY:
		return qmp->dp_phy ?: ERR_PTR(-ENODEV);
	}

	return ERR_PTR(-EINVAL);
}

static int qmp_usbc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
@@ -1369,9 +1551,19 @@ static int qmp_usbc_probe(struct platform_device *pdev)

	phy_set_drvdata(qmp->usb_phy, qmp);

	if (qmp->dp_serdes != 0) {
		qmp->dp_phy = devm_phy_create(dev, np, &qmp_usbc_dp_phy_ops);
		if (IS_ERR(qmp->dp_phy)) {
			ret = PTR_ERR(qmp->dp_phy);
			dev_err(dev, "failed to create PHY: %d\n", ret);
			goto err_node_put;
		}
		phy_set_drvdata(qmp->dp_phy, qmp);
	}

	of_node_put(np);

	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
	phy_provider = devm_of_phy_provider_register(dev, qmp_usbc_phy_xlate);

	return PTR_ERR_OR_ZERO(phy_provider);