sunxi_dwhdmi.c revision 1.2 1 /* $NetBSD: sunxi_dwhdmi.c,v 1.2 2019/01/31 01:49:28 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.2 2019/01/31 01:49:28 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
65 struct fdt_device_ports sc_ports;
66 struct drm_display_mode sc_curmode;
67 };
68
69 #define to_sunxi_dwhdmi_softc(x) container_of(x, struct sunxi_dwhdmi_softc, sc_base)
70
71 static int
72 sunxi_dwhdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate)
73 {
74 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
75 struct fdt_endpoint *in_ep = fdt_endpoint_remote(ep);
76 struct fdt_endpoint *out_ep, *out_rep;
77 struct drm_encoder *encoder;
78 struct drm_bridge *bridge;
79 int error;
80
81 if (!activate)
82 return EINVAL;
83
84 if (fdt_endpoint_port_index(ep) != DWHDMI_PORT_INPUT)
85 return EINVAL;
86
87 switch (fdt_endpoint_type(in_ep)) {
88 case EP_DRM_ENCODER:
89 encoder = fdt_endpoint_get_data(in_ep);
90 break;
91 case EP_DRM_BRIDGE:
92 bridge = fdt_endpoint_get_data(in_ep);
93 encoder = bridge->encoder;
94 break;
95 default:
96 encoder = NULL;
97 break;
98 }
99
100 if (encoder == NULL)
101 return EINVAL;
102
103 sc->sc_phy = fdtbus_phy_get(sc->sc_phandle, "hdmi-phy");
104 if (sc->sc_phy == NULL) {
105 device_printf(dev, "couldn't find hdmi-phy\n");
106 return ENXIO;
107 }
108
109 error = dwhdmi_bind(&sc->sc_base, encoder);
110 if (error != 0)
111 return error;
112
113 out_ep = fdt_endpoint_get_from_index(&sc->sc_ports, DWHDMI_PORT_OUTPUT, 0);
114 if (out_ep != NULL) {
115 /* Ignore downstream connectors, we have our own. */
116 out_rep = fdt_endpoint_remote(out_ep);
117 if (out_rep != NULL && fdt_endpoint_type(out_rep) == EP_DRM_CONNECTOR)
118 return 0;
119
120 error = fdt_endpoint_activate(out_ep, activate);
121 if (error != 0)
122 return error;
123 }
124
125 return 0;
126 }
127
128 static void *
129 sunxi_dwhdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep)
130 {
131 struct sunxi_dwhdmi_softc * const sc = device_private(dev);
132
133 return &sc->sc_base.sc_bridge;
134 }
135
136 static enum drm_connector_status
137 sunxi_dwhdmi_detect(struct dwhdmi_softc *dsc, bool force)
138 {
139 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
140
141 KASSERT(sc->sc_phy != NULL);
142
143 if (sunxi_hdmiphy_detect(sc->sc_phy, force))
144 return connector_status_connected;
145 else
146 return connector_status_disconnected;
147 }
148
149 static void
150 sunxi_dwhdmi_enable(struct dwhdmi_softc *dsc)
151 {
152 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
153 int error;
154
155 KASSERT(sc->sc_phy != NULL);
156
157 error = fdtbus_phy_enable(sc->sc_phy, true);
158 if (error != 0) {
159 device_printf(dsc->sc_dev, "failed to enable phy: %d\n", error);
160 return;
161 }
162
163 error = sunxi_hdmiphy_config(sc->sc_phy, &sc->sc_curmode);
164 if (error != 0)
165 device_printf(dsc->sc_dev, "failed to configure phy: %d\n", error);
166 }
167
168 static void
169 sunxi_dwhdmi_disable(struct dwhdmi_softc *dsc)
170 {
171 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
172 int error;
173
174 KASSERT(sc->sc_phy != NULL);
175
176 error = fdtbus_phy_enable(sc->sc_phy, false);
177 if (error != 0)
178 device_printf(dsc->sc_dev, "failed to disable phy\n");
179 }
180
181 static void
182 sunxi_dwhdmi_mode_set(struct dwhdmi_softc *dsc, struct drm_display_mode *mode,
183 struct drm_display_mode *adjusted_mode)
184 {
185 struct sunxi_dwhdmi_softc * const sc = to_sunxi_dwhdmi_softc(dsc);
186
187 sc->sc_curmode = *adjusted_mode;
188 }
189
190 static int
191 sunxi_dwhdmi_match(device_t parent, cfdata_t cf, void *aux)
192 {
193 struct fdt_attach_args * const faa = aux;
194
195 return of_match_compatible(faa->faa_phandle, compatible);
196 }
197
198 static void
199 sunxi_dwhdmi_attach(device_t parent, device_t self, void *aux)
200 {
201 struct sunxi_dwhdmi_softc * const sc = device_private(self);
202 struct fdt_attach_args * const faa = aux;
203 const int phandle = faa->faa_phandle;
204 struct clk *clk_iahb, *clk_isfr;
205 struct fdtbus_reset *rst;
206 bus_addr_t addr;
207 bus_size_t size;
208
209 if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
210 aprint_error(": couldn't get registers\n");
211 return;
212 }
213
214 rst = fdtbus_reset_get(phandle, "ctrl");
215 if (rst == NULL || fdtbus_reset_deassert(rst) != 0) {
216 aprint_error(": couldn't de-assert reset\n");
217 return;
218 }
219
220 clk_iahb = fdtbus_clock_get(phandle, "iahb");
221 if (clk_iahb == NULL || clk_enable(clk_iahb) != 0) {
222 aprint_error(": couldn't enable iahb clock\n");
223 return;
224 }
225
226 clk_isfr = fdtbus_clock_get(phandle, "isfr");
227 if (clk_isfr == NULL || clk_enable(clk_isfr) != 0) {
228 aprint_error(": couldn't enable isfr clock\n");
229 return;
230 }
231
232 sc->sc_base.sc_dev = self;
233 sc->sc_base.sc_reg_width = 1;
234 sc->sc_base.sc_bst = faa->faa_bst;
235 if (bus_space_map(sc->sc_base.sc_bst, addr, size, 0, &sc->sc_base.sc_bsh) != 0) {
236 aprint_error(": couldn't map registers\n");
237 return;
238 }
239 sc->sc_base.sc_detect = sunxi_dwhdmi_detect;
240 sc->sc_base.sc_enable = sunxi_dwhdmi_enable;
241 sc->sc_base.sc_disable = sunxi_dwhdmi_disable;
242 sc->sc_base.sc_mode_set = sunxi_dwhdmi_mode_set;
243 sc->sc_phandle = faa->faa_phandle;
244
245 aprint_naive("\n");
246 aprint_normal(": HDMI TX\n");
247
248 if (dwhdmi_attach(&sc->sc_base) != 0) {
249 aprint_error_dev(self, "failed to attach driver\n");
250 return;
251 }
252
253 sc->sc_ports.dp_ep_activate = sunxi_dwhdmi_ep_activate;
254 sc->sc_ports.dp_ep_get_data = sunxi_dwhdmi_ep_get_data;
255 fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_BRIDGE);
256 }
257
258 CFATTACH_DECL_NEW(sunxi_dwhdmi, sizeof(struct sunxi_dwhdmi_softc),
259 sunxi_dwhdmi_match, sunxi_dwhdmi_attach, NULL, NULL);
260