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, 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 | |
115 | fwnode_remove: |
116 | fwnode_remove_software_node(fwnode: 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(port: tcpm->tcpm_port); |
128 | fwnode_remove_software_node(fwnode: 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 | |