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