1 /* $NetBSD: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $ */ 2 3 /*- 4 * Copyright (c) 2019 Jared McNeill <jmcneill@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: hdmi_connector.c,v 1.1 2019/01/30 01:24:00 jmcneill Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/device.h> 35 #include <sys/systm.h> 36 #include <sys/gpio.h> 37 38 #include <dev/fdt/fdtvar.h> 39 #include <dev/fdt/fdt_port.h> 40 41 #include <dev/i2c/ddcvar.h> 42 43 #include <drm/drmP.h> 44 #include <drm/drm_crtc.h> 45 #include <drm/drm_crtc_helper.h> 46 #include <drm/drm_edid.h> 47 48 static const char * const compatible[] = { 49 "hdmi-connector", 50 NULL 51 }; 52 53 struct dispcon_hdmi_connector { 54 struct drm_connector base; 55 struct fdtbus_gpio_pin *hpd; 56 i2c_tag_t ddc; 57 58 int type; /* DRM_MODE_CONNECTOR_* */ 59 }; 60 61 struct dispcon_hdmi_softc { 62 struct fdt_device_ports sc_ports; 63 struct dispcon_hdmi_connector sc_connector; 64 }; 65 66 #define to_dispcon_hdmi_connector(x) container_of(x, struct dispcon_hdmi_connector, base) 67 68 static enum drm_connector_status 69 dispcon_hdmi_connector_detect(struct drm_connector *connector, bool force) 70 { 71 struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); 72 bool con; 73 74 if (hdmi_connector->hpd == NULL) { 75 /* 76 * No hotplug detect pin available. Assume that we are connected. 77 */ 78 return connector_status_connected; 79 } 80 81 /* 82 * Read connect status from hotplug detect pin. 83 */ 84 con = fdtbus_gpio_read(hdmi_connector->hpd); 85 if (con) { 86 return connector_status_connected; 87 } else { 88 return connector_status_disconnected; 89 } 90 } 91 92 static void 93 dispcon_hdmi_connector_destroy(struct drm_connector *connector) 94 { 95 drm_connector_unregister(connector); 96 drm_connector_cleanup(connector); 97 } 98 99 static const struct drm_connector_funcs dispcon_hdmi_connector_funcs = { 100 .dpms = drm_helper_connector_dpms, 101 .detect = dispcon_hdmi_connector_detect, 102 .fill_modes = drm_helper_probe_single_connector_modes, 103 .destroy = dispcon_hdmi_connector_destroy, 104 }; 105 106 static int 107 dispcon_hdmi_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode) 108 { 109 return MODE_OK; 110 } 111 112 static int 113 dispcon_hdmi_connector_get_modes(struct drm_connector *connector) 114 { 115 struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); 116 char edid[EDID_LENGTH * 4]; 117 struct edid *pedid = NULL; 118 int error, block; 119 120 if (hdmi_connector->ddc != NULL) { 121 memset(edid, 0, sizeof(edid)); 122 for (block = 0; block < 4; block++) { 123 error = ddc_read_edid_block(hdmi_connector->ddc, 124 &edid[block * EDID_LENGTH], EDID_LENGTH, block); 125 if (error) 126 break; 127 if (block == 0) { 128 pedid = (struct edid *)edid; 129 if (edid[0x7e] == 0) 130 break; 131 } 132 } 133 } 134 135 drm_mode_connector_update_edid_property(connector, pedid); 136 if (pedid == NULL) 137 return 0; 138 139 error = drm_add_edid_modes(connector, pedid); 140 drm_edid_to_eld(connector, pedid); 141 142 return error; 143 } 144 145 static struct drm_encoder * 146 dispcon_hdmi_connector_best_encoder(struct drm_connector *connector) 147 { 148 int enc_id = connector->encoder_ids[0]; 149 struct drm_mode_object *obj; 150 struct drm_encoder *encoder = NULL; 151 152 if (enc_id) { 153 obj = drm_mode_object_find(connector->dev, enc_id, 154 DRM_MODE_OBJECT_ENCODER); 155 if (obj == NULL) 156 return NULL; 157 encoder = obj_to_encoder(obj); 158 } 159 160 return encoder; 161 } 162 163 static const struct drm_connector_helper_funcs dispcon_hdmi_connector_helper_funcs = { 164 .mode_valid = dispcon_hdmi_connector_mode_valid, 165 .get_modes = dispcon_hdmi_connector_get_modes, 166 .best_encoder = dispcon_hdmi_connector_best_encoder, 167 }; 168 169 static int 170 dispcon_hdmi_ep_activate(device_t dev, struct fdt_endpoint *ep, bool activate) 171 { 172 struct drm_connector *connector = fdt_endpoint_get_data(ep); 173 struct dispcon_hdmi_connector *hdmi_connector = to_dispcon_hdmi_connector(connector); 174 struct fdt_endpoint *rep = fdt_endpoint_remote(ep); 175 struct drm_encoder *encoder; 176 177 if (fdt_endpoint_port_index(ep) != 0) 178 return EINVAL; 179 180 if (fdt_endpoint_type(rep) != EP_DRM_ENCODER) 181 return EINVAL; 182 183 if (activate) { 184 encoder = fdt_endpoint_get_data(rep); 185 186 connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; 187 connector->interlace_allowed = 0; 188 connector->doublescan_allowed = 0; 189 190 drm_connector_init(encoder->dev, connector, &dispcon_hdmi_connector_funcs, 191 hdmi_connector->type); 192 drm_connector_helper_add(connector, &dispcon_hdmi_connector_helper_funcs); 193 drm_connector_register(connector); 194 drm_mode_connector_attach_encoder(connector, encoder); 195 } 196 197 return 0; 198 } 199 200 static void * 201 dispcon_hdmi_ep_get_data(device_t dev, struct fdt_endpoint *ep) 202 { 203 struct dispcon_hdmi_softc * const sc = device_private(dev); 204 205 return &sc->sc_connector.base; 206 } 207 208 static int 209 dispcon_hdmi_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 dispcon_hdmi_attach(device_t parent, device_t self, void *aux) 218 { 219 struct dispcon_hdmi_softc * const sc = device_private(self); 220 struct dispcon_hdmi_connector * const hdmi_connector = &sc->sc_connector; 221 struct fdt_attach_args * const faa = aux; 222 const int phandle = faa->faa_phandle; 223 224 aprint_naive("\n"); 225 aprint_normal(": HDMI connector\n"); 226 227 hdmi_connector->type = DRM_MODE_CONNECTOR_HDMIA; 228 hdmi_connector->hpd = fdtbus_gpio_acquire(phandle, "hpd-gpios", GPIO_PIN_INPUT); 229 hdmi_connector->ddc = fdtbus_i2c_acquire(phandle, "ddc-i2c-bus"); 230 231 sc->sc_ports.dp_ep_activate = dispcon_hdmi_ep_activate; 232 sc->sc_ports.dp_ep_get_data = dispcon_hdmi_ep_get_data; 233 fdt_ports_register(&sc->sc_ports, self, phandle, EP_DRM_CONNECTOR); 234 } 235 236 CFATTACH_DECL_NEW(dispcon_hdmi, sizeof(struct dispcon_hdmi_softc), 237 dispcon_hdmi_match, dispcon_hdmi_attach, NULL, NULL); 238