b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | From 4dcb742bde0e0d11386035d9069ca23e6abc28af Mon Sep 17 00:00:00 2001 |
| 2 | From: James Hughes <james.hughes@raspberrypi.org> |
| 3 | Date: Thu, 14 Mar 2019 13:27:54 +0000 |
| 4 | Subject: [PATCH] Pulled in the multi frame buffer support from the Pi3 |
| 5 | repo |
| 6 | |
| 7 | --- |
| 8 | drivers/video/fbdev/bcm2708_fb.c | 467 +++++++++++++++------ |
| 9 | include/soc/bcm2835/raspberrypi-firmware.h | 13 + |
| 10 | 2 files changed, 343 insertions(+), 137 deletions(-) |
| 11 | |
| 12 | --- a/drivers/video/fbdev/bcm2708_fb.c |
| 13 | +++ b/drivers/video/fbdev/bcm2708_fb.c |
| 14 | @@ -2,6 +2,7 @@ |
| 15 | * linux/drivers/video/bcm2708_fb.c |
| 16 | * |
| 17 | * Copyright (C) 2010 Broadcom |
| 18 | + * Copyright (C) 2018 Raspberry Pi (Trading) Ltd |
| 19 | * |
| 20 | * This file is subject to the terms and conditions of the GNU General Public |
| 21 | * License. See the file COPYING in the main directory of this archive |
| 22 | @@ -13,6 +14,7 @@ |
| 23 | * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com> |
| 24 | * |
| 25 | */ |
| 26 | + |
| 27 | #include <linux/module.h> |
| 28 | #include <linux/kernel.h> |
| 29 | #include <linux/errno.h> |
| 30 | @@ -33,6 +35,7 @@ |
| 31 | #include <linux/io.h> |
| 32 | #include <linux/dma-mapping.h> |
| 33 | #include <soc/bcm2835/raspberrypi-firmware.h> |
| 34 | +#include <linux/mutex.h> |
| 35 | |
| 36 | //#define BCM2708_FB_DEBUG |
| 37 | #define MODULE_NAME "bcm2708_fb" |
| 38 | @@ -79,65 +82,139 @@ struct bcm2708_fb_stats { |
| 39 | u32 dma_irqs; |
| 40 | }; |
| 41 | |
| 42 | +struct vc4_display_settings_t { |
| 43 | + u32 display_num; |
| 44 | + u32 width; |
| 45 | + u32 height; |
| 46 | + u32 depth; |
| 47 | + u32 pitch; |
| 48 | + u32 virtual_width; |
| 49 | + u32 virtual_height; |
| 50 | + u32 virtual_width_offset; |
| 51 | + u32 virtual_height_offset; |
| 52 | + unsigned long fb_bus_address; |
| 53 | +}; |
| 54 | + |
| 55 | +struct bcm2708_fb_dev; |
| 56 | + |
| 57 | struct bcm2708_fb { |
| 58 | struct fb_info fb; |
| 59 | struct platform_device *dev; |
| 60 | - struct rpi_firmware *fw; |
| 61 | u32 cmap[16]; |
| 62 | u32 gpu_cmap[256]; |
| 63 | - int dma_chan; |
| 64 | - int dma_irq; |
| 65 | - void __iomem *dma_chan_base; |
| 66 | - void *cb_base; /* DMA control blocks */ |
| 67 | - dma_addr_t cb_handle; |
| 68 | struct dentry *debugfs_dir; |
| 69 | - wait_queue_head_t dma_waitq; |
| 70 | - struct bcm2708_fb_stats stats; |
| 71 | + struct dentry *debugfs_subdir; |
| 72 | unsigned long fb_bus_address; |
| 73 | - bool disable_arm_alloc; |
| 74 | + struct { u32 base, length; } gpu; |
| 75 | + struct vc4_display_settings_t display_settings; |
| 76 | + struct debugfs_regset32 screeninfo_regset; |
| 77 | + struct bcm2708_fb_dev *fbdev; |
| 78 | unsigned int image_size; |
| 79 | dma_addr_t dma_addr; |
| 80 | void *cpuaddr; |
| 81 | }; |
| 82 | |
| 83 | +#define MAX_FRAMEBUFFERS 3 |
| 84 | + |
| 85 | +struct bcm2708_fb_dev { |
| 86 | + int firmware_supports_multifb; |
| 87 | + /* Protects the DMA system from multiple FB access */ |
| 88 | + struct mutex dma_mutex; |
| 89 | + int dma_chan; |
| 90 | + int dma_irq; |
| 91 | + void __iomem *dma_chan_base; |
| 92 | + wait_queue_head_t dma_waitq; |
| 93 | + bool disable_arm_alloc; |
| 94 | + struct bcm2708_fb_stats dma_stats; |
| 95 | + void *cb_base; /* DMA control blocks */ |
| 96 | + dma_addr_t cb_handle; |
| 97 | + int instance_count; |
| 98 | + int num_displays; |
| 99 | + struct rpi_firmware *fw; |
| 100 | + struct bcm2708_fb displays[MAX_FRAMEBUFFERS]; |
| 101 | +}; |
| 102 | + |
| 103 | #define to_bcm2708(info) container_of(info, struct bcm2708_fb, fb) |
| 104 | |
| 105 | static void bcm2708_fb_debugfs_deinit(struct bcm2708_fb *fb) |
| 106 | { |
| 107 | - debugfs_remove_recursive(fb->debugfs_dir); |
| 108 | - fb->debugfs_dir = NULL; |
| 109 | + debugfs_remove_recursive(fb->debugfs_subdir); |
| 110 | + fb->debugfs_subdir = NULL; |
| 111 | + |
| 112 | + fb->fbdev->instance_count--; |
| 113 | + |
| 114 | + if (!fb->fbdev->instance_count) { |
| 115 | + debugfs_remove_recursive(fb->debugfs_dir); |
| 116 | + fb->debugfs_dir = NULL; |
| 117 | + } |
| 118 | } |
| 119 | |
| 120 | static int bcm2708_fb_debugfs_init(struct bcm2708_fb *fb) |
| 121 | { |
| 122 | + char buf[3]; |
| 123 | + struct bcm2708_fb_dev *fbdev = fb->fbdev; |
| 124 | + |
| 125 | static struct debugfs_reg32 stats_registers[] = { |
| 126 | - { |
| 127 | - "dma_copies", |
| 128 | - offsetof(struct bcm2708_fb_stats, dma_copies) |
| 129 | - }, |
| 130 | - { |
| 131 | - "dma_irqs", |
| 132 | - offsetof(struct bcm2708_fb_stats, dma_irqs) |
| 133 | - }, |
| 134 | + {"dma_copies", offsetof(struct bcm2708_fb_stats, dma_copies)}, |
| 135 | + {"dma_irqs", offsetof(struct bcm2708_fb_stats, dma_irqs)}, |
| 136 | + }; |
| 137 | + |
| 138 | + static struct debugfs_reg32 screeninfo[] = { |
| 139 | + {"width", offsetof(struct fb_var_screeninfo, xres)}, |
| 140 | + {"height", offsetof(struct fb_var_screeninfo, yres)}, |
| 141 | + {"bpp", offsetof(struct fb_var_screeninfo, bits_per_pixel)}, |
| 142 | + {"xres_virtual", offsetof(struct fb_var_screeninfo, xres_virtual)}, |
| 143 | + {"yres_virtual", offsetof(struct fb_var_screeninfo, yres_virtual)}, |
| 144 | + {"xoffset", offsetof(struct fb_var_screeninfo, xoffset)}, |
| 145 | + {"yoffset", offsetof(struct fb_var_screeninfo, yoffset)}, |
| 146 | }; |
| 147 | |
| 148 | - fb->debugfs_dir = debugfs_create_dir(DRIVER_NAME, NULL); |
| 149 | + fb->debugfs_dir = debugfs_lookup(DRIVER_NAME, NULL); |
| 150 | + |
| 151 | + if (!fb->debugfs_dir) |
| 152 | + fb->debugfs_dir = debugfs_create_dir(DRIVER_NAME, NULL); |
| 153 | + |
| 154 | if (!fb->debugfs_dir) { |
| 155 | - pr_warn("%s: could not create debugfs entry\n", |
| 156 | - __func__); |
| 157 | + dev_warn(fb->fb.dev, "%s: could not create debugfs folder\n", |
| 158 | + __func__); |
| 159 | return -EFAULT; |
| 160 | } |
| 161 | |
| 162 | - fb->stats.regset.regs = stats_registers; |
| 163 | - fb->stats.regset.nregs = ARRAY_SIZE(stats_registers); |
| 164 | - fb->stats.regset.base = &fb->stats; |
| 165 | - |
| 166 | - if (!debugfs_create_regset32("stats", 0444, fb->debugfs_dir, |
| 167 | - &fb->stats.regset)) { |
| 168 | - pr_warn("%s: could not create statistics registers\n", |
| 169 | - __func__); |
| 170 | + snprintf(buf, sizeof(buf), "%u", fb->display_settings.display_num); |
| 171 | + |
| 172 | + fb->debugfs_subdir = debugfs_create_dir(buf, fb->debugfs_dir); |
| 173 | + |
| 174 | + if (!fb->debugfs_subdir) { |
| 175 | + dev_warn(fb->fb.dev, "%s: could not create debugfs entry %u\n", |
| 176 | + __func__, fb->display_settings.display_num); |
| 177 | + return -EFAULT; |
| 178 | + } |
| 179 | + |
| 180 | + fbdev->dma_stats.regset.regs = stats_registers; |
| 181 | + fbdev->dma_stats.regset.nregs = ARRAY_SIZE(stats_registers); |
| 182 | + fbdev->dma_stats.regset.base = &fbdev->dma_stats; |
| 183 | + |
| 184 | + if (!debugfs_create_regset32("dma_stats", 0444, fb->debugfs_subdir, |
| 185 | + &fbdev->dma_stats.regset)) { |
| 186 | + dev_warn(fb->fb.dev, "%s: could not create statistics registers\n", |
| 187 | + __func__); |
| 188 | + goto fail; |
| 189 | + } |
| 190 | + |
| 191 | + fb->screeninfo_regset.regs = screeninfo; |
| 192 | + fb->screeninfo_regset.nregs = ARRAY_SIZE(screeninfo); |
| 193 | + fb->screeninfo_regset.base = &fb->fb.var; |
| 194 | + |
| 195 | + if (!debugfs_create_regset32("screeninfo", 0444, fb->debugfs_subdir, |
| 196 | + &fb->screeninfo_regset)) { |
| 197 | + dev_warn(fb->fb.dev, |
| 198 | + "%s: could not create dimensions registers\n", |
| 199 | + __func__); |
| 200 | goto fail; |
| 201 | } |
| 202 | + |
| 203 | + fbdev->instance_count++; |
| 204 | + |
| 205 | return 0; |
| 206 | |
| 207 | fail: |
| 208 | @@ -145,6 +222,20 @@ fail: |
| 209 | return -EFAULT; |
| 210 | } |
| 211 | |
| 212 | +static void set_display_num(struct bcm2708_fb *fb) |
| 213 | +{ |
| 214 | + if (fb && fb->fbdev && fb->fbdev->firmware_supports_multifb) { |
| 215 | + u32 tmp = fb->display_settings.display_num; |
| 216 | + |
| 217 | + if (rpi_firmware_property(fb->fbdev->fw, |
| 218 | + RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, |
| 219 | + &tmp, |
| 220 | + sizeof(tmp))) |
| 221 | + dev_warn_once(fb->fb.dev, |
| 222 | + "Set display number call failed. Old GPU firmware?"); |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | static int bcm2708_fb_set_bitfields(struct fb_var_screeninfo *var) |
| 227 | { |
| 228 | int ret = 0; |
| 229 | @@ -222,11 +313,11 @@ static int bcm2708_fb_check_var(struct f |
| 230 | struct fb_info *info) |
| 231 | { |
| 232 | /* info input, var output */ |
| 233 | - print_debug("%s(%p) %dx%d (%dx%d), %d, %d\n", |
| 234 | + print_debug("%s(%p) %ux%u (%ux%u), %ul, %u\n", |
| 235 | __func__, info, info->var.xres, info->var.yres, |
| 236 | info->var.xres_virtual, info->var.yres_virtual, |
| 237 | - (int)info->screen_size, info->var.bits_per_pixel); |
| 238 | - print_debug("%s(%p) %dx%d (%dx%d), %d\n", __func__, var, var->xres, |
| 239 | + info->screen_size, info->var.bits_per_pixel); |
| 240 | + print_debug("%s(%p) %ux%u (%ux%u), %u\n", __func__, var, var->xres, |
| 241 | var->yres, var->xres_virtual, var->yres_virtual, |
| 242 | var->bits_per_pixel); |
| 243 | |
| 244 | @@ -289,17 +380,24 @@ static int bcm2708_fb_set_par(struct fb_ |
| 245 | }; |
| 246 | int ret, image_size; |
| 247 | |
| 248 | - |
| 249 | - print_debug("%s(%p) %dx%d (%dx%d), %d, %d\n", __func__, info, |
| 250 | + print_debug("%s(%p) %dx%d (%dx%d), %d, %d (display %d)\n", __func__, |
| 251 | + info, |
| 252 | info->var.xres, info->var.yres, info->var.xres_virtual, |
| 253 | info->var.yres_virtual, (int)info->screen_size, |
| 254 | - info->var.bits_per_pixel); |
| 255 | + info->var.bits_per_pixel, value); |
| 256 | + |
| 257 | + /* Need to set the display number to act on first |
| 258 | + * Cannot do it in the tag list because on older firmware the call |
| 259 | + * will fail and stop the rest of the list being executed. |
| 260 | + * We can ignore this call failing as the default at other end is 0 |
| 261 | + */ |
| 262 | + set_display_num(fb); |
| 263 | |
| 264 | /* Try allocating our own buffer. We can specify all the parameters */ |
| 265 | image_size = ((info->var.xres * info->var.yres) * |
| 266 | info->var.bits_per_pixel) >> 3; |
| 267 | |
| 268 | - if (!fb->disable_arm_alloc && |
| 269 | + if (!fb->fbdev->disable_arm_alloc && |
| 270 | (image_size != fb->image_size || !fb->dma_addr)) { |
| 271 | if (fb->dma_addr) { |
| 272 | dma_free_coherent(info->device, fb->image_size, |
| 273 | @@ -314,7 +412,7 @@ static int bcm2708_fb_set_par(struct fb_ |
| 274 | |
| 275 | if (!fb->cpuaddr) { |
| 276 | fb->dma_addr = 0; |
| 277 | - fb->disable_arm_alloc = true; |
| 278 | + fb->fbdev->disable_arm_alloc = true; |
| 279 | } else { |
| 280 | fb->image_size = image_size; |
| 281 | } |
| 282 | @@ -325,7 +423,7 @@ static int bcm2708_fb_set_par(struct fb_ |
| 283 | fbinfo.screen_size = image_size; |
| 284 | fbinfo.pitch = (info->var.xres * info->var.bits_per_pixel) >> 3; |
| 285 | |
| 286 | - ret = rpi_firmware_property_list(fb->fw, &fbinfo, |
| 287 | + ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo, |
| 288 | sizeof(fbinfo)); |
| 289 | if (ret || fbinfo.base != fb->dma_addr) { |
| 290 | /* Firmware either failed, or assigned a different base |
| 291 | @@ -338,7 +436,7 @@ static int bcm2708_fb_set_par(struct fb_ |
| 292 | fb->image_size = 0; |
| 293 | fb->cpuaddr = NULL; |
| 294 | fb->dma_addr = 0; |
| 295 | - fb->disable_arm_alloc = true; |
| 296 | + fb->fbdev->disable_arm_alloc = true; |
| 297 | } |
| 298 | } else { |
| 299 | /* Our allocation failed - drop into the old scheme of |
| 300 | @@ -357,7 +455,7 @@ static int bcm2708_fb_set_par(struct fb_ |
| 301 | fbinfo.tag6.tag = RPI_FIRMWARE_FRAMEBUFFER_GET_PITCH; |
| 302 | fbinfo.pitch = 0; |
| 303 | |
| 304 | - ret = rpi_firmware_property_list(fb->fw, &fbinfo, |
| 305 | + ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo, |
| 306 | sizeof(fbinfo)); |
| 307 | if (ret) { |
| 308 | dev_err(info->device, |
| 309 | @@ -447,7 +545,10 @@ static int bcm2708_fb_setcolreg(unsigned |
| 310 | packet->length = regno + 1; |
| 311 | memcpy(packet->cmap, fb->gpu_cmap, |
| 312 | sizeof(packet->cmap)); |
| 313 | - ret = rpi_firmware_property(fb->fw, |
| 314 | + |
| 315 | + set_display_num(fb); |
| 316 | + |
| 317 | + ret = rpi_firmware_property(fb->fbdev->fw, |
| 318 | RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE, |
| 319 | packet, |
| 320 | (2 + packet->length) * sizeof(u32)); |
| 321 | @@ -486,8 +587,11 @@ static int bcm2708_fb_blank(int blank_mo |
| 322 | return -EINVAL; |
| 323 | } |
| 324 | |
| 325 | - ret = rpi_firmware_property(fb->fw, RPI_FIRMWARE_FRAMEBUFFER_BLANK, |
| 326 | + set_display_num(fb); |
| 327 | + |
| 328 | + ret = rpi_firmware_property(fb->fbdev->fw, RPI_FIRMWARE_FRAMEBUFFER_BLANK, |
| 329 | &value, sizeof(value)); |
| 330 | + |
| 331 | if (ret) |
| 332 | dev_err(info->device, "%s(%d) failed: %d\n", __func__, |
| 333 | blank_mode, ret); |
| 334 | @@ -504,12 +608,14 @@ static int bcm2708_fb_pan_display(struct |
| 335 | info->var.yoffset = var->yoffset; |
| 336 | result = bcm2708_fb_set_par(info); |
| 337 | if (result != 0) |
| 338 | - pr_err("%s(%d,%d) returns=%d\n", __func__, var->xoffset, |
| 339 | + pr_err("%s(%u,%u) returns=%d\n", __func__, var->xoffset, |
| 340 | var->yoffset, result); |
| 341 | return result; |
| 342 | } |
| 343 | |
| 344 | static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) |
| 345 | +static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd, |
| 346 | + unsigned long arg) |
| 347 | { |
| 348 | struct bcm2708_fb *fb = to_bcm2708(info); |
| 349 | u32 dummy = 0; |
| 350 | @@ -517,7 +623,9 @@ static int bcm2708_ioctl(struct fb_info |
| 351 | |
| 352 | switch (cmd) { |
| 353 | case FBIO_WAITFORVSYNC: |
| 354 | - ret = rpi_firmware_property(fb->fw, |
| 355 | + set_display_num(fb); |
| 356 | + |
| 357 | + ret = rpi_firmware_property(fb->fbdev->fw, |
| 358 | RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC, |
| 359 | &dummy, sizeof(dummy)); |
| 360 | break; |
| 361 | @@ -534,23 +642,22 @@ static int bcm2708_ioctl(struct fb_info |
| 362 | static void bcm2708_fb_fillrect(struct fb_info *info, |
| 363 | const struct fb_fillrect *rect) |
| 364 | { |
| 365 | - /* (is called) print_debug("bcm2708_fb_fillrect\n"); */ |
| 366 | cfb_fillrect(info, rect); |
| 367 | } |
| 368 | |
| 369 | /* A helper function for configuring dma control block */ |
| 370 | static void set_dma_cb(struct bcm2708_dma_cb *cb, |
| 371 | - int burst_size, |
| 372 | - dma_addr_t dst, |
| 373 | - int dst_stride, |
| 374 | - dma_addr_t src, |
| 375 | - int src_stride, |
| 376 | - int w, |
| 377 | - int h) |
| 378 | + int burst_size, |
| 379 | + dma_addr_t dst, |
| 380 | + int dst_stride, |
| 381 | + dma_addr_t src, |
| 382 | + int src_stride, |
| 383 | + int w, |
| 384 | + int h) |
| 385 | { |
| 386 | cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | |
| 387 | - BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | |
| 388 | - BCM2708_DMA_D_INC | BCM2708_DMA_TDMODE; |
| 389 | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | |
| 390 | + BCM2708_DMA_D_INC | BCM2708_DMA_TDMODE; |
| 391 | cb->dst = dst; |
| 392 | cb->src = src; |
| 393 | /* |
| 394 | @@ -568,15 +675,19 @@ static void bcm2708_fb_copyarea(struct f |
| 395 | const struct fb_copyarea *region) |
| 396 | { |
| 397 | struct bcm2708_fb *fb = to_bcm2708(info); |
| 398 | - struct bcm2708_dma_cb *cb = fb->cb_base; |
| 399 | + struct bcm2708_fb_dev *fbdev = fb->fbdev; |
| 400 | + struct bcm2708_dma_cb *cb = fbdev->cb_base; |
| 401 | int bytes_per_pixel = (info->var.bits_per_pixel + 7) >> 3; |
| 402 | |
| 403 | /* Channel 0 supports larger bursts and is a bit faster */ |
| 404 | - int burst_size = (fb->dma_chan == 0) ? 8 : 2; |
| 405 | + int burst_size = (fbdev->dma_chan == 0) ? 8 : 2; |
| 406 | int pixels = region->width * region->height; |
| 407 | |
| 408 | - /* Fallback to cfb_copyarea() if we don't like something */ |
| 409 | - if (bytes_per_pixel > 4 || |
| 410 | + /* If DMA is currently in use (ie being used on another FB), then |
| 411 | + * rather than wait for it to finish, just use the cfb_copyarea |
| 412 | + */ |
| 413 | + if (!mutex_trylock(&fbdev->dma_mutex) || |
| 414 | + bytes_per_pixel > 4 || |
| 415 | info->var.xres * info->var.yres > 1920 * 1200 || |
| 416 | region->width <= 0 || region->width > info->var.xres || |
| 417 | region->height <= 0 || region->height > info->var.yres || |
| 418 | @@ -603,8 +714,8 @@ static void bcm2708_fb_copyarea(struct f |
| 419 | * 1920x1200 resolution at 32bpp pixel depth. |
| 420 | */ |
| 421 | int y; |
| 422 | - dma_addr_t control_block_pa = fb->cb_handle; |
| 423 | - dma_addr_t scratchbuf = fb->cb_handle + 16 * 1024; |
| 424 | + dma_addr_t control_block_pa = fbdev->cb_handle; |
| 425 | + dma_addr_t scratchbuf = fbdev->cb_handle + 16 * 1024; |
| 426 | int scanline_size = bytes_per_pixel * region->width; |
| 427 | int scanlines_per_cb = (64 * 1024 - 16 * 1024) / scanline_size; |
| 428 | |
| 429 | @@ -654,10 +765,10 @@ static void bcm2708_fb_copyarea(struct f |
| 430 | } |
| 431 | set_dma_cb(cb, burst_size, |
| 432 | fb->fb_bus_address + dy * fb->fb.fix.line_length + |
| 433 | - bytes_per_pixel * region->dx, |
| 434 | + bytes_per_pixel * region->dx, |
| 435 | stride, |
| 436 | fb->fb_bus_address + sy * fb->fb.fix.line_length + |
| 437 | - bytes_per_pixel * region->sx, |
| 438 | + bytes_per_pixel * region->sx, |
| 439 | stride, |
| 440 | region->width * bytes_per_pixel, |
| 441 | region->height); |
| 442 | @@ -667,32 +778,33 @@ static void bcm2708_fb_copyarea(struct f |
| 443 | cb->next = 0; |
| 444 | |
| 445 | if (pixels < dma_busy_wait_threshold) { |
| 446 | - bcm_dma_start(fb->dma_chan_base, fb->cb_handle); |
| 447 | - bcm_dma_wait_idle(fb->dma_chan_base); |
| 448 | + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); |
| 449 | + bcm_dma_wait_idle(fbdev->dma_chan_base); |
| 450 | } else { |
| 451 | - void __iomem *dma_chan = fb->dma_chan_base; |
| 452 | + void __iomem *local_dma_chan = fbdev->dma_chan_base; |
| 453 | |
| 454 | cb->info |= BCM2708_DMA_INT_EN; |
| 455 | - bcm_dma_start(fb->dma_chan_base, fb->cb_handle); |
| 456 | - while (bcm_dma_is_busy(dma_chan)) { |
| 457 | - wait_event_interruptible(fb->dma_waitq, |
| 458 | - !bcm_dma_is_busy(dma_chan)); |
| 459 | + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); |
| 460 | + while (bcm_dma_is_busy(local_dma_chan)) { |
| 461 | + wait_event_interruptible(fbdev->dma_waitq, |
| 462 | + !bcm_dma_is_busy(local_dma_chan)); |
| 463 | } |
| 464 | - fb->stats.dma_irqs++; |
| 465 | + fbdev->dma_stats.dma_irqs++; |
| 466 | } |
| 467 | - fb->stats.dma_copies++; |
| 468 | + fbdev->dma_stats.dma_copies++; |
| 469 | + |
| 470 | + mutex_unlock(&fbdev->dma_mutex); |
| 471 | } |
| 472 | |
| 473 | static void bcm2708_fb_imageblit(struct fb_info *info, |
| 474 | const struct fb_image *image) |
| 475 | { |
| 476 | - /* (is called) print_debug("bcm2708_fb_imageblit\n"); */ |
| 477 | cfb_imageblit(info, image); |
| 478 | } |
| 479 | |
| 480 | static irqreturn_t bcm2708_fb_dma_irq(int irq, void *cxt) |
| 481 | { |
| 482 | - struct bcm2708_fb *fb = cxt; |
| 483 | + struct bcm2708_fb_dev *fbdev = cxt; |
| 484 | |
| 485 | /* FIXME: should read status register to check if this is |
| 486 | * actually interrupting us or not, in case this interrupt |
| 487 | @@ -702,9 +814,9 @@ static irqreturn_t bcm2708_fb_dma_irq(in |
| 488 | */ |
| 489 | |
| 490 | /* acknowledge the interrupt */ |
| 491 | - writel(BCM2708_DMA_INT, fb->dma_chan_base + BCM2708_DMA_CS); |
| 492 | + writel(BCM2708_DMA_INT, fbdev->dma_chan_base + BCM2708_DMA_CS); |
| 493 | |
| 494 | - wake_up(&fb->dma_waitq); |
| 495 | + wake_up(&fbdev->dma_waitq); |
| 496 | return IRQ_HANDLED; |
| 497 | } |
| 498 | |
| 499 | @@ -737,11 +849,23 @@ static int bcm2708_fb_register(struct bc |
| 500 | fb->fb.fix.ywrapstep = 0; |
| 501 | fb->fb.fix.accel = FB_ACCEL_NONE; |
| 502 | |
| 503 | - fb->fb.var.xres = fbwidth; |
| 504 | - fb->fb.var.yres = fbheight; |
| 505 | - fb->fb.var.xres_virtual = fbwidth; |
| 506 | - fb->fb.var.yres_virtual = fbheight; |
| 507 | - fb->fb.var.bits_per_pixel = fbdepth; |
| 508 | + /* If we have data from the VC4 on FB's, use that, otherwise use the |
| 509 | + * module parameters |
| 510 | + */ |
| 511 | + if (fb->display_settings.width) { |
| 512 | + fb->fb.var.xres = fb->display_settings.width; |
| 513 | + fb->fb.var.yres = fb->display_settings.height; |
| 514 | + fb->fb.var.xres_virtual = fb->fb.var.xres; |
| 515 | + fb->fb.var.yres_virtual = fb->fb.var.yres; |
| 516 | + fb->fb.var.bits_per_pixel = fb->display_settings.depth; |
| 517 | + } else { |
| 518 | + fb->fb.var.xres = fbwidth; |
| 519 | + fb->fb.var.yres = fbheight; |
| 520 | + fb->fb.var.xres_virtual = fbwidth; |
| 521 | + fb->fb.var.yres_virtual = fbheight; |
| 522 | + fb->fb.var.bits_per_pixel = fbdepth; |
| 523 | + } |
| 524 | + |
| 525 | fb->fb.var.vmode = FB_VMODE_NONINTERLACED; |
| 526 | fb->fb.var.activate = FB_ACTIVATE_NOW; |
| 527 | fb->fb.var.nonstd = 0; |
| 528 | @@ -757,26 +881,23 @@ static int bcm2708_fb_register(struct bc |
| 529 | fb->fb.monspecs.dclkmax = 100000000; |
| 530 | |
| 531 | bcm2708_fb_set_bitfields(&fb->fb.var); |
| 532 | - init_waitqueue_head(&fb->dma_waitq); |
| 533 | |
| 534 | /* |
| 535 | * Allocate colourmap. |
| 536 | */ |
| 537 | - |
| 538 | fb_set_var(&fb->fb, &fb->fb.var); |
| 539 | + |
| 540 | ret = bcm2708_fb_set_par(&fb->fb); |
| 541 | + |
| 542 | if (ret) |
| 543 | return ret; |
| 544 | |
| 545 | - print_debug("BCM2708FB: registering framebuffer (%dx%d@%d) (%d)\n", |
| 546 | - fbwidth, fbheight, fbdepth, fbswap); |
| 547 | - |
| 548 | ret = register_framebuffer(&fb->fb); |
| 549 | - print_debug("BCM2708FB: register framebuffer (%d)\n", ret); |
| 550 | + |
| 551 | if (ret == 0) |
| 552 | goto out; |
| 553 | |
| 554 | - print_debug("BCM2708FB: cannot register framebuffer (%d)\n", ret); |
| 555 | + dev_warn(fb->fb.dev, "Unable to register framebuffer (%d)\n", ret); |
| 556 | out: |
| 557 | return ret; |
| 558 | } |
| 559 | @@ -785,10 +906,18 @@ static int bcm2708_fb_probe(struct platf |
| 560 | { |
| 561 | struct device_node *fw_np; |
| 562 | struct rpi_firmware *fw; |
| 563 | - struct bcm2708_fb *fb; |
| 564 | - int ret; |
| 565 | + int ret, i; |
| 566 | + u32 num_displays; |
| 567 | + struct bcm2708_fb_dev *fbdev; |
| 568 | + struct { u32 base, length; } gpu_mem; |
| 569 | + |
| 570 | + fbdev = devm_kzalloc(&dev->dev, sizeof(*fbdev), GFP_KERNEL); |
| 571 | + |
| 572 | + if (!fbdev) |
| 573 | + return -ENOMEM; |
| 574 | |
| 575 | fw_np = of_parse_phandle(dev->dev.of_node, "firmware", 0); |
| 576 | + |
| 577 | /* Remove comment when booting without Device Tree is no longer supported |
| 578 | * if (!fw_np) { |
| 579 | * dev_err(&dev->dev, "Missing firmware node\n"); |
| 580 | @@ -796,90 +925,154 @@ static int bcm2708_fb_probe(struct platf |
| 581 | * } |
| 582 | */ |
| 583 | fw = rpi_firmware_get(fw_np); |
| 584 | + fbdev->fw = fw; |
| 585 | + |
| 586 | if (!fw) |
| 587 | return -EPROBE_DEFER; |
| 588 | |
| 589 | - fb = kzalloc(sizeof(*fb), GFP_KERNEL); |
| 590 | - if (!fb) { |
| 591 | - ret = -ENOMEM; |
| 592 | - goto free_region; |
| 593 | + ret = rpi_firmware_property(fw, |
| 594 | + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS, |
| 595 | + &num_displays, sizeof(u32)); |
| 596 | + |
| 597 | + /* If we fail to get the number of displays, or it returns 0, then |
| 598 | + * assume old firmware that doesn't have the mailbox call, so just |
| 599 | + * set one display |
| 600 | + */ |
| 601 | + if (ret || num_displays == 0) { |
| 602 | + num_displays = 1; |
| 603 | + dev_err(&dev->dev, |
| 604 | + "Unable to determine number of FB's. Assuming 1\n"); |
| 605 | + ret = 0; |
| 606 | + } else { |
| 607 | + fbdev->firmware_supports_multifb = 1; |
| 608 | + } |
| 609 | + |
| 610 | + if (num_displays > MAX_FRAMEBUFFERS) { |
| 611 | + dev_warn(&dev->dev, |
| 612 | + "More displays reported from firmware than supported in driver (%u vs %u)", |
| 613 | + num_displays, MAX_FRAMEBUFFERS); |
| 614 | + num_displays = MAX_FRAMEBUFFERS; |
| 615 | } |
| 616 | |
| 617 | - fb->fw = fw; |
| 618 | - bcm2708_fb_debugfs_init(fb); |
| 619 | + dev_info(&dev->dev, "FB found %d display(s)\n", num_displays); |
| 620 | + |
| 621 | + /* Set up the DMA information. Note we have just one set of DMA |
| 622 | + * parameters to work with all the FB's so requires synchronising when |
| 623 | + * being used |
| 624 | + */ |
| 625 | + |
| 626 | + mutex_init(&fbdev->dma_mutex); |
| 627 | |
| 628 | - fb->cb_base = dma_alloc_wc(&dev->dev, SZ_64K, |
| 629 | - &fb->cb_handle, GFP_KERNEL); |
| 630 | - if (!fb->cb_base) { |
| 631 | + fbdev->cb_base = dma_alloc_wc(&dev->dev, SZ_64K, |
| 632 | + &fbdev->cb_handle, |
| 633 | + GFP_KERNEL); |
| 634 | + if (!fbdev->cb_base) { |
| 635 | dev_err(&dev->dev, "cannot allocate DMA CBs\n"); |
| 636 | ret = -ENOMEM; |
| 637 | goto free_fb; |
| 638 | } |
| 639 | |
| 640 | - pr_info("BCM2708FB: allocated DMA memory %pad\n", &fb->cb_handle); |
| 641 | - |
| 642 | ret = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK, |
| 643 | - &fb->dma_chan_base, &fb->dma_irq); |
| 644 | + &fbdev->dma_chan_base, |
| 645 | + &fbdev->dma_irq); |
| 646 | if (ret < 0) { |
| 647 | - dev_err(&dev->dev, "couldn't allocate a DMA channel\n"); |
| 648 | + dev_err(&dev->dev, "Couldn't allocate a DMA channel\n"); |
| 649 | goto free_cb; |
| 650 | } |
| 651 | - fb->dma_chan = ret; |
| 652 | + fbdev->dma_chan = ret; |
| 653 | |
| 654 | - ret = request_irq(fb->dma_irq, bcm2708_fb_dma_irq, |
| 655 | - 0, "bcm2708_fb dma", fb); |
| 656 | + ret = request_irq(fbdev->dma_irq, bcm2708_fb_dma_irq, |
| 657 | + 0, "bcm2708_fb DMA", fbdev); |
| 658 | if (ret) { |
| 659 | - pr_err("%s: failed to request DMA irq\n", __func__); |
| 660 | + dev_err(&dev->dev, |
| 661 | + "Failed to request DMA irq\n"); |
| 662 | goto free_dma_chan; |
| 663 | } |
| 664 | |
| 665 | - pr_info("BCM2708FB: allocated DMA channel %d\n", fb->dma_chan); |
| 666 | + rpi_firmware_property(fbdev->fw, |
| 667 | + RPI_FIRMWARE_GET_VC_MEMORY, |
| 668 | + &gpu_mem, sizeof(gpu_mem)); |
| 669 | + |
| 670 | + for (i = 0; i < num_displays; i++) { |
| 671 | + struct bcm2708_fb *fb = &fbdev->displays[i]; |
| 672 | + |
| 673 | + fb->display_settings.display_num = i; |
| 674 | + fb->dev = dev; |
| 675 | + fb->fb.device = &dev->dev; |
| 676 | + fb->fbdev = fbdev; |
| 677 | + |
| 678 | + fb->gpu.base = gpu_mem.base; |
| 679 | + fb->gpu.length = gpu_mem.length; |
| 680 | + |
| 681 | + if (fbdev->firmware_supports_multifb) { |
| 682 | + ret = rpi_firmware_property(fw, |
| 683 | + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS, |
| 684 | + &fb->display_settings, |
| 685 | + GET_DISPLAY_SETTINGS_PAYLOAD_SIZE); |
| 686 | + } else { |
| 687 | + memset(&fb->display_settings, 0, |
| 688 | + sizeof(fb->display_settings)); |
| 689 | + } |
| 690 | + |
| 691 | + ret = bcm2708_fb_register(fb); |
| 692 | |
| 693 | - fb->dev = dev; |
| 694 | - fb->fb.device = &dev->dev; |
| 695 | + if (ret == 0) { |
| 696 | + bcm2708_fb_debugfs_init(fb); |
| 697 | |
| 698 | - /* failure here isn't fatal, but we'll fail in vc_mem_copy if |
| 699 | - * fb->gpu is not valid |
| 700 | - */ |
| 701 | - rpi_firmware_property(fb->fw, RPI_FIRMWARE_GET_VC_MEMORY, &fb->gpu, |
| 702 | - sizeof(fb->gpu)); |
| 703 | + fbdev->num_displays++; |
| 704 | |
| 705 | - ret = bcm2708_fb_register(fb); |
| 706 | - if (ret == 0) { |
| 707 | - platform_set_drvdata(dev, fb); |
| 708 | - goto out; |
| 709 | + dev_info(&dev->dev, |
| 710 | + "Registered framebuffer for display %u, size %ux%u\n", |
| 711 | + fb->display_settings.display_num, |
| 712 | + fb->fb.var.xres, |
| 713 | + fb->fb.var.yres); |
| 714 | + } else { |
| 715 | + // Use this to flag if this FB entry is in use. |
| 716 | + fb->fbdev = NULL; |
| 717 | + } |
| 718 | + } |
| 719 | + |
| 720 | + // Did we actually successfully create any FB's? |
| 721 | + if (fbdev->num_displays) { |
| 722 | + init_waitqueue_head(&fbdev->dma_waitq); |
| 723 | + platform_set_drvdata(dev, fbdev); |
| 724 | + return ret; |
| 725 | } |
| 726 | |
| 727 | free_dma_chan: |
| 728 | - bcm_dma_chan_free(fb->dma_chan); |
| 729 | + bcm_dma_chan_free(fbdev->dma_chan); |
| 730 | free_cb: |
| 731 | - dma_free_wc(&dev->dev, SZ_64K, fb->cb_base, fb->cb_handle); |
| 732 | + dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base, |
| 733 | + fbdev->cb_handle); |
| 734 | free_fb: |
| 735 | - kfree(fb); |
| 736 | -free_region: |
| 737 | dev_err(&dev->dev, "probe failed, err %d\n", ret); |
| 738 | -out: |
| 739 | + |
| 740 | return ret; |
| 741 | } |
| 742 | |
| 743 | static int bcm2708_fb_remove(struct platform_device *dev) |
| 744 | { |
| 745 | - struct bcm2708_fb *fb = platform_get_drvdata(dev); |
| 746 | + struct bcm2708_fb_dev *fbdev = platform_get_drvdata(dev); |
| 747 | + int i; |
| 748 | |
| 749 | platform_set_drvdata(dev, NULL); |
| 750 | |
| 751 | - if (fb->fb.screen_base) |
| 752 | - iounmap(fb->fb.screen_base); |
| 753 | - unregister_framebuffer(&fb->fb); |
| 754 | - |
| 755 | - dma_free_wc(&dev->dev, SZ_64K, fb->cb_base, fb->cb_handle); |
| 756 | - bcm_dma_chan_free(fb->dma_chan); |
| 757 | - |
| 758 | - bcm2708_fb_debugfs_deinit(fb); |
| 759 | + for (i = 0; i < fbdev->num_displays; i++) { |
| 760 | + if (fbdev->displays[i].fb.screen_base) |
| 761 | + iounmap(fbdev->displays[i].fb.screen_base); |
| 762 | + |
| 763 | + if (fbdev->displays[i].fbdev) { |
| 764 | + unregister_framebuffer(&fbdev->displays[i].fb); |
| 765 | + bcm2708_fb_debugfs_deinit(&fbdev->displays[i]); |
| 766 | + } |
| 767 | + } |
| 768 | |
| 769 | - free_irq(fb->dma_irq, fb); |
| 770 | + dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base, |
| 771 | + fbdev->cb_handle); |
| 772 | + bcm_dma_chan_free(fbdev->dma_chan); |
| 773 | + free_irq(fbdev->dma_irq, fbdev); |
| 774 | |
| 775 | - kfree(fb); |
| 776 | + mutex_destroy(&fbdev->dma_mutex); |
| 777 | |
| 778 | return 0; |
| 779 | } |
| 780 | @@ -894,10 +1087,10 @@ static struct platform_driver bcm2708_fb |
| 781 | .probe = bcm2708_fb_probe, |
| 782 | .remove = bcm2708_fb_remove, |
| 783 | .driver = { |
| 784 | - .name = DRIVER_NAME, |
| 785 | - .owner = THIS_MODULE, |
| 786 | - .of_match_table = bcm2708_fb_of_match_table, |
| 787 | - }, |
| 788 | + .name = DRIVER_NAME, |
| 789 | + .owner = THIS_MODULE, |
| 790 | + .of_match_table = bcm2708_fb_of_match_table, |
| 791 | + }, |
| 792 | }; |
| 793 | |
| 794 | static int __init bcm2708_fb_init(void) |
| 795 | --- a/include/soc/bcm2835/raspberrypi-firmware.h |
| 796 | +++ b/include/soc/bcm2835/raspberrypi-firmware.h |
| 797 | @@ -106,9 +106,15 @@ enum rpi_firmware_property_tag { |
| 798 | RPI_FIRMWARE_FRAMEBUFFER_GET_VIRTUAL_OFFSET = 0x00040009, |
| 799 | RPI_FIRMWARE_FRAMEBUFFER_GET_OVERSCAN = 0x0004000a, |
| 800 | RPI_FIRMWARE_FRAMEBUFFER_GET_PALETTE = 0x0004000b, |
| 801 | + RPI_FIRMWARE_FRAMEBUFFER_GET_LAYER = 0x0004000c, |
| 802 | + RPI_FIRMWARE_FRAMEBUFFER_GET_TRANSFORM = 0x0004000d, |
| 803 | + RPI_FIRMWARE_FRAMEBUFFER_GET_VSYNC = 0x0004000e, |
| 804 | RPI_FIRMWARE_FRAMEBUFFER_GET_TOUCHBUF = 0x0004000f, |
| 805 | RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF = 0x00040010, |
| 806 | RPI_FIRMWARE_FRAMEBUFFER_RELEASE = 0x00048001, |
| 807 | + RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM = 0x00048013, |
| 808 | + RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS = 0x00040013, |
| 809 | + RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS = 0x00040014, |
| 810 | RPI_FIRMWARE_FRAMEBUFFER_TEST_PHYSICAL_WIDTH_HEIGHT = 0x00044003, |
| 811 | RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_WIDTH_HEIGHT = 0x00044004, |
| 812 | RPI_FIRMWARE_FRAMEBUFFER_TEST_DEPTH = 0x00044005, |
| 813 | @@ -117,6 +123,8 @@ enum rpi_firmware_property_tag { |
| 814 | RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_OFFSET = 0x00044009, |
| 815 | RPI_FIRMWARE_FRAMEBUFFER_TEST_OVERSCAN = 0x0004400a, |
| 816 | RPI_FIRMWARE_FRAMEBUFFER_TEST_PALETTE = 0x0004400b, |
| 817 | + RPI_FIRMWARE_FRAMEBUFFER_TEST_LAYER = 0x0004400c, |
| 818 | + RPI_FIRMWARE_FRAMEBUFFER_TEST_TRANSFORM = 0x0004400d, |
| 819 | RPI_FIRMWARE_FRAMEBUFFER_TEST_VSYNC = 0x0004400e, |
| 820 | RPI_FIRMWARE_FRAMEBUFFER_SET_PHYSICAL_WIDTH_HEIGHT = 0x00048003, |
| 821 | RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_WIDTH_HEIGHT = 0x00048004, |
| 822 | @@ -127,9 +135,12 @@ enum rpi_firmware_property_tag { |
| 823 | RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_OFFSET = 0x00048009, |
| 824 | RPI_FIRMWARE_FRAMEBUFFER_SET_OVERSCAN = 0x0004800a, |
| 825 | RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE = 0x0004800b, |
| 826 | + |
| 827 | RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF = 0x0004801f, |
| 828 | RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF = 0x00048020, |
| 829 | RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC = 0x0004800e, |
| 830 | + RPI_FIRMWARE_FRAMEBUFFER_SET_LAYER = 0x0004800c, |
| 831 | + RPI_FIRMWARE_FRAMEBUFFER_SET_TRANSFORM = 0x0004800d, |
| 832 | RPI_FIRMWARE_FRAMEBUFFER_SET_BACKLIGHT = 0x0004800f, |
| 833 | |
| 834 | RPI_FIRMWARE_VCHIQ_INIT = 0x00048010, |
| 835 | @@ -138,6 +149,8 @@ enum rpi_firmware_property_tag { |
| 836 | RPI_FIRMWARE_GET_DMA_CHANNELS = 0x00060001, |
| 837 | }; |
| 838 | |
| 839 | +#define GET_DISPLAY_SETTINGS_PAYLOAD_SIZE 64 |
| 840 | + |
| 841 | #if IS_ENABLED(CONFIG_RASPBERRYPI_FIRMWARE) |
| 842 | int rpi_firmware_property(struct rpi_firmware *fw, |
| 843 | u32 tag, void *data, size_t len); |