1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (c) 2023, Linaro Ltd. All rights reserved.
4 */
5
6#include <linux/err.h>
7#include <linux/interrupt.h>
8#include <linux/kernel.h>
9#include <linux/mod_devicetable.h>
10#include <linux/module.h>
11#include <linux/of.h>
12#include <linux/of_graph.h>
13#include <linux/platform_device.h>
14#include <linux/regmap.h>
15#include <linux/regulator/consumer.h>
16#include <linux/slab.h>
17#include <linux/usb/role.h>
18#include <linux/usb/tcpm.h>
19#include <linux/usb/typec_mux.h>
20
21#include <drm/bridge/aux-bridge.h>
22
23#include "qcom_pmic_typec.h"
24#include "qcom_pmic_typec_pdphy.h"
25#include "qcom_pmic_typec_port.h"
26
27struct pmic_typec_resources {
28 const struct pmic_typec_pdphy_resources *pdphy_res;
29 const struct pmic_typec_port_resources *port_res;
30};
31
32static int qcom_pmic_typec_init(struct tcpc_dev *tcpc)
33{
34 return 0;
35}
36
37static int qcom_pmic_typec_probe(struct platform_device *pdev)
38{
39 struct pmic_typec *tcpm;
40 struct device *dev = &pdev->dev;
41 struct device_node *np = dev->of_node;
42 const struct pmic_typec_resources *res;
43 struct regmap *regmap;
44 struct device *bridge_dev;
45 u32 base;
46 int ret;
47
48 res = of_device_get_match_data(dev);
49 if (!res)
50 return -ENODEV;
51
52 tcpm = devm_kzalloc(dev, size: sizeof(*tcpm), GFP_KERNEL);
53 if (!tcpm)
54 return -ENOMEM;
55
56 tcpm->dev = dev;
57 tcpm->tcpc.init = qcom_pmic_typec_init;
58
59 regmap = dev_get_regmap(dev: dev->parent, NULL);
60 if (!regmap) {
61 dev_err(dev, "Failed to get regmap\n");
62 return -ENODEV;
63 }
64
65 ret = of_property_read_u32_index(np, propname: "reg", index: 0, out_value: &base);
66 if (ret)
67 return ret;
68
69 ret = qcom_pmic_typec_port_probe(pdev, tcpm,
70 res: res->port_res, regmap, base);
71 if (ret)
72 return ret;
73
74 if (res->pdphy_res) {
75 ret = of_property_read_u32_index(np, propname: "reg", index: 1, out_value: &base);
76 if (ret)
77 return ret;
78
79 ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm,
80 res: res->pdphy_res, regmap, base);
81 if (ret)
82 return ret;
83 } else {
84 ret = qcom_pmic_typec_pdphy_stub_probe(pdev, tcpm);
85 if (ret)
86 return ret;
87 }
88
89 platform_set_drvdata(pdev, data: tcpm);
90
91 tcpm->tcpc.fwnode = device_get_named_child_node(dev: tcpm->dev, childname: "connector");
92 if (!tcpm->tcpc.fwnode)
93 return -EINVAL;
94
95 bridge_dev = drm_dp_hpd_bridge_register(parent: tcpm->dev, to_of_node(tcpm->tcpc.fwnode));
96 if (IS_ERR(ptr: bridge_dev))
97 return PTR_ERR(ptr: bridge_dev);
98
99 tcpm->tcpm_port = tcpm_register_port(dev: tcpm->dev, tcpc: &tcpm->tcpc);
100 if (IS_ERR(ptr: tcpm->tcpm_port)) {
101 ret = PTR_ERR(ptr: tcpm->tcpm_port);
102 goto fwnode_remove;
103 }
104
105 ret = tcpm->port_start(tcpm, tcpm->tcpm_port);
106 if (ret)
107 goto fwnode_remove;
108
109 ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port);
110 if (ret)
111 goto fwnode_remove;
112
113 return 0;
114
115fwnode_remove:
116 fwnode_remove_software_node(fwnode: tcpm->tcpc.fwnode);
117
118 return ret;
119}
120
121static void qcom_pmic_typec_remove(struct platform_device *pdev)
122{
123 struct pmic_typec *tcpm = platform_get_drvdata(pdev);
124
125 tcpm->pdphy_stop(tcpm);
126 tcpm->port_stop(tcpm);
127 tcpm_unregister_port(port: tcpm->tcpm_port);
128 fwnode_remove_software_node(fwnode: tcpm->tcpc.fwnode);
129}
130
131static const struct pmic_typec_resources pm8150b_typec_res = {
132 .pdphy_res = &pm8150b_pdphy_res,
133 .port_res = &pm8150b_port_res,
134};
135
136static const struct pmic_typec_resources pmi632_typec_res = {
137 /* PD PHY not present */
138 .port_res = &pm8150b_port_res,
139};
140
141static const struct of_device_id qcom_pmic_typec_table[] = {
142 { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res },
143 { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res },
144 { }
145};
146MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table);
147
148static struct platform_driver qcom_pmic_typec_driver = {
149 .driver = {
150 .name = "qcom,pmic-typec",
151 .of_match_table = qcom_pmic_typec_table,
152 },
153 .probe = qcom_pmic_typec_probe,
154 .remove_new = qcom_pmic_typec_remove,
155};
156
157module_platform_driver(qcom_pmic_typec_driver);
158
159MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver");
160MODULE_LICENSE("GPL");
161

source code of linux/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c