sunxi_dwhdmi.c revision 1.6 1 /* $NetBSD: sunxi_dwhdmi.c,v 1.6 2019/11/23 12:30:45 jmcneill Exp $ */
2
3 /*-
4 * Copyright (c) 2019 Jared D. McNeill <jmcneill (at) invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: sunxi_dwhdmi.c,v 1.6 2019/11/23 12:30:45 jmcneill Exp $");
31
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/device.h>
35 #include <sys/intr.h>
36 #include <sys/systm.h>
37 #include <sys/kernel.h>
38 #include <sys/conf.h>
39
40 #include <drm/drmP.h>
41
42 #include <dev/fdt/fdtvar.h>
43 #include <dev/fdt/fdt_port.h>
44
45 #include <dev/ic/dw_hdmi.h>
46
47 #include <arm/sunxi/sunxi_hdmiphy.h>
48
49 enum {
50 DWHDMI_PORT_INPUT = 0,
51 DWHDMI_PORT_OUTPUT = 1,
52 };
53
54 static const char * const compatible[] = {
55 "allwinner,sun8i-h3-dw-hdmi",
56 "allwinner,sun50i-a64-dw-hdmi",
57 NULL
58 };
59
60 struct sunxi_dwhdmi_softc {
61 struct dwhdmi_softc sc_base;
62 int sc_phandle;
63 struct fdtbus_phy *sc_phy;
64 struct fdtbus_regulator *sc_regulator;
65
66 struct fdt_device_ports sc_ports;
67 struct drm_display_mode sc_curmode;
68 };
69
70 #define to_sunxi_dwhdmi_softc(x) container_of(x, struct sunxi_dwhdmi_softc, sc_base)
71
72 static int
73 sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
74 {
75 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
76 struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
77 struct fdt_endpoint *out_ep, *out_rep;
78 struct drm_encoder *encoder;
79 struct drm_bridge *bridge;
80 int error;
81
82 if (!activate)
83 return EINVAL;
84
85 if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT)
86 return EINVAL;
87
88 switch (fdt_endpoint_type(in_ep)) {
89 case EP_DRM_ENCODER:
90 encoder = fdt_endpoint_get_data(in_ep);
91 break;
92 case EP_DRM_BRIDGE:
93 bridge = fdt_endpoint_get_data(in_ep);
94 encoder = bridge->encoder;
95 break;
96 default:
97 encoder = NULL;
98 break;
99 }
100
101 if (encoder == NULL)
102 return EINVAL;
103
104 if (sc->sc_regulator != NULL) {
105 error = fdtbus_regulator_enable(sc->sc_regulator);
106 if (error != 0) {
107 device_printf(dev, "couldn't enable supply\n");
108 return error;
109 }
110 }
111
112 error = dwhdmi_bind(&sc->sc_base, encoder);
113 if (error != 0)
114 return error;
115
116 out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
117 if (out_ep != NULL) {
118 /* Ignore downstream connectors, we have our own. */
119 out_rep = fdt_endpoint_remote(out_ep);
120 if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
121 return 0;
122
123 error = fdt_endpoint_activate(out_ep, activate);
124 if (error != 0)
125 return error;
126 }
127
128 return 0;
129 }
130
131 static void *
132 sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
133 {
134 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
135
136 return &sc->sc_base.sc_bridge;
137 }
138
139 static enum drm_connector_status
140 sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
141 {
142 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
143
144 KASSERT(sc->sc_phy != NULL);
145
146 if (sunxi_hdmiphy_detect(sc->sc_phy, force))
147 return connector_status_connected;
148 else
149 return connector_status_disconnected;
150 }
151
152 static void
153 sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
154 {
155 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
156 int error;
157
158 KASSERT(sc->sc_phy != NULL);
159
160 error = fdtbus_phy_enable(sc->sc_phy, true);
161 if (error != 0) {
162 device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
163 return;
164 }
165
166 error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
167 if (error != 0)
168 device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
169 }
170
171 static void
172 sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
173 {
174 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
175 int error;
176
177 KASSERT(sc->sc_phy != NULL);
178
179 error = fdtbus_phy_enable(sc->sc_phy, false);
180 if (error != 0)
181 device_printf(dsc->sc_dev, "failed to disable phy\n");
182 }
183
184 static void
185 sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode,
186 struct drm_display_mode *adjusted_mode)
187 {
188 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
189
190 sc->sc_curmode = *adjusted_mode;
191 }
192
193 static audio_dai_tag_t
194 sunxi_dwhdmi_dai_get_tag(device_t dev, const void *data, size_t len)
195 {
196 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
197
198 if (len != 4)
199 return NULL;
200
201 return &sc->sc_base.sc_dai;
202 }
203
204 static struct fdtbus_dai_controller_func sunxi_dwhdmi_dai_funcs = {
205 .get_tag = sunxi_dwhdmi_dai_get_tag
206 };
207
208 static int
209 sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
210 {
211 struct fdt_attach_args * const faa = aux;
212
213 return of_match_compatible(faa->faa_phandle, compatible);
214 }
215
216 static void
217 sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
218 {
219 struct sunxi_dwhdmi_softc * const sc = device_private(self);
220 struct fdt_attach_args * const faa = aux;
221 const int phandle = faa->faa_phandle;
222 struct clk *clk_iahb, *clk_isfr, *clk_tmds;
223 struct fdtbus_reset *rst;
224 bus_addr_t addr;
225 bus_size_t size;
226
227 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
228 aprint_error(": couldn't get registers\n");
229 return;
230 }
231
232 rst = fdtbus_reset_get(phandle, "ctrl");
233 if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
234 aprint_error(": couldn't de-assert reset\n");
235 return;
236 }
237
238 clk_iahb = fdtbus_clock_get(phandle, "iahb");
239 if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
240 aprint_error(": couldn't enable iahb clock\n");
241 return;
242 }
243
244 clk_isfr = fdtbus_clock_get(phandle, "isfr");
245 if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
246 aprint_error(": couldn't enable isfr clock\n");
247 return;
248 }
249
250 clk_tmds = fdtbus_clock_get(phandle, "tmds");
251 if (clk_tmds == NULL || clk_enable(clk_tmds) != 0) {
252 aprint_error(": couldn't enable tmds clock\n");
253 return;
254 }
255
256 sc->sc_base.sc_dev = self;
257 sc->sc_base.sc_reg_width = 1;
258 sc->sc_base.sc_bst = faa->faa_bst;
259 if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
260 aprint_error(": couldn't map registers\n");
261 return;
262 }
263 sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
264 sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
265 sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
266 sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
267 sc->sc_base.sc_scl_hcnt = 0xd8;
268 sc->sc_base.sc_scl_lcnt = 0xfe;
269 sc->sc_phandle = faa->faa_phandle;
270
271 aprint_naive("\n");
272 aprint_normal(": HDMI TX\n");
273
274 sc->sc_regulator = fdtbus_regulator_acquire(sc->sc_phandle, "hvcc-supply");
275
276 sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
277 if (sc->sc_phy == NULL)
278 sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "phy");
279 if (sc->sc_phy == NULL) {
280 device_printf(self, "couldn't find PHY\n");
281 return;
282 }
283
284 sunxi_hdmiphy_init(sc->sc_phy);
285
286 if (dwhdmi_attach(&sc->sc_base) != 0) {
287 aprint_error_dev(self, "failed to attach driver\n");
288 return;
289 }
290
291 sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
292 sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
293 fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
294
295 fdtbus_register_dai_controller(self, phandle, &sunxi_dwhdmi_dai_funcs);
296 }
297
298 CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
299 sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);
300