// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2023, Linaro Ltd. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qcom_pmic_typec.h" #include "qcom_pmic_typec_pdphy.h" #include "qcom_pmic_typec_port.h" struct pmic_typec_resources { const struct pmic_typec_pdphy_resources *pdphy_res; const struct pmic_typec_port_resources *port_res; }; static int qcom_pmic_typec_init(struct tcpc_dev *tcpc) { return 0; } static int qcom_pmic_typec_probe(struct platform_device *pdev) { struct pmic_typec *tcpm; struct device *dev = &pdev->dev; struct device_node *np = dev->of_node; const struct pmic_typec_resources *res; struct regmap *regmap; struct auxiliary_device *bridge_dev; u32 base; int ret; res = of_device_get_match_data(dev); if (!res) return -ENODEV; tcpm = devm_kzalloc(dev, sizeof(*tcpm), GFP_KERNEL); if (!tcpm) return -ENOMEM; tcpm->dev = dev; tcpm->tcpc.init = qcom_pmic_typec_init; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) { dev_err(dev, "Failed to get regmap\n"); return -ENODEV; } ret = of_property_read_u32_index(np, "reg", 0, &base); if (ret) return ret; ret = qcom_pmic_typec_port_probe(pdev, tcpm, res->port_res, regmap, base); if (ret) return ret; if (res->pdphy_res) { ret = of_property_read_u32_index(np, "reg", 1, &base); if (ret) return ret; ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm, res->pdphy_res, regmap, base); if (ret) return ret; } else { ret = qcom_pmic_typec_pdphy_stub_probe(pdev, tcpm); if (ret) return ret; } platform_set_drvdata(pdev, tcpm); tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); if (!tcpm->tcpc.fwnode) return -EINVAL; bridge_dev = devm_drm_dp_hpd_bridge_alloc(tcpm->dev, to_of_node(tcpm->tcpc.fwnode)); if (IS_ERR(bridge_dev)) return PTR_ERR(bridge_dev); tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc); if (IS_ERR(tcpm->tcpm_port)) { ret = PTR_ERR(tcpm->tcpm_port); goto fwnode_remove; } ret = tcpm->port_start(tcpm, tcpm->tcpm_port); if (ret) goto port_unregister; ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); if (ret) goto port_stop; ret = devm_drm_dp_hpd_bridge_add(tcpm->dev, bridge_dev); if (ret) goto pdphy_stop; return 0; pdphy_stop: tcpm->pdphy_stop(tcpm); port_stop: tcpm->port_stop(tcpm); port_unregister: tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove: fwnode_remove_software_node(tcpm->tcpc.fwnode); return ret; } static void qcom_pmic_typec_remove(struct platform_device *pdev) { struct pmic_typec *tcpm = platform_get_drvdata(pdev); tcpm->pdphy_stop(tcpm); tcpm->port_stop(tcpm); tcpm_unregister_port(tcpm->tcpm_port); fwnode_remove_software_node(tcpm->tcpc.fwnode); } static const struct pmic_typec_resources pm8150b_typec_res = { .pdphy_res = &pm8150b_pdphy_res, .port_res = &pm8150b_port_res, }; static const struct pmic_typec_resources pmi632_typec_res = { /* PD PHY not present */ .port_res = &pm8150b_port_res, }; static const struct of_device_id qcom_pmic_typec_table[] = { { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res }, { } }; MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table); static struct platform_driver qcom_pmic_typec_driver = { .driver = { .name = "qcom,pmic-typec", .of_match_table = qcom_pmic_typec_table, }, .probe = qcom_pmic_typec_probe, .remove_new = qcom_pmic_typec_remove, }; module_platform_driver(qcom_pmic_typec_driver); MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver"); MODULE_LICENSE("GPL");