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 
27 struct pmic_typec_resources {
28 	const struct pmic_typec_pdphy_resources	*pdphy_res;
29 	const struct pmic_typec_port_resources	*port_res;
30 };
31 
32 static int qcom_pmic_typec_init(struct tcpc_dev *tcpc)
33 {
34 	return 0;
35 }
36 
37 static 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, 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->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, "reg", 0, &base);
66 	if (ret)
67 		return ret;
68 
69 	ret = qcom_pmic_typec_port_probe(pdev, tcpm,
70 					 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, "reg", 1, &base);
76 		if (ret)
77 			return ret;
78 
79 		ret = qcom_pmic_typec_pdphy_probe(pdev, tcpm,
80 						  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, tcpm);
90 
91 	tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector");
92 	if (!tcpm->tcpc.fwnode)
93 		return -EINVAL;
94 
95 	bridge_dev = drm_dp_hpd_bridge_register(tcpm->dev, to_of_node(tcpm->tcpc.fwnode));
96 	if (IS_ERR(bridge_dev))
97 		return PTR_ERR(bridge_dev);
98 
99 	tcpm->tcpm_port = tcpm_register_port(tcpm->dev, &tcpm->tcpc);
100 	if (IS_ERR(tcpm->tcpm_port)) {
101 		ret = PTR_ERR(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 
115 fwnode_remove:
116 	fwnode_remove_software_node(tcpm->tcpc.fwnode);
117 
118 	return ret;
119 }
120 
121 static 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(tcpm->tcpm_port);
128 	fwnode_remove_software_node(tcpm->tcpc.fwnode);
129 }
130 
131 static const struct pmic_typec_resources pm8150b_typec_res = {
132 	.pdphy_res = &pm8150b_pdphy_res,
133 	.port_res = &pm8150b_port_res,
134 };
135 
136 static const struct pmic_typec_resources pmi632_typec_res = {
137 	/* PD PHY not present */
138 	.port_res = &pm8150b_port_res,
139 };
140 
141 static 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 };
146 MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table);
147 
148 static 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 
157 module_platform_driver(qcom_pmic_typec_driver);
158 
159 MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver");
160 MODULE_LICENSE("GPL");
161