sunxi_dwhdmi.c revision 1.5 1 /* $NetBSD: sunxi_dwhdmi.c,v 1.5 2019/11/22 19:48:58 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.5 2019/11/22 19:48:58 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 sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
105 if (sc->sc_phy == NULL) {
106 device_printf(dev, "couldn't find hdmi-phy\n");
107 return ENXIO;
108 }
109
110 sc->sc_regulator = fdtbus_regulator_acquire(sc->sc_phandle, "hvcc-supply");
111 if (sc->sc_regulator != NULL) {
112 error = fdtbus_regulator_enable(sc->sc_regulator);
113 if (error != 0) {
114 device_printf(dev, "couldn't enable supply\n");
115 return error;
116 }
117 }
118
119 error = dwhdmi_bind(&sc->sc_base, encoder);
120 if (error != 0)
121 return error;
122
123 out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
124 if (out_ep != NULL) {
125 /* Ignore downstream connectors, we have our own. */
126 out_rep = fdt_endpoint_remote(out_ep);
127 if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
128 return 0;
129
130 error = fdt_endpoint_activate(out_ep, activate);
131 if (error != 0)
132 return error;
133 }
134
135 return 0;
136 }
137
138 static void *
139 sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
140 {
141 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
142
143 return &sc->sc_base.sc_bridge;
144 }
145
146 static enum drm_connector_status
147 sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
148 {
149 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
150
151 KASSERT(sc->sc_phy != NULL);
152
153 if (sunxi_hdmiphy_detect(sc->sc_phy, force))
154 return connector_status_connected;
155 else
156 return connector_status_disconnected;
157 }
158
159 static void
160 sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
161 {
162 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
163 int error;
164
165 KASSERT(sc->sc_phy != NULL);
166
167 error = fdtbus_phy_enable(sc->sc_phy, true);
168 if (error != 0) {
169 device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
170 return;
171 }
172
173 error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
174 if (error != 0)
175 device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
176 }
177
178 static void
179 sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
180 {
181 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
182 int error;
183
184 KASSERT(sc->sc_phy != NULL);
185
186 error = fdtbus_phy_enable(sc->sc_phy, false);
187 if (error != 0)
188 device_printf(dsc->sc_dev, "failed to disable phy\n");
189 }
190
191 static void
192 sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode,
193 struct drm_display_mode *adjusted_mode)
194 {
195 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
196
197 sc->sc_curmode = *adjusted_mode;
198 }
199
200 static audio_dai_tag_t
201 sunxi_dwhdmi_dai_get_tag(device_t dev, const void *data, size_t len)
202 {
203 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
204
205 if (len != 4)
206 return NULL;
207
208 return &sc->sc_base.sc_dai;
209 }
210
211 static struct fdtbus_dai_controller_func sunxi_dwhdmi_dai_funcs = {
212 .get_tag = sunxi_dwhdmi_dai_get_tag
213 };
214
215 static int
216 sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
217 {
218 struct fdt_attach_args * const faa = aux;
219
220 return of_match_compatible(faa->faa_phandle, compatible);
221 }
222
223 static void
224 sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
225 {
226 struct sunxi_dwhdmi_softc * const sc = device_private(self);
227 struct fdt_attach_args * const faa = aux;
228 const int phandle = faa->faa_phandle;
229 struct clk *clk_iahb, *clk_isfr, *clk_tmds;
230 struct fdtbus_reset *rst;
231 bus_addr_t addr;
232 bus_size_t size;
233
234 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
235 aprint_error(": couldn't get registers\n");
236 return;
237 }
238
239 rst = fdtbus_reset_get(phandle, "ctrl");
240 if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
241 aprint_error(": couldn't de-assert reset\n");
242 return;
243 }
244
245 clk_iahb = fdtbus_clock_get(phandle, "iahb");
246 if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
247 aprint_error(": couldn't enable iahb clock\n");
248 return;
249 }
250
251 clk_isfr = fdtbus_clock_get(phandle, "isfr");
252 if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
253 aprint_error(": couldn't enable isfr clock\n");
254 return;
255 }
256
257 clk_tmds = fdtbus_clock_get(phandle, "tmds");
258 if (clk_tmds == NULL || clk_enable(clk_tmds) != 0) {
259 aprint_error(": couldn't enable tmds clock\n");
260 return;
261 }
262
263 sc->sc_base.sc_dev = self;
264 sc->sc_base.sc_reg_width = 1;
265 sc->sc_base.sc_bst = faa->faa_bst;
266 if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
267 aprint_error(": couldn't map registers\n");
268 return;
269 }
270 sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
271 sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
272 sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
273 sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
274 sc->sc_phandle = faa->faa_phandle;
275
276 aprint_naive("\n");
277 aprint_normal(": HDMI TX\n");
278
279 if (dwhdmi_attach(&sc->sc_base) != 0) {
280 aprint_error_dev(self, "failed to attach driver\n");
281 return;
282 }
283
284 sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
285 sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
286 fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
287
288 fdtbus_register_dai_controller(self, phandle, &sunxi_dwhdmi_dai_funcs);
289 }
290
291 CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
292 sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);
293