GNU Linux-libre 4.19.264-gnu1
[releases.git] / drivers / gpu / drm / sun4i / sun8i_dw_hdmi.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (c) 2018 Jernej Skrabec <jernej.skrabec@siol.net>
4  */
5
6 #include <linux/component.h>
7 #include <linux/module.h>
8 #include <linux/platform_device.h>
9
10 #include <drm/drm_of.h>
11 #include <drm/drmP.h>
12 #include <drm/drm_crtc_helper.h>
13
14 #include "sun8i_dw_hdmi.h"
15 #include "sun8i_tcon_top.h"
16
17 static void sun8i_dw_hdmi_encoder_mode_set(struct drm_encoder *encoder,
18                                            struct drm_display_mode *mode,
19                                            struct drm_display_mode *adj_mode)
20 {
21         struct sun8i_dw_hdmi *hdmi = encoder_to_sun8i_dw_hdmi(encoder);
22
23         clk_set_rate(hdmi->clk_tmds, mode->crtc_clock * 1000);
24 }
25
26 static const struct drm_encoder_helper_funcs
27 sun8i_dw_hdmi_encoder_helper_funcs = {
28         .mode_set = sun8i_dw_hdmi_encoder_mode_set,
29 };
30
31 static const struct drm_encoder_funcs sun8i_dw_hdmi_encoder_funcs = {
32         .destroy = drm_encoder_cleanup,
33 };
34
35 static enum drm_mode_status
36 sun8i_dw_hdmi_mode_valid(struct drm_connector *connector,
37                          const struct drm_display_mode *mode)
38 {
39         if (mode->clock > 297000)
40                 return MODE_CLOCK_HIGH;
41
42         return MODE_OK;
43 }
44
45 static bool sun8i_dw_hdmi_node_is_tcon_top(struct device_node *node)
46 {
47         return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) &&
48                 !!of_match_node(sun8i_tcon_top_of_table, node);
49 }
50
51 static u32 sun8i_dw_hdmi_find_possible_crtcs(struct drm_device *drm,
52                                              struct device_node *node)
53 {
54         struct device_node *port, *ep, *remote, *remote_port;
55         u32 crtcs = 0;
56
57         remote = of_graph_get_remote_node(node, 0, -1);
58         if (!remote)
59                 return 0;
60
61         if (sun8i_dw_hdmi_node_is_tcon_top(remote)) {
62                 port = of_graph_get_port_by_id(remote, 4);
63                 if (!port)
64                         goto crtcs_exit;
65
66                 for_each_child_of_node(port, ep) {
67                         remote_port = of_graph_get_remote_port(ep);
68                         if (remote_port) {
69                                 crtcs |= drm_of_crtc_port_mask(drm, remote_port);
70                                 of_node_put(remote_port);
71                         }
72                 }
73         } else {
74                 crtcs = drm_of_find_possible_crtcs(drm, node);
75         }
76
77 crtcs_exit:
78         of_node_put(remote);
79
80         return crtcs;
81 }
82
83 static int sun8i_dw_hdmi_bind(struct device *dev, struct device *master,
84                               void *data)
85 {
86         struct platform_device *pdev = to_platform_device(dev);
87         struct dw_hdmi_plat_data *plat_data;
88         struct drm_device *drm = data;
89         struct device_node *phy_node;
90         struct drm_encoder *encoder;
91         struct sun8i_dw_hdmi *hdmi;
92         int ret;
93
94         if (!pdev->dev.of_node)
95                 return -ENODEV;
96
97         hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
98         if (!hdmi)
99                 return -ENOMEM;
100
101         plat_data = &hdmi->plat_data;
102         hdmi->dev = &pdev->dev;
103         encoder = &hdmi->encoder;
104
105         encoder->possible_crtcs =
106                 sun8i_dw_hdmi_find_possible_crtcs(drm, dev->of_node);
107         /*
108          * If we failed to find the CRTC(s) which this encoder is
109          * supposed to be connected to, it's because the CRTC has
110          * not been registered yet.  Defer probing, and hope that
111          * the required CRTC is added later.
112          */
113         if (encoder->possible_crtcs == 0)
114                 return -EPROBE_DEFER;
115
116         hdmi->rst_ctrl = devm_reset_control_get(dev, "ctrl");
117         if (IS_ERR(hdmi->rst_ctrl)) {
118                 dev_err(dev, "Could not get ctrl reset control\n");
119                 return PTR_ERR(hdmi->rst_ctrl);
120         }
121
122         hdmi->clk_tmds = devm_clk_get(dev, "tmds");
123         if (IS_ERR(hdmi->clk_tmds)) {
124                 dev_err(dev, "Couldn't get the tmds clock\n");
125                 return PTR_ERR(hdmi->clk_tmds);
126         }
127
128         ret = reset_control_deassert(hdmi->rst_ctrl);
129         if (ret) {
130                 dev_err(dev, "Could not deassert ctrl reset control\n");
131                 return ret;
132         }
133
134         ret = clk_prepare_enable(hdmi->clk_tmds);
135         if (ret) {
136                 dev_err(dev, "Could not enable tmds clock\n");
137                 goto err_assert_ctrl_reset;
138         }
139
140         phy_node = of_parse_phandle(dev->of_node, "phys", 0);
141         if (!phy_node) {
142                 dev_err(dev, "Can't found PHY phandle\n");
143                 ret = -EINVAL;
144                 goto err_disable_clk_tmds;
145         }
146
147         ret = sun8i_hdmi_phy_probe(hdmi, phy_node);
148         of_node_put(phy_node);
149         if (ret) {
150                 dev_err(dev, "Couldn't get the HDMI PHY\n");
151                 goto err_disable_clk_tmds;
152         }
153
154         drm_encoder_helper_add(encoder, &sun8i_dw_hdmi_encoder_helper_funcs);
155         drm_encoder_init(drm, encoder, &sun8i_dw_hdmi_encoder_funcs,
156                          DRM_MODE_ENCODER_TMDS, NULL);
157
158         sun8i_hdmi_phy_init(hdmi->phy);
159
160         plat_data->mode_valid = &sun8i_dw_hdmi_mode_valid;
161         plat_data->phy_ops = sun8i_hdmi_phy_get_ops();
162         plat_data->phy_name = "sun8i_dw_hdmi_phy";
163         plat_data->phy_data = hdmi->phy;
164
165         platform_set_drvdata(pdev, hdmi);
166
167         hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
168
169         /*
170          * If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
171          * which would have called the encoder cleanup.  Do it manually.
172          */
173         if (IS_ERR(hdmi->hdmi)) {
174                 ret = PTR_ERR(hdmi->hdmi);
175                 goto cleanup_encoder;
176         }
177
178         return 0;
179
180 cleanup_encoder:
181         drm_encoder_cleanup(encoder);
182         sun8i_hdmi_phy_remove(hdmi);
183 err_disable_clk_tmds:
184         clk_disable_unprepare(hdmi->clk_tmds);
185 err_assert_ctrl_reset:
186         reset_control_assert(hdmi->rst_ctrl);
187
188         return ret;
189 }
190
191 static void sun8i_dw_hdmi_unbind(struct device *dev, struct device *master,
192                                  void *data)
193 {
194         struct sun8i_dw_hdmi *hdmi = dev_get_drvdata(dev);
195
196         dw_hdmi_unbind(hdmi->hdmi);
197         sun8i_hdmi_phy_remove(hdmi);
198         clk_disable_unprepare(hdmi->clk_tmds);
199         reset_control_assert(hdmi->rst_ctrl);
200 }
201
202 static const struct component_ops sun8i_dw_hdmi_ops = {
203         .bind   = sun8i_dw_hdmi_bind,
204         .unbind = sun8i_dw_hdmi_unbind,
205 };
206
207 static int sun8i_dw_hdmi_probe(struct platform_device *pdev)
208 {
209         return component_add(&pdev->dev, &sun8i_dw_hdmi_ops);
210 }
211
212 static int sun8i_dw_hdmi_remove(struct platform_device *pdev)
213 {
214         component_del(&pdev->dev, &sun8i_dw_hdmi_ops);
215
216         return 0;
217 }
218
219 static const struct of_device_id sun8i_dw_hdmi_dt_ids[] = {
220         { .compatible = "allwinner,sun8i-a83t-dw-hdmi" },
221         { /* sentinel */ },
222 };
223 MODULE_DEVICE_TABLE(of, sun8i_dw_hdmi_dt_ids);
224
225 static struct platform_driver sun8i_dw_hdmi_pltfm_driver = {
226         .probe  = sun8i_dw_hdmi_probe,
227         .remove = sun8i_dw_hdmi_remove,
228         .driver = {
229                 .name = "sun8i-dw-hdmi",
230                 .of_match_table = sun8i_dw_hdmi_dt_ids,
231         },
232 };
233 module_platform_driver(sun8i_dw_hdmi_pltfm_driver);
234
235 MODULE_AUTHOR("Jernej Skrabec <jernej.skrabec@siol.net>");
236 MODULE_DESCRIPTION("Allwinner DW HDMI bridge");
237 MODULE_LICENSE("GPL");