jcs's openbsd hax
openbsd
at jcs 453 lines 12 kB view raw
1/* $OpenBSD: qcdpc.c,v 1.1 2025/07/17 15:52:10 kettenis Exp $ */ 2/* 3 * Copyright (c) 2025 Mark Kettenis <kettenis@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/param.h> 19#include <sys/systm.h> 20#include <sys/device.h> 21 22#include <machine/bus.h> 23#include <machine/fdt.h> 24#include <machine/simplebusvar.h> 25 26#include <dev/ofw/openfirm.h> 27#include <dev/ofw/fdt.h> 28 29#include <drm/display/drm_dp_helper.h> 30 31#include <dev/wscons/wsconsio.h> 32#include <dev/wscons/wsdisplayvar.h> 33 34#define DP_HW_REVISION 0x00 35#define DP_INTR_STATUS 0x20 36#define DP_INTR_AUX_XFER_DONE (1U << 3) 37#define DP_INTR_WRONG_ADDR (1U << 6) 38#define DP_INTR_TIMEOUT (1U << 9) 39#define DP_INTR_NACK_DEFER (1U << 12) 40#define DP_INTR_WRONG_DATA_CNT (1U << 15) 41#define DP_INTR_AUX_ERROR (1U << 27) 42#define DP_INTR_STATUS_ACK_SHIFT 1 43#define DP_INTR_STATUS_MASK_SHIFT 2 44#define DP_INTR_ERROR \ 45 (DP_INTR_WRONG_ADDR | DP_INTR_TIMEOUT | DP_INTR_NACK_DEFER | \ 46 DP_INTR_WRONG_DATA_CNT | DP_INTR_AUX_ERROR) 47 48#define DP_AUX_CTRL 0x30 49#define DP_AUX_DATA 0x34 50#define DP_AUX_DATA_SHIFT 8 51#define DP_AUX_DATA_MASK (0xffU << 8) 52#define DP_AUX_DATA_READ (1U << 0) 53#define DP_AUX_DATA_WRITE (0U << 0) 54#define DP_AUX_DATA_INDEX_SHIFT 16 55#define DP_AUX_DATA_INDEX_MASK (0xffU << 16) 56#define DP_AUX_DATA_INDEX_WRITE (1U << 31) 57#define DP_AUX_TRANS_CTRL 0x38 58#define DP_AUX_TRANS_CTRL_GO (1U << 9) 59#define DP_PHY_AUX_INTERRUPT_CLEAR 0x4c 60#define DP_PHY_AUX_INTERRUPT_STATUS 0xbc 61 62struct qcdpc_softc { 63 struct simplebus_softc sc_sbus; 64 bus_space_tag_t sc_iot; 65 bus_space_handle_t sc_ahb_ioh; 66 bus_space_handle_t sc_aux_ioh; 67 void *sc_ih; 68 69 struct drm_dp_aux sc_aux; 70 uint32_t sc_intr_status; 71 72 struct drm_edp_backlight_info sc_bl; 73 uint32_t sc_bl_level; 74}; 75 76struct qcdpc_softc *qcdpc_bl; 77 78int qcdpc_match(struct device *, void *, void *); 79void qcdpc_attach(struct device *, struct device *, void *); 80 81const struct cfattach qcdpc_ca = { 82 sizeof(struct qcdpc_softc), qcdpc_match, qcdpc_attach 83}; 84 85struct cfdriver qcdpc_cd = { 86 NULL, "qcdpc", DV_DULL 87}; 88 89int qcdpc_print(void *, const char *); 90void qcdpc_attach_backlight(struct qcdpc_softc *); 91int qcdpc_intr(void *); 92ssize_t qcdpc_dp_aux_transfer(struct drm_dp_aux *, struct drm_dp_aux_msg *); 93 94int 95qcdpc_match(struct device *parent, void *match, void *aux) 96{ 97 struct fdt_attach_args *faa = aux; 98 99 return (OF_is_compatible(faa->fa_node, "qcom,sc8280xp-dp") || 100 OF_is_compatible(faa->fa_node, "qcom,sc8280xp-edp") || 101 OF_is_compatible(faa->fa_node, "qcom,x1e80100-dp")); 102} 103 104void 105qcdpc_attach(struct device *parent, struct device *self, void *aux) 106{ 107 struct qcdpc_softc *sc = (struct qcdpc_softc *)self; 108 struct fdt_attach_args *faa = aux; 109 struct fdt_attach_args fa; 110 int node; 111 112 /* 113 * Check that we have an AUX channel as all the functionality 114 * provided by this driver requires one. 115 * 116 * XXX Effectively this means we only attach to eDP 117 * controllers. Since the eDP controller is almost certainly 118 * enabled on all machines we care about, this means we can 119 * get away with not having a clock/reset controller. 120 * Accessing registers on a disabled hardware block tends to 121 * result in an instant reset on Qualcomm SoCs. 122 */ 123 node = OF_getnodebyname(faa->fa_node, "aux-bus"); 124 if (node == 0) { 125 printf("\n"); 126 return; 127 } 128 129 if (faa->fa_nreg < 2) { 130 printf(": no registers\n"); 131 return; 132 } 133 134 sc->sc_iot = faa->fa_iot; 135 136 if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, 137 faa->fa_reg[0].size, 0, &sc->sc_ahb_ioh)) { 138 printf(": can't map AHB registers\n"); 139 return; 140 } 141 142 if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr, 143 faa->fa_reg[1].size, 0, &sc->sc_aux_ioh)) { 144 printf(": can't map AUX registers\n"); 145 bus_space_unmap(sc->sc_iot, sc->sc_ahb_ioh, 146 faa->fa_reg[0].size); 147 return; 148 } 149 150 sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO, qcdpc_intr, 151 sc, sc->sc_sbus.sc_dev.dv_xname); 152 if (sc->sc_ih == NULL) { 153 printf(": can't establish interrupt\n"); 154 bus_space_unmap(sc->sc_iot, sc->sc_ahb_ioh, 155 faa->fa_reg[0].size); 156 bus_space_unmap(sc->sc_iot, sc->sc_aux_ioh, 157 faa->fa_reg[1].size); 158 return; 159 } 160 161 printf("\n"); 162 163 sc->sc_aux.name = sc->sc_sbus.sc_dev.dv_xname; 164 sc->sc_aux.transfer = qcdpc_dp_aux_transfer; 165 drm_dp_aux_init(&sc->sc_aux); 166 167 node = OF_getnodebyname(node, "panel"); 168 if (node) { 169 /* We have a panel. Try to enable its backlight control. */ 170 qcdpc_attach_backlight(sc); 171 172 memset(&fa, 0, sizeof(fa)); 173 fa.fa_name = ""; 174 fa.fa_node = node; 175 config_found(self, &fa, qcdpc_print); 176 } 177} 178 179int 180qcdpc_print(void *aux, const char *pnp) 181{ 182 struct fdt_attach_args *fa = aux; 183 char name[32]; 184 185 if (!pnp) 186 return (QUIET); 187 188 if (OF_getprop(fa->fa_node, "name", name, sizeof(name)) > 0) { 189 name[sizeof(name) - 1] = 0; 190 printf("\"%s\"", name); 191 } else 192 printf("node %u", fa->fa_node); 193 194 printf(" at %s", pnp); 195 196 return (UNCONF); 197} 198 199int 200qcdpc_get_param(struct wsdisplay_param *dp) 201{ 202 struct qcdpc_softc *sc = qcdpc_bl; 203 204 switch (dp->param) { 205 case WSDISPLAYIO_PARAM_BRIGHTNESS: 206 dp->min = 0; 207 dp->max = sc->sc_bl.max; 208 dp->curval = sc->sc_bl_level; 209 return 0; 210 default: 211 return -1; 212 } 213} 214 215int 216qcdpc_set_param(struct wsdisplay_param *dp) 217{ 218 struct qcdpc_softc *sc = qcdpc_bl; 219 int ret; 220 221 switch (dp->param) { 222 case WSDISPLAYIO_PARAM_BRIGHTNESS: 223 ret = drm_edp_backlight_set_level(&sc->sc_aux, 224 &sc->sc_bl, dp->curval); 225 if (ret < 0) 226 return -1; 227 sc->sc_bl_level = dp->curval; 228 return 0; 229 default: 230 return -1; 231 } 232} 233 234void 235qcdpc_attach_backlight(struct qcdpc_softc *sc) 236{ 237 uint8_t edp_dpcd[EDP_DISPLAY_CTL_CAP_SIZE]; 238 int ret; 239 240 ret = drm_dp_dpcd_read(&sc->sc_aux, DP_EDP_DPCD_REV, edp_dpcd, 241 EDP_DISPLAY_CTL_CAP_SIZE); 242 if (ret < 0) 243 return; 244 245 if (drm_edp_backlight_supported(edp_dpcd)) { 246 uint16_t current_level; 247 uint8_t current_mode; 248 249 ret = drm_edp_backlight_init(&sc->sc_aux, &sc->sc_bl, 0, 250 edp_dpcd, &current_level, &current_mode); 251 if (ret < 0 || !sc->sc_bl.aux_set) 252 return; 253 sc->sc_bl_level = current_level; 254 255 qcdpc_bl = sc; 256 ws_get_param = qcdpc_get_param; 257 ws_set_param = qcdpc_set_param; 258 } 259} 260 261int 262qcdpc_intr(void *arg) 263{ 264 struct qcdpc_softc *sc = arg; 265 int handled = 0; 266 uint32_t status; 267 268 status = bus_space_read_4(sc->sc_iot, sc->sc_ahb_ioh, DP_INTR_STATUS); 269 if (status & (DP_INTR_AUX_XFER_DONE | DP_INTR_ERROR)) { 270 sc->sc_intr_status = status; 271 handled = 1; 272 } 273 bus_space_write_4(sc->sc_iot, sc->sc_ahb_ioh, DP_INTR_STATUS, 274 DP_INTR_AUX_XFER_DONE << DP_INTR_STATUS_ACK_SHIFT | 275 DP_INTR_ERROR << DP_INTR_STATUS_ACK_SHIFT); 276 277 return handled; 278} 279 280/* 281 * The function below implement a DP AUX channel backend for DRM and 282 * therefore follow the Linux convention of returning negative error 283 * values. 284 */ 285 286int 287qcdpc_dp_aux_wait(struct qcdpc_softc *sc) 288{ 289 uint32_t status; 290 int s, timo; 291 292 if (cold) { 293 for (timo = 250; timo > 0; timo--) { 294 status = bus_space_read_4(sc->sc_iot, sc->sc_ahb_ioh, 295 DP_INTR_STATUS); 296 if (status & (DP_INTR_AUX_XFER_DONE | DP_INTR_ERROR)) { 297 sc->sc_intr_status = status; 298 break; 299 } 300 delay(1000); 301 } 302 } else { 303 s = splbio(); 304 if (sc->sc_intr_status == 0) { 305 tsleep_nsec(&sc->sc_intr_status, PWAIT, "qcdpc", 306 MSEC_TO_NSEC(250)); 307 } 308 splx(s); 309 } 310 311 return sc->sc_intr_status ? 0 : -ETIMEDOUT; 312} 313 314ssize_t 315qcdpc_dp_aux_write(struct qcdpc_softc *sc, struct drm_dp_aux_msg *msg) 316{ 317 uint8_t buf[DP_AUX_MAX_PAYLOAD_BYTES + 4]; 318 uint8_t *msgbuf; 319 uint32_t val, stat; 320 size_t len; 321 int i, ret; 322 323 KASSERT(msg->size <= DP_AUX_MAX_PAYLOAD_BYTES); 324 325 if (msg->request == DP_AUX_NATIVE_READ) 326 len = 0; 327 else 328 len = msg->size; 329 330 buf[0] = msg->address >> 16; 331 if (msg->request == DP_AUX_NATIVE_READ) 332 buf[0] |= (1 << 4); 333 buf[1] = msg->address >> 8; 334 buf[2] = msg->address >> 0; 335 buf[3] = msg->size - 1; 336 memcpy(&buf[4], msg->buffer, len); 337 338 for (i = 0; i < len + 4; i++) { 339 val = buf[i] << DP_AUX_DATA_SHIFT; 340 val |= DP_AUX_DATA_WRITE; 341 if (i == 0) 342 val |= DP_AUX_DATA_INDEX_WRITE; 343 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, 344 DP_AUX_DATA, val); 345 } 346 347 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, DP_AUX_TRANS_CTRL, 0); 348 349 /* Clear HW interrupts. */ 350 bus_space_read_4(sc->sc_iot, sc->sc_aux_ioh, 351 DP_PHY_AUX_INTERRUPT_STATUS); 352 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, 353 DP_PHY_AUX_INTERRUPT_CLEAR, 0x1f); 354 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, 355 DP_PHY_AUX_INTERRUPT_CLEAR, 0x9f); 356 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, 357 DP_PHY_AUX_INTERRUPT_CLEAR, 0); 358 359 sc->sc_intr_status = 0; 360 bus_space_write_4(sc->sc_iot, sc->sc_ahb_ioh, DP_INTR_STATUS, 361 DP_INTR_AUX_XFER_DONE << DP_INTR_STATUS_ACK_SHIFT | 362 DP_INTR_ERROR << DP_INTR_STATUS_ACK_SHIFT | 363 DP_INTR_AUX_XFER_DONE << DP_INTR_STATUS_MASK_SHIFT | 364 DP_INTR_ERROR << DP_INTR_STATUS_MASK_SHIFT); 365 366 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, DP_AUX_TRANS_CTRL, 367 DP_AUX_TRANS_CTRL_GO); 368 369 ret = qcdpc_dp_aux_wait(sc); 370 371 bus_space_write_4(sc->sc_iot, sc->sc_ahb_ioh, DP_INTR_STATUS, 372 DP_INTR_AUX_XFER_DONE << DP_INTR_STATUS_ACK_SHIFT | 373 DP_INTR_ERROR << DP_INTR_STATUS_ACK_SHIFT); 374 375 if (ret < 0) 376 return ret; 377 378 return len; 379} 380 381ssize_t 382qcdpc_dp_aux_read(struct qcdpc_softc *sc, struct drm_dp_aux_msg *msg) 383{ 384 uint8_t *p = msg->buffer; 385 uint32_t val; 386 int i, j; 387 388 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, DP_AUX_TRANS_CTRL, 0); 389 390 val = DP_AUX_DATA_INDEX_WRITE | DP_AUX_DATA_READ; 391 bus_space_write_4(sc->sc_iot, sc->sc_aux_ioh, DP_AUX_DATA, val); 392 393 /* Discard the first byte. */ 394 bus_space_read_4(sc->sc_iot, sc->sc_aux_ioh, DP_AUX_DATA); 395 396 for (i = 0; i < msg->size; i++) { 397 val = bus_space_read_4(sc->sc_iot, sc->sc_aux_ioh, 398 DP_AUX_DATA); 399 *p++ = val >> DP_AUX_DATA_SHIFT; 400 j = (val & DP_AUX_DATA_INDEX_MASK) >> DP_AUX_DATA_INDEX_SHIFT; 401 if (j != i) 402 break; 403 } 404 405 return i; 406} 407 408ssize_t 409qcdpc_dp_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) 410{ 411 struct qcdpc_softc *sc = container_of(aux, struct qcdpc_softc, sc_aux); 412 ssize_t ret; 413 int native; 414 415 if (msg->request == DP_AUX_NATIVE_WRITE || 416 msg->request == DP_AUX_NATIVE_READ) 417 native = 1; 418 else 419 native = 0; 420 421 if (msg->size == 0 || msg->buffer == NULL) { 422 if (native) 423 msg->reply = DP_AUX_NATIVE_REPLY_ACK; 424 else 425 msg->reply = DP_AUX_I2C_REPLY_ACK; 426 return msg->size; 427 } 428 429 if ((native && msg->size > DP_AUX_MAX_PAYLOAD_BYTES) || 430 msg->size > 128) 431 return -EINVAL; 432 433 /* XXX */ 434 if (msg->request != DP_AUX_NATIVE_READ && 435 msg->request != DP_AUX_NATIVE_WRITE) 436 return -EINVAL; 437 438 ret = qcdpc_dp_aux_write(sc, msg); 439 if (ret < 0) 440 return ret; 441 442 if (msg->request == DP_AUX_NATIVE_READ) 443 ret = qcdpc_dp_aux_read(sc, msg); 444 445 if (sc->sc_intr_status & DP_INTR_TIMEOUT) 446 ret = -ETIMEDOUT; 447 else if (sc->sc_intr_status & DP_INTR_ERROR) 448 msg->reply = DP_AUX_NATIVE_REPLY_NACK; 449 else 450 msg->reply = DP_AUX_NATIVE_REPLY_ACK; 451 452 return ret; 453}