1 /* $NetBSD: sunxi_drm.c,v 1.26 2022/09/25 07:50:23 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2019 Jared D. 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: sunxi_drm.c,v 1.26 2022/09/25 07:50:23 riastradh Exp $"); 31 32 #include <sys/param.h> 33 #include <sys/bus.h> 34 #include <sys/conf.h> 35 #include <sys/device.h> 36 #include <sys/intr.h> 37 #include <sys/kernel.h> 38 #include <sys/systm.h> 39 40 #include <uvm/uvm_device.h> 41 #include <uvm/uvm_extern.h> 42 #include <uvm/uvm_object.h> 43 44 #include <dev/fdt/fdt_port.h> 45 #include <dev/fdt/fdtvar.h> 46 47 #include <arm/sunxi/sunxi_drm.h> 48 49 #include <drm/drm_auth.h> 50 #include <drm/drm_crtc_helper.h> 51 #include <drm/drm_drv.h> 52 #include <drm/drm_fb_helper.h> 53 #include <drm/drm_fourcc.h> 54 #include <drm/drm_vblank.h> 55 56 #define SUNXI_DRM_MAX_WIDTH 3840 57 #define SUNXI_DRM_MAX_HEIGHT 2160 58 59 /* 60 * The DRM headers break trunc_page/round_page macros with a redefinition 61 * of PAGE_MASK. Use our own macros instead. 62 */ 63 #define SUNXI_PAGE_MASK (PAGE_SIZE - 1) 64 #define SUNXI_TRUNC_PAGE(x) ((x) & ~SUNXI_PAGE_MASK) 65 #define SUNXI_ROUND_PAGE(x) (((x) + SUNXI_PAGE_MASK) & ~SUNXI_PAGE_MASK) 66 67 static TAILQ_HEAD(, sunxi_drm_endpoint) sunxi_drm_endpoints = 68 TAILQ_HEAD_INITIALIZER(sunxi_drm_endpoints); 69 70 static const struct device_compatible_entry compat_data[] = { 71 { .compat = "allwinner,sun8i-h3-display-engine" }, 72 { .compat = "allwinner,sun8i-v3s-display-engine" }, 73 { .compat = "allwinner,sun50i-a64-display-engine" }, 74 DEVICE_COMPAT_EOL 75 }; 76 77 static const char * fb_compatible[] = { 78 "allwinner,simple-framebuffer", 79 NULL 80 }; 81 82 static int sunxi_drm_match(device_t, cfdata_t, void *); 83 static void sunxi_drm_attach(device_t, device_t, void *); 84 85 static void sunxi_drm_init(device_t); 86 static vmem_t *sunxi_drm_alloc_cma_pool(struct drm_device *, size_t); 87 88 static uint32_t sunxi_drm_get_vblank_counter(struct drm_device *, unsigned int); 89 static int sunxi_drm_enable_vblank(struct drm_device *, unsigned int); 90 static void sunxi_drm_disable_vblank(struct drm_device *, unsigned int); 91 92 static int sunxi_drm_load(struct drm_device *, unsigned long); 93 static void sunxi_drm_unload(struct drm_device *); 94 95 static void sunxi_drm_task_work(struct work *, void *); 96 97 static struct drm_driver sunxi_drm_driver = { 98 .driver_features = DRIVER_MODESET | DRIVER_GEM, 99 .dev_priv_size = 0, 100 .load = sunxi_drm_load, 101 .unload = sunxi_drm_unload, 102 103 .gem_free_object = drm_gem_cma_free_object, 104 .mmap_object = drm_gem_or_legacy_mmap_object, 105 .gem_uvm_ops = &drm_gem_cma_uvm_ops, 106 107 .dumb_create = drm_gem_cma_dumb_create, 108 .dumb_destroy = drm_gem_dumb_destroy, 109 110 .get_vblank_counter = sunxi_drm_get_vblank_counter, 111 .enable_vblank = sunxi_drm_enable_vblank, 112 .disable_vblank = sunxi_drm_disable_vblank, 113 114 .name = DRIVER_NAME, 115 .desc = DRIVER_DESC, 116 .date = DRIVER_DATE, 117 .major = DRIVER_MAJOR, 118 .minor = DRIVER_MINOR, 119 .patchlevel = DRIVER_PATCHLEVEL, 120 }; 121 122 CFATTACH_DECL_NEW(sunxi_drm, sizeof(struct sunxi_drm_softc), 123 sunxi_drm_match, sunxi_drm_attach, NULL, NULL); 124 125 static int 126 sunxi_drm_match(device_t parent, cfdata_t cf, void *aux) 127 { 128 struct fdt_attach_args * const faa = aux; 129 130 return of_compatible_match(faa->faa_phandle, compat_data); 131 } 132 133 static void 134 sunxi_drm_attach(device_t parent, device_t self, void *aux) 135 { 136 struct sunxi_drm_softc * const sc = device_private(self); 137 struct fdt_attach_args * const faa = aux; 138 struct drm_driver * const driver = &sunxi_drm_driver; 139 prop_dictionary_t dict = device_properties(self); 140 bool is_disabled; 141 142 aprint_naive("\n"); 143 144 if (prop_dictionary_get_bool(dict, "disabled", &is_disabled) && 145 is_disabled) { 146 aprint_normal(": Display Engine Pipeline (disabled)\n"); 147 return; 148 } 149 150 aprint_normal(": Display Engine Pipeline\n"); 151 152 #ifdef WSDISPLAY_MULTICONS 153 const bool is_console = true; 154 prop_dictionary_set_bool(dict, "is_console", is_console); 155 #endif 156 157 sc->sc_dev = self; 158 sc->sc_dmat = faa->faa_dmat; 159 sc->sc_bst = faa->faa_bst; 160 sc->sc_phandle = faa->faa_phandle; 161 sc->sc_task_thread = NULL; 162 SIMPLEQ_INIT(&sc->sc_tasks); 163 if (workqueue_create(&sc->sc_task_wq, "sunxidrm", 164 &sunxi_drm_task_work, NULL, PRI_NONE, IPL_NONE, WQ_MPSAFE)) { 165 aprint_error_dev(self, "unable to create workqueue\n"); 166 sc->sc_task_wq = NULL; 167 return; 168 } 169 170 sc->sc_ddev = drm_dev_alloc(driver, sc->sc_dev); 171 if (IS_ERR(sc->sc_ddev)) { 172 aprint_error_dev(self, "couldn't allocate DRM device\n"); 173 return; 174 } 175 sc->sc_ddev->dev_private = sc; 176 sc->sc_ddev->bst = sc->sc_bst; 177 sc->sc_ddev->bus_dmat = sc->sc_dmat; 178 sc->sc_ddev->dmat = sc->sc_ddev->bus_dmat; 179 sc->sc_ddev->dmat_subregion_p = false; 180 181 fdt_remove_bycompat(fb_compatible); 182 183 config_defer(self, sunxi_drm_init); 184 } 185 186 static void 187 sunxi_drm_init(device_t dev) 188 { 189 struct sunxi_drm_softc * const sc = device_private(dev); 190 struct drm_driver * const driver = &sunxi_drm_driver; 191 int error; 192 193 /* 194 * Cause any tasks issued synchronously during attach to be 195 * processed at the end of this function. 196 */ 197 sc->sc_task_thread = curlwp; 198 199 error = -drm_dev_register(sc->sc_ddev, 0); 200 if (error) { 201 aprint_error_dev(dev, "couldn't register DRM device: %d\n", 202 error); 203 goto out; 204 } 205 sc->sc_dev_registered = true; 206 207 aprint_normal_dev(dev, "initialized %s %d.%d.%d %s on minor %d\n", 208 driver->name, driver->major, driver->minor, driver->patchlevel, 209 driver->date, sc->sc_ddev->primary->index); 210 211 /* 212 * Process asynchronous tasks queued synchronously during 213 * attach. This will be for display detection to attach a 214 * framebuffer, so we have the opportunity for a console device 215 * to attach before autoconf has completed, in time for init(8) 216 * to find that console without panicking. 217 */ 218 while (!SIMPLEQ_EMPTY(&sc->sc_tasks)) { 219 struct sunxi_drm_task *const task = 220 SIMPLEQ_FIRST(&sc->sc_tasks); 221 222 SIMPLEQ_REMOVE_HEAD(&sc->sc_tasks, sdt_u.queue); 223 (*task->sdt_fn)(task); 224 } 225 226 out: /* Cause any subsequent tasks to be processed by the workqueue. */ 227 atomic_store_relaxed(&sc->sc_task_thread, NULL); 228 } 229 230 static vmem_t * 231 sunxi_drm_alloc_cma_pool(struct drm_device *ddev, size_t cma_size) 232 { 233 struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); 234 bus_dma_segment_t segs[1]; 235 int nsegs; 236 int error; 237 238 error = bus_dmamem_alloc(sc->sc_dmat, cma_size, PAGE_SIZE, 0, 239 segs, 1, &nsegs, BUS_DMA_NOWAIT); 240 if (error) { 241 aprint_error_dev(sc->sc_dev, "couldn't allocate CMA pool\n"); 242 return NULL; 243 } 244 245 return vmem_create("sunxidrm", segs[0].ds_addr, segs[0].ds_len, 246 PAGE_SIZE, NULL, NULL, NULL, 0, VM_SLEEP, IPL_NONE); 247 } 248 249 static int 250 sunxi_drm_fb_create_handle(struct drm_framebuffer *fb, 251 struct drm_file *file, unsigned int *handle) 252 { 253 struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb); 254 255 return drm_gem_handle_create(file, &sfb->obj->base, handle); 256 } 257 258 static void 259 sunxi_drm_fb_destroy(struct drm_framebuffer *fb) 260 { 261 struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(fb); 262 263 drm_framebuffer_cleanup(fb); 264 drm_gem_object_put_unlocked(&sfb->obj->base); 265 kmem_free(sfb, sizeof(*sfb)); 266 } 267 268 static const struct drm_framebuffer_funcs sunxi_drm_framebuffer_funcs = { 269 .create_handle = sunxi_drm_fb_create_handle, 270 .destroy = sunxi_drm_fb_destroy, 271 }; 272 273 static struct drm_framebuffer * 274 sunxi_drm_fb_create(struct drm_device *ddev, struct drm_file *file, 275 const struct drm_mode_fb_cmd2 *cmd) 276 { 277 struct sunxi_drm_framebuffer *fb; 278 struct drm_gem_object *gem_obj; 279 int error; 280 281 if (cmd->flags) 282 return NULL; 283 284 gem_obj = drm_gem_object_lookup(file, cmd->handles[0]); 285 if (gem_obj == NULL) 286 return NULL; 287 288 fb = kmem_zalloc(sizeof(*fb), KM_SLEEP); 289 fb->obj = to_drm_gem_cma_obj(gem_obj); 290 drm_helper_mode_fill_fb_struct(ddev, &fb->base, cmd); 291 292 error = drm_framebuffer_init(ddev, &fb->base, &sunxi_drm_framebuffer_funcs); 293 if (error != 0) 294 goto dealloc; 295 296 return &fb->base; 297 298 dealloc: 299 drm_framebuffer_cleanup(&fb->base); 300 kmem_free(fb, sizeof(*fb)); 301 drm_gem_object_put_unlocked(gem_obj); 302 303 return NULL; 304 } 305 306 static struct drm_mode_config_funcs sunxi_drm_mode_config_funcs = { 307 .fb_create = sunxi_drm_fb_create, 308 }; 309 310 static int 311 sunxi_drm_simplefb_lookup(bus_addr_t *paddr, bus_size_t *psize) 312 { 313 static const struct device_compatible_entry simplefb_compat[] = { 314 { .compat = "simple-framebuffer" }, 315 DEVICE_COMPAT_EOL 316 }; 317 int chosen, child, error; 318 bus_addr_t addr_end; 319 320 chosen = OF_finddevice("/chosen"); 321 if (chosen == -1) 322 return ENOENT; 323 324 for (child = OF_child(chosen); child; child = OF_peer(child)) { 325 if (!fdtbus_status_okay(child)) 326 continue; 327 if (!of_compatible_match(child, simplefb_compat)) 328 continue; 329 error = fdtbus_get_reg(child, 0, paddr, psize); 330 if (error != 0) 331 return error; 332 333 /* Reclaim entire pages used by the simplefb */ 334 addr_end = *paddr + *psize; 335 *paddr = SUNXI_TRUNC_PAGE(*paddr); 336 *psize = SUNXI_ROUND_PAGE(addr_end - *paddr); 337 return 0; 338 } 339 340 return ENOENT; 341 } 342 343 static int 344 sunxi_drm_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) 345 { 346 struct sunxi_drm_softc * const sc = sunxi_drm_private(helper->dev); 347 struct drm_device *ddev = helper->dev; 348 struct sunxi_drm_framebuffer *sfb = to_sunxi_drm_framebuffer(helper->fb); 349 struct drm_framebuffer *fb = helper->fb; 350 struct sunxi_drmfb_attach_args sfa; 351 bus_addr_t sfb_addr; 352 bus_size_t sfb_size; 353 size_t cma_size; 354 int error; 355 356 const u_int width = sizes->surface_width; 357 const u_int height = sizes->surface_height; 358 const u_int pitch = width * (32 / 8); 359 360 const size_t size = roundup(height * pitch, PAGE_SIZE); 361 362 if (sunxi_drm_simplefb_lookup(&sfb_addr, &sfb_size) != 0) 363 sfb_size = 0; 364 365 /* Reserve enough memory for a 4K plane, rounded to 1MB */ 366 cma_size = (SUNXI_DRM_MAX_WIDTH * SUNXI_DRM_MAX_HEIGHT * 4); 367 if (sfb_size == 0) { 368 /* Add memory for FB console if we cannot reclaim bootloader memory */ 369 cma_size += size; 370 } 371 cma_size = roundup(cma_size, 1024 * 1024); 372 sc->sc_ddev->cma_pool = sunxi_drm_alloc_cma_pool(sc->sc_ddev, cma_size); 373 if (sc->sc_ddev->cma_pool != NULL) { 374 if (sfb_size != 0) { 375 error = vmem_add(sc->sc_ddev->cma_pool, sfb_addr, 376 sfb_size, VM_SLEEP); 377 if (error != 0) 378 sfb_size = 0; 379 } 380 aprint_normal_dev(sc->sc_dev, "reserved %u MB DRAM for CMA", 381 (u_int)((cma_size + sfb_size) / (1024 * 1024))); 382 if (sfb_size != 0) 383 aprint_normal(" (%u MB reclaimed from bootloader)", 384 (u_int)(sfb_size / (1024 * 1024))); 385 aprint_normal("\n"); 386 } 387 388 sfb->obj = drm_gem_cma_create(ddev, size); 389 if (sfb->obj == NULL) { 390 DRM_ERROR("failed to allocate memory for framebuffer\n"); 391 return -ENOMEM; 392 } 393 394 fb->pitches[0] = pitch; 395 fb->offsets[0] = 0; 396 fb->width = width; 397 fb->height = height; 398 fb->format = drm_format_info(DRM_FORMAT_XRGB8888); 399 fb->dev = ddev; 400 401 error = drm_framebuffer_init(ddev, fb, &sunxi_drm_framebuffer_funcs); 402 if (error != 0) { 403 DRM_ERROR("failed to initialize framebuffer\n"); 404 return error; 405 } 406 407 memset(&sfa, 0, sizeof(sfa)); 408 sfa.sfa_drm_dev = ddev; 409 sfa.sfa_fb_helper = helper; 410 sfa.sfa_fb_sizes = *sizes; 411 sfa.sfa_fb_bst = sc->sc_bst; 412 sfa.sfa_fb_dmat = sc->sc_dmat; 413 sfa.sfa_fb_linebytes = helper->fb->pitches[0]; 414 415 helper->fbdev = config_found(ddev->dev, &sfa, NULL, 416 CFARGS(.iattr = "sunxifbbus")); 417 if (helper->fbdev == NULL) { 418 DRM_ERROR("unable to attach framebuffer\n"); 419 return -ENXIO; 420 } 421 422 return 0; 423 } 424 425 static struct drm_fb_helper_funcs sunxi_drm_fb_helper_funcs = { 426 .fb_probe = sunxi_drm_fb_probe, 427 }; 428 429 static int 430 sunxi_drm_load(struct drm_device *ddev, unsigned long flags) 431 { 432 struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); 433 struct sunxi_drm_endpoint *sep; 434 struct sunxi_drm_fbdev *fbdev; 435 const u_int *data; 436 int datalen, error, num_crtc; 437 438 drm_mode_config_init(ddev); 439 ddev->mode_config.min_width = 0; 440 ddev->mode_config.min_height = 0; 441 ddev->mode_config.max_width = SUNXI_DRM_MAX_WIDTH; 442 ddev->mode_config.max_height = SUNXI_DRM_MAX_HEIGHT; 443 ddev->mode_config.funcs = &sunxi_drm_mode_config_funcs; 444 445 num_crtc = 0; 446 data = fdtbus_get_prop(sc->sc_phandle, "allwinner,pipelines", &datalen); 447 while (datalen >= 4) { 448 const int crtc_phandle = fdtbus_get_phandle_from_native(be32dec(data)); 449 450 TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries) 451 if (sep->phandle == crtc_phandle && sep->ddev == NULL) { 452 sep->ddev = ddev; 453 error = fdt_endpoint_activate_direct(sep->ep, true); 454 if (error != 0) { 455 aprint_error_dev(sc->sc_dev, "failed to activate endpoint: %d\n", 456 error); 457 } 458 if (fdt_endpoint_type(sep->ep) == EP_DRM_CRTC) 459 num_crtc++; 460 } 461 462 datalen -= 4; 463 data++; 464 } 465 466 if (num_crtc == 0) { 467 aprint_error_dev(sc->sc_dev, "no pipelines configured\n"); 468 error = ENXIO; 469 goto drmerr; 470 } 471 472 fbdev = kmem_zalloc(sizeof(*fbdev), KM_SLEEP); 473 474 drm_fb_helper_prepare(ddev, &fbdev->helper, &sunxi_drm_fb_helper_funcs); 475 476 error = drm_fb_helper_init(ddev, &fbdev->helper, num_crtc); 477 if (error) 478 goto allocerr; 479 480 fbdev->helper.fb = kmem_zalloc(sizeof(struct sunxi_drm_framebuffer), KM_SLEEP); 481 482 drm_fb_helper_single_add_all_connectors(&fbdev->helper); 483 484 drm_helper_disable_unused_functions(ddev); 485 486 drm_fb_helper_initial_config(&fbdev->helper, 32); 487 488 /* XXX */ 489 ddev->irq_enabled = true; 490 drm_vblank_init(ddev, num_crtc); 491 492 return 0; 493 494 allocerr: 495 kmem_free(fbdev, sizeof(*fbdev)); 496 drmerr: 497 drm_mode_config_cleanup(ddev); 498 499 return error; 500 } 501 502 static uint32_t 503 sunxi_drm_get_vblank_counter(struct drm_device *ddev, unsigned int crtc) 504 { 505 struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); 506 507 if (crtc >= __arraycount(sc->sc_vbl)) 508 return 0; 509 510 if (sc->sc_vbl[crtc].get_vblank_counter == NULL) 511 return 0; 512 513 return sc->sc_vbl[crtc].get_vblank_counter(sc->sc_vbl[crtc].priv); 514 } 515 516 static int 517 sunxi_drm_enable_vblank(struct drm_device *ddev, unsigned int crtc) 518 { 519 struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); 520 521 if (crtc >= __arraycount(sc->sc_vbl)) 522 return 0; 523 524 if (sc->sc_vbl[crtc].enable_vblank == NULL) 525 return 0; 526 527 sc->sc_vbl[crtc].enable_vblank(sc->sc_vbl[crtc].priv); 528 529 return 0; 530 } 531 532 static void 533 sunxi_drm_disable_vblank(struct drm_device *ddev, unsigned int crtc) 534 { 535 struct sunxi_drm_softc * const sc = sunxi_drm_private(ddev); 536 537 if (crtc >= __arraycount(sc->sc_vbl)) 538 return; 539 540 if (sc->sc_vbl[crtc].disable_vblank == NULL) 541 return; 542 543 sc->sc_vbl[crtc].disable_vblank(sc->sc_vbl[crtc].priv); 544 } 545 546 static void 547 sunxi_drm_unload(struct drm_device *ddev) 548 { 549 drm_mode_config_cleanup(ddev); 550 } 551 552 int 553 sunxi_drm_register_endpoint(int phandle, struct fdt_endpoint *ep) 554 { 555 struct sunxi_drm_endpoint *sep; 556 557 sep = kmem_zalloc(sizeof(*sep), KM_SLEEP); 558 sep->phandle = phandle; 559 sep->ep = ep; 560 sep->ddev = NULL; 561 TAILQ_INSERT_TAIL(&sunxi_drm_endpoints, sep, entries); 562 563 return 0; 564 } 565 566 struct drm_device * 567 sunxi_drm_endpoint_device(struct fdt_endpoint *ep) 568 { 569 struct sunxi_drm_endpoint *sep; 570 571 TAILQ_FOREACH(sep, &sunxi_drm_endpoints, entries) 572 if (sep->ep == ep) 573 return sep->ddev; 574 575 return NULL; 576 } 577 578 static void 579 sunxi_drm_task_work(struct work *work, void *cookie) 580 { 581 struct sunxi_drm_task *task = container_of(work, struct sunxi_drm_task, 582 sdt_u.work); 583 584 (*task->sdt_fn)(task); 585 } 586 587 void 588 sunxi_task_init(struct sunxi_drm_task *task, 589 void (*fn)(struct sunxi_drm_task *)) 590 { 591 592 task->sdt_fn = fn; 593 } 594 595 void 596 sunxi_task_schedule(device_t self, struct sunxi_drm_task *task) 597 { 598 struct sunxi_drm_softc *sc = device_private(self); 599 600 if (atomic_load_relaxed(&sc->sc_task_thread) == curlwp) 601 SIMPLEQ_INSERT_TAIL(&sc->sc_tasks, task, sdt_u.queue); 602 else 603 workqueue_enqueue(sc->sc_task_wq, &task->sdt_u.work, NULL); 604 } 605