static int qcom_qmp_phy_probe()

in qualcomm/phy-qcom-qmp.c [6093:6244]


static int qcom_qmp_phy_probe(struct platform_device *pdev)
{
	struct qcom_qmp *qmp;
	struct device *dev = &pdev->dev;
	struct device_node *child;
	struct phy_provider *phy_provider;
	void __iomem *serdes;
	void __iomem *usb_serdes;
	void __iomem *dp_serdes = NULL;
	const struct qmp_phy_combo_cfg *combo_cfg = NULL;
	const struct qmp_phy_cfg *cfg = NULL;
	const struct qmp_phy_cfg *usb_cfg = NULL;
	const struct qmp_phy_cfg *dp_cfg = NULL;
	int num, id, expected_phys;
	int ret;

	qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL);
	if (!qmp)
		return -ENOMEM;

	qmp->dev = dev;
	dev_set_drvdata(dev, qmp);

	/* Get the specific init parameters of QMP phy */
	cfg = of_device_get_match_data(dev);
	if (!cfg) {
		const struct of_device_id *match;

		match = of_match_device(qcom_qmp_combo_phy_of_match_table, dev);
		if (!match)
			return -EINVAL;

		combo_cfg = match->data;
		if (!combo_cfg)
			return -EINVAL;

		usb_cfg = combo_cfg->usb_cfg;
		cfg = usb_cfg; /* Setup clks and regulators */
	}

	/* per PHY serdes; usually located at base address */
	usb_serdes = serdes = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(serdes))
		return PTR_ERR(serdes);

	/* per PHY dp_com; if PHY has dp_com control block */
	if (combo_cfg || cfg->has_phy_dp_com_ctrl) {
		qmp->dp_com = devm_platform_ioremap_resource(pdev, 1);
		if (IS_ERR(qmp->dp_com))
			return PTR_ERR(qmp->dp_com);
	}

	if (combo_cfg) {
		/* Only two serdes for combo PHY */
		dp_serdes = devm_platform_ioremap_resource(pdev, 2);
		if (IS_ERR(dp_serdes))
			return PTR_ERR(dp_serdes);

		dp_cfg = combo_cfg->dp_cfg;
		expected_phys = 2;
	} else {
		expected_phys = cfg->nlanes;
	}

	mutex_init(&qmp->phy_mutex);

	ret = qcom_qmp_phy_clk_init(dev, cfg);
	if (ret)
		return ret;

	ret = qcom_qmp_phy_reset_init(dev, cfg);
	if (ret)
		return ret;

	ret = qcom_qmp_phy_vreg_init(dev, cfg);
	if (ret) {
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "failed to get regulator supplies: %d\n",
				ret);
		return ret;
	}

	num = of_get_available_child_count(dev->of_node);
	/* do we have a rogue child node ? */
	if (num > expected_phys)
		return -EINVAL;

	qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL);
	if (!qmp->phys)
		return -ENOMEM;

	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
	/*
	 * Prevent runtime pm from being ON by default. Users can enable
	 * it using power/control in sysfs.
	 */
	pm_runtime_forbid(dev);

	id = 0;
	for_each_available_child_of_node(dev->of_node, child) {
		if (of_node_name_eq(child, "dp-phy")) {
			cfg = dp_cfg;
			serdes = dp_serdes;
		} else if (of_node_name_eq(child, "usb3-phy")) {
			cfg = usb_cfg;
			serdes = usb_serdes;
		}

		/* Create per-lane phy */
		ret = qcom_qmp_phy_create(dev, child, id, serdes, cfg);
		if (ret) {
			dev_err(dev, "failed to create lane%d phy, %d\n",
				id, ret);
			goto err_node_put;
		}

		/*
		 * Register the pipe clock provided by phy.
		 * See function description to see details of this pipe clock.
		 */
		if (cfg->type == PHY_TYPE_USB3 || cfg->type == PHY_TYPE_PCIE) {
			ret = phy_pipe_clk_register(qmp, child);
			if (ret) {
				dev_err(qmp->dev,
					"failed to register pipe clock source\n");
				goto err_node_put;
			}
		} else if (cfg->type == PHY_TYPE_DP) {
			ret = phy_dp_clks_register(qmp, qmp->phys[id], child);
			if (ret) {
				dev_err(qmp->dev,
					"failed to register DP clock source\n");
				goto err_node_put;
			}
		}
		id++;
	}

	phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
	if (!IS_ERR(phy_provider))
		dev_info(dev, "Registered Qcom-QMP phy\n");
	else
		pm_runtime_disable(dev);

	return PTR_ERR_OR_ZERO(phy_provider);

err_node_put:
	pm_runtime_disable(dev);
	of_node_put(child);
	return ret;
}