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 auxiliary_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 = devm_drm_dp_hpd_bridge_alloc(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 port_unregister;
108
109	ret = tcpm->pdphy_start(tcpm, tcpm->tcpm_port);
110	if (ret)
111		goto port_stop;
112
113	ret = devm_drm_dp_hpd_bridge_add(tcpm->dev, bridge_dev);
114	if (ret)
115		goto pdphy_stop;
116
117	return 0;
118
119pdphy_stop:
120	tcpm->pdphy_stop(tcpm);
121port_stop:
122	tcpm->port_stop(tcpm);
123port_unregister:
124	tcpm_unregister_port(tcpm->tcpm_port);
125fwnode_remove:
126	fwnode_remove_software_node(tcpm->tcpc.fwnode);
127
128	return ret;
129}
130
131static void qcom_pmic_typec_remove(struct platform_device *pdev)
132{
133	struct pmic_typec *tcpm = platform_get_drvdata(pdev);
134
135	tcpm->pdphy_stop(tcpm);
136	tcpm->port_stop(tcpm);
137	tcpm_unregister_port(tcpm->tcpm_port);
138	fwnode_remove_software_node(tcpm->tcpc.fwnode);
139}
140
141static const struct pmic_typec_resources pm8150b_typec_res = {
142	.pdphy_res = &pm8150b_pdphy_res,
143	.port_res = &pm8150b_port_res,
144};
145
146static const struct pmic_typec_resources pmi632_typec_res = {
147	/* PD PHY not present */
148	.port_res = &pm8150b_port_res,
149};
150
151static const struct of_device_id qcom_pmic_typec_table[] = {
152	{ .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res },
153	{ .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res },
154	{ }
155};
156MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table);
157
158static struct platform_driver qcom_pmic_typec_driver = {
159	.driver = {
160		.name = "qcom,pmic-typec",
161		.of_match_table = qcom_pmic_typec_table,
162	},
163	.probe = qcom_pmic_typec_probe,
164	.remove_new = qcom_pmic_typec_remove,
165};
166
167module_platform_driver(qcom_pmic_typec_driver);
168
169MODULE_DESCRIPTION("QCOM PMIC USB Type-C Port Manager Driver");
170MODULE_LICENSE("GPL");
171