1b7c7436aSJernej Skrabec // SPDX-License-Identifier: GPL-2.0+
2b7c7436aSJernej Skrabec /*
3b7c7436aSJernej Skrabec  * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
4b7c7436aSJernej Skrabec  */
5b7c7436aSJernej Skrabec 
6b7c7436aSJernej Skrabec #include <linux/component.h>
7b7c7436aSJernej Skrabec #include <linux/module.h>
8b7c7436aSJernej Skrabec #include <linux/platform_device.h>
9b7c7436aSJernej Skrabec 
10b7c7436aSJernej Skrabec #include <drm/drm_of.h>
11b7c7436aSJernej Skrabec #include <drm/drmP.h>
12b7c7436aSJernej Skrabec #include <drm/drm_crtc_helper.h>
13b7c7436aSJernej Skrabec 
14b7c7436aSJernej Skrabec #include "sun8i_dw_hdmi.h"
1557e23de0SJernej Skrabec #include "sun8i_tcon_top.h"
16b7c7436aSJernej Skrabec 
17b7c7436aSJernej Skrabec static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
18b7c7436aSJernej Skrabec 					   struct drm_display_mode *mode,
19b7c7436aSJernej Skrabec 					   struct drm_display_mode *adj_mode)
20b7c7436aSJernej Skrabec {
21b7c7436aSJernej Skrabec 	struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder);
22b7c7436aSJernej Skrabec 
23b7c7436aSJernej Skrabec 	clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000);
24b7c7436aSJernej Skrabec }
25b7c7436aSJernej Skrabec 
26b7c7436aSJernej Skrabec static const struct drm_encoder_helper_funcs
27b7c7436aSJernej Skrabec sun8i_dw_hdmi_encoder_helper_funcs = {
28b7c7436aSJernej Skrabec 	.mode_set = sun8i_dw_hdmi_encoder_mode_set,
29b7c7436aSJernej Skrabec };
30b7c7436aSJernej Skrabec 
31b7c7436aSJernej Skrabec static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
32b7c7436aSJernej Skrabec 	.destroy = drm_encoder_cleanup,
33b7c7436aSJernej Skrabec };
34b7c7436aSJernej Skrabec 
35b7c7436aSJernej Skrabec static enum drm_mode_status
36b7c7436aSJernej Skrabec sun8i_dw_hdmi_mode_valid(struct drm_connector *connector,
37b7c7436aSJernej Skrabec 			 const struct drm_display_mode *mode)
38b7c7436aSJernej Skrabec {
39b7c7436aSJernej Skrabec 	if (mode->clock > 297000)
40b7c7436aSJernej Skrabec 		return MODE_CLOCK_HIGH;
41b7c7436aSJernej Skrabec 
42b7c7436aSJernej Skrabec 	return MODE_OK;
43b7c7436aSJernej Skrabec }
44b7c7436aSJernej Skrabec 
4557e23de0SJernej Skrabec static bool sun8i_dw_hdmi_node_is_tcon_top(struct device_node *node)
4657e23de0SJernej Skrabec {
4757e23de0SJernej Skrabec 	return !!of_match_node(sun8i_tcon_top_of_table, node);
4857e23de0SJernej Skrabec }
4957e23de0SJernej Skrabec 
5057e23de0SJernej Skrabec static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
5157e23de0SJernej Skrabec 					     struct device_node *node)
5257e23de0SJernej Skrabec {
5357e23de0SJernej Skrabec 	struct device_node *port, *ep, *remote, *remote_port;
5457e23de0SJernej Skrabec 	u32 crtcs = 0;
5557e23de0SJernej Skrabec 
5657e23de0SJernej Skrabec 	port = of_graph_get_port_by_id(node, 0);
5757e23de0SJernej Skrabec 	if (!port)
5857e23de0SJernej Skrabec 		return 0;
5957e23de0SJernej Skrabec 
6057e23de0SJernej Skrabec 	ep = of_get_next_available_child(port, NULL);
6157e23de0SJernej Skrabec 	if (!ep)
6257e23de0SJernej Skrabec 		return 0;
6357e23de0SJernej Skrabec 
6457e23de0SJernej Skrabec 	remote = of_graph_get_remote_port_parent(ep);
6557e23de0SJernej Skrabec 	if (!remote)
6657e23de0SJernej Skrabec 		return 0;
6757e23de0SJernej Skrabec 
6857e23de0SJernej Skrabec 	if (sun8i_dw_hdmi_node_is_tcon_top(remote)) {
6957e23de0SJernej Skrabec 		port = of_graph_get_port_by_id(remote, 4);
7057e23de0SJernej Skrabec 		if (!port)
7157e23de0SJernej Skrabec 			return 0;
7257e23de0SJernej Skrabec 
7357e23de0SJernej Skrabec 		for_each_child_of_node(port, ep) {
7457e23de0SJernej Skrabec 			remote_port = of_graph_get_remote_port(ep);
7557e23de0SJernej Skrabec 			if (remote_port) {
7657e23de0SJernej Skrabec 				crtcs |= drm_of_crtc_port_mask(drm, remote_port);
7757e23de0SJernej Skrabec 				of_node_put(remote_port);
7857e23de0SJernej Skrabec 			}
7957e23de0SJernej Skrabec 		}
8057e23de0SJernej Skrabec 	} else {
8157e23de0SJernej Skrabec 		crtcs = drm_of_find_possible_crtcs(drm, node);
8257e23de0SJernej Skrabec 	}
8357e23de0SJernej Skrabec 
8457e23de0SJernej Skrabec 	return crtcs;
8557e23de0SJernej Skrabec }
8657e23de0SJernej Skrabec 
87b7c7436aSJernej Skrabec static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
88b7c7436aSJernej Skrabec 			      void *data)
89b7c7436aSJernej Skrabec {
90b7c7436aSJernej Skrabec 	struct platform_device *pdev = to_platform_device(dev);
91b7c7436aSJernej Skrabec 	struct dw_hdmi_plat_data *plat_data;
92b7c7436aSJernej Skrabec 	struct drm_device *drm = data;
93b7c7436aSJernej Skrabec 	struct device_node *phy_node;
94b7c7436aSJernej Skrabec 	struct drm_encoder *encoder;
95b7c7436aSJernej Skrabec 	struct sun8i_dw_hdmi *hdmi;
96b7c7436aSJernej Skrabec 	int ret;
97b7c7436aSJernej Skrabec 
98b7c7436aSJernej Skrabec 	if (!pdev->dev.of_node)
99b7c7436aSJernej Skrabec 		return -ENODEV;
100b7c7436aSJernej Skrabec 
101b7c7436aSJernej Skrabec 	hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
102b7c7436aSJernej Skrabec 	if (!hdmi)
103b7c7436aSJernej Skrabec 		return -ENOMEM;
104b7c7436aSJernej Skrabec 
105b7c7436aSJernej Skrabec 	plat_data = &hdmi->plat_data;
106b7c7436aSJernej Skrabec 	hdmi->dev = &pdev->dev;
107b7c7436aSJernej Skrabec 	encoder = &hdmi->encoder;
108b7c7436aSJernej Skrabec 
10957e23de0SJernej Skrabec 	encoder->possible_crtcs =
11057e23de0SJernej Skrabec 		sun8i_dw_hdmi_find_possible_crtcs(drm, dev->of_node);
111b7c7436aSJernej Skrabec 	/*
112b7c7436aSJernej Skrabec 	 * If we failed to find the CRTC(s) which this encoder is
113b7c7436aSJernej Skrabec 	 * supposed to be connected to, it's because the CRTC has
114b7c7436aSJernej Skrabec 	 * not been registered yet.  Defer probing, and hope that
115b7c7436aSJernej Skrabec 	 * the required CRTC is added later.
116b7c7436aSJernej Skrabec 	 */
117b7c7436aSJernej Skrabec 	if (encoder->possible_crtcs == 0)
118b7c7436aSJernej Skrabec 		return -EPROBE_DEFER;
119b7c7436aSJernej Skrabec 
120b7c7436aSJernej Skrabec 	hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl");
121b7c7436aSJernej Skrabec 	if (IS_ERR(hdmi->rst_ctrl)) {
122b7c7436aSJernej Skrabec 		dev_err(dev, "Could not get ctrl reset control\n");
123b7c7436aSJernej Skrabec 		return PTR_ERR(hdmi->rst_ctrl);
124b7c7436aSJernej Skrabec 	}
125b7c7436aSJernej Skrabec 
126b7c7436aSJernej Skrabec 	hdmi->clk_tmds = devm_clk_get(dev, "tmds");
127b7c7436aSJernej Skrabec 	if (IS_ERR(hdmi->clk_tmds)) {
128b7c7436aSJernej Skrabec 		dev_err(dev, "Couldn't get the tmds clock\n");
129b7c7436aSJernej Skrabec 		return PTR_ERR(hdmi->clk_tmds);
130b7c7436aSJernej Skrabec 	}
131b7c7436aSJernej Skrabec 
132b7c7436aSJernej Skrabec 	ret = reset_control_deassert(hdmi->rst_ctrl);
133b7c7436aSJernej Skrabec 	if (ret) {
134b7c7436aSJernej Skrabec 		dev_err(dev, "Could not deassert ctrl reset control\n");
135b7c7436aSJernej Skrabec 		return ret;
136b7c7436aSJernej Skrabec 	}
137b7c7436aSJernej Skrabec 
138b7c7436aSJernej Skrabec 	ret = clk_prepare_enable(hdmi->clk_tmds);
139b7c7436aSJernej Skrabec 	if (ret) {
140b7c7436aSJernej Skrabec 		dev_err(dev, "Could not enable tmds clock\n");
141b7c7436aSJernej Skrabec 		goto err_assert_ctrl_reset;
142b7c7436aSJernej Skrabec 	}
143b7c7436aSJernej Skrabec 
144b7c7436aSJernej Skrabec 	phy_node = of_parse_phandle(dev->of_node, "phys", 0);
145b7c7436aSJernej Skrabec 	if (!phy_node) {
146b7c7436aSJernej Skrabec 		dev_err(dev, "Can't found PHY phandle\n");
147b7c7436aSJernej Skrabec 		goto err_disable_clk_tmds;
148b7c7436aSJernej Skrabec 	}
149b7c7436aSJernej Skrabec 
150b7c7436aSJernej Skrabec 	ret = sun8i_hdmi_phy_probe(hdmi, phy_node);
151b7c7436aSJernej Skrabec 	of_node_put(phy_node);
152b7c7436aSJernej Skrabec 	if (ret) {
153b7c7436aSJernej Skrabec 		dev_err(dev, "Couldn't get the HDMI PHY\n");
154b7c7436aSJernej Skrabec 		goto err_disable_clk_tmds;
155b7c7436aSJernej Skrabec 	}
156b7c7436aSJernej Skrabec 
157b7c7436aSJernej Skrabec 	drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
158b7c7436aSJernej Skrabec 	drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
159b7c7436aSJernej Skrabec 			 DRM_MODE_ENCODER_TMDS, NULL);
160b7c7436aSJernej Skrabec 
161b7c7436aSJernej Skrabec 	sun8i_hdmi_phy_init(hdmi->phy);
162b7c7436aSJernej Skrabec 
163b7c7436aSJernej Skrabec 	plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid;
164b7c7436aSJernej Skrabec 	plat_data->phy_ops = sun8i_hdmi_phy_get_ops();
165b7c7436aSJernej Skrabec 	plat_data->phy_name = "sun8i_dw_hdmi_phy";
166b7c7436aSJernej Skrabec 	plat_data->phy_data = hdmi->phy;
167b7c7436aSJernej Skrabec 
168b7c7436aSJernej Skrabec 	platform_set_drvdata(pdev, hdmi);
169b7c7436aSJernej Skrabec 
170b7c7436aSJernej Skrabec 	hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
171b7c7436aSJernej Skrabec 
172b7c7436aSJernej Skrabec 	/*
173b7c7436aSJernej Skrabec 	 * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
174b7c7436aSJernej Skrabec 	 * which would have called the encoder cleanup.  Do it manually.
175b7c7436aSJernej Skrabec 	 */
176b7c7436aSJernej Skrabec 	if (IS_ERR(hdmi->hdmi)) {
177b7c7436aSJernej Skrabec 		ret = PTR_ERR(hdmi->hdmi);
178b7c7436aSJernej Skrabec 		goto cleanup_encoder;
179b7c7436aSJernej Skrabec 	}
180b7c7436aSJernej Skrabec 
181b7c7436aSJernej Skrabec 	return 0;
182b7c7436aSJernej Skrabec 
183b7c7436aSJernej Skrabec cleanup_encoder:
184b7c7436aSJernej Skrabec 	drm_encoder_cleanup(encoder);
185b7c7436aSJernej Skrabec 	sun8i_hdmi_phy_remove(hdmi);
186b7c7436aSJernej Skrabec err_disable_clk_tmds:
187b7c7436aSJernej Skrabec 	clk_disable_unprepare(hdmi->clk_tmds);
188b7c7436aSJernej Skrabec err_assert_ctrl_reset:
189b7c7436aSJernej Skrabec 	reset_control_assert(hdmi->rst_ctrl);
190b7c7436aSJernej Skrabec 
191b7c7436aSJernej Skrabec 	return ret;
192b7c7436aSJernej Skrabec }
193b7c7436aSJernej Skrabec 
194b7c7436aSJernej Skrabec static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
195b7c7436aSJernej Skrabec 				 void *data)
196b7c7436aSJernej Skrabec {
197b7c7436aSJernej Skrabec 	struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev);
198b7c7436aSJernej Skrabec 
199b7c7436aSJernej Skrabec 	dw_hdmi_unbind(hdmi->hdmi);
200b7c7436aSJernej Skrabec 	sun8i_hdmi_phy_remove(hdmi);
201b7c7436aSJernej Skrabec 	clk_disable_unprepare(hdmi->clk_tmds);
202b7c7436aSJernej Skrabec 	reset_control_assert(hdmi->rst_ctrl);
203b7c7436aSJernej Skrabec }
204b7c7436aSJernej Skrabec 
205b7c7436aSJernej Skrabec static const struct component_ops sun8i_dw_hdmi_ops = {
206b7c7436aSJernej Skrabec 	.bind	= sun8i_dw_hdmi_bind,
207b7c7436aSJernej Skrabec 	.unbind	= sun8i_dw_hdmi_unbind,
208b7c7436aSJernej Skrabec };
209b7c7436aSJernej Skrabec 
210b7c7436aSJernej Skrabec static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
211b7c7436aSJernej Skrabec {
212b7c7436aSJernej Skrabec 	return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
213b7c7436aSJernej Skrabec }
214b7c7436aSJernej Skrabec 
215b7c7436aSJernej Skrabec static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
216b7c7436aSJernej Skrabec {
217b7c7436aSJernej Skrabec 	component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
218b7c7436aSJernej Skrabec 
219b7c7436aSJernej Skrabec 	return 0;
220b7c7436aSJernej Skrabec }
221b7c7436aSJernej Skrabec 
222b7c7436aSJernej Skrabec static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
223b7c7436aSJernej Skrabec 	{ .compatible = "allwinner,sun8i-a83t-dw-hdmi" },
224b7c7436aSJernej Skrabec 	{ /* sentinel */ },
225b7c7436aSJernej Skrabec };
226b7c7436aSJernej Skrabec MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
227b7c7436aSJernej Skrabec 
228b7c7436aSJernej Skrabec struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
229b7c7436aSJernej Skrabec 	.probe  = sun8i_dw_hdmi_probe,
230b7c7436aSJernej Skrabec 	.remove = sun8i_dw_hdmi_remove,
231b7c7436aSJernej Skrabec 	.driver = {
232b7c7436aSJernej Skrabec 		.name = "sun8i-dw-hdmi",
233b7c7436aSJernej Skrabec 		.of_match_table = sun8i_dw_hdmi_dt_ids,
234b7c7436aSJernej Skrabec 	},
235b7c7436aSJernej Skrabec };
236b7c7436aSJernej Skrabec module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
237b7c7436aSJernej Skrabec 
238b7c7436aSJernej Skrabec MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
239b7c7436aSJernej Skrabec MODULE_DESCRIPTION("Allwinner DW HDMI bridge");
240b7c7436aSJernej Skrabec MODULE_LICENSE("GPL");
241