| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2023, Linaro Ltd. All rights reserved. |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_graph.h> |
| #include <linux/platform_device.h> |
| #include <linux/regmap.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/slab.h> |
| #include <linux/usb/role.h> |
| #include <linux/usb/tcpm.h> |
| #include <linux/usb/typec_mux.h> |
| |
| #include <drm/bridge/aux-bridge.h> |
| |
| #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 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 = drm_dp_hpd_bridge_register(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 fwnode_remove; |
| |
| ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); |
| if (ret) |
| goto fwnode_remove; |
| |
| return 0; |
| |
| 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"); |