]>
Commit | Line | Data |
---|---|---|
a4422ff2 BD |
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> | |
484468fb | 11 | #include <linux/of.h> |
a4422ff2 BD |
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> | |
4b3cd783 | 20 | |
7d9f1b72 | 21 | #include <drm/bridge/aux-bridge.h> |
4b3cd783 | 22 | |
d2f9b93d | 23 | #include "qcom_pmic_typec.h" |
a4422ff2 BD |
24 | #include "qcom_pmic_typec_pdphy.h" |
25 | #include "qcom_pmic_typec_port.h" | |
26 | ||
27 | struct pmic_typec_resources { | |
d2f9b93d | 28 | const struct pmic_typec_pdphy_resources *pdphy_res; |
f1a27f08 | 29 | const struct pmic_typec_port_resources *port_res; |
a4422ff2 BD |
30 | }; |
31 | ||
a4422ff2 BD |
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; | |
7d9f1b72 | 44 | struct device *bridge_dev; |
cf92b9df | 45 | u32 base; |
a4422ff2 BD |
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; | |
a4422ff2 BD |
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 | ||
cf92b9df | 65 | ret = of_property_read_u32_index(np, "reg", 0, &base); |
a4422ff2 BD |
66 | if (ret) |
67 | return ret; | |
68 | ||
f1a27f08 | 69 | ret = qcom_pmic_typec_port_probe(pdev, tcpm, |
cf92b9df | 70 | res->port_res, regmap, base); |
a4422ff2 BD |
71 | if (ret) |
72 | return ret; | |
73 | ||
cf92b9df DB |
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 | } | |
a4422ff2 | 88 | |
a4422ff2 BD |
89 | platform_set_drvdata(pdev, tcpm); |
90 | ||
91 | tcpm->tcpc.fwnode = device_get_named_child_node(tcpm->dev, "connector"); | |
da6d91ed YY |
92 | if (!tcpm->tcpc.fwnode) |
93 | return -EINVAL; | |
a4422ff2 | 94 | |
7d9f1b72 DB |
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 | ||
a4422ff2 BD |
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 | ||
f1a27f08 | 105 | ret = tcpm->port_start(tcpm, tcpm->tcpm_port); |
a4422ff2 BD |
106 | if (ret) |
107 | goto fwnode_remove; | |
108 | ||
d2f9b93d | 109 | ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port); |
a4422ff2 BD |
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 | ||
922c0cb5 | 121 | static void qcom_pmic_typec_remove(struct platform_device *pdev) |
a4422ff2 BD |
122 | { |
123 | struct pmic_typec *tcpm = platform_get_drvdata(pdev); | |
124 | ||
d2f9b93d | 125 | tcpm->pdphy_stop(tcpm); |
f1a27f08 | 126 | tcpm->port_stop(tcpm); |
a4422ff2 BD |
127 | tcpm_unregister_port(tcpm->tcpm_port); |
128 | fwnode_remove_software_node(tcpm->tcpc.fwnode); | |
a4422ff2 BD |
129 | } |
130 | ||
f1a27f08 | 131 | static const struct pmic_typec_resources pm8150b_typec_res = { |
a4422ff2 BD |
132 | .pdphy_res = &pm8150b_pdphy_res, |
133 | .port_res = &pm8150b_port_res, | |
134 | }; | |
135 | ||
cf92b9df DB |
136 | static const struct pmic_typec_resources pmi632_typec_res = { |
137 | /* PD PHY not present */ | |
138 | .port_res = &pm8150b_port_res, | |
139 | }; | |
140 | ||
a4422ff2 BD |
141 | static const struct of_device_id qcom_pmic_typec_table[] = { |
142 | { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res }, | |
cf92b9df | 143 | { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res }, |
a4422ff2 BD |
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, | |
922c0cb5 | 154 | .remove_new = qcom_pmic_typec_remove, |
a4422ff2 BD |
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"); |